二叉树
1 二叉树与分治递归
几乎所有的二叉树问题都可以通过分治解决,包括二叉树的遍历、二叉树中求最大分支长度、最大深度任意两个结点间的最大距离,搜索二叉树,平衡二叉树 等等,只要题目中没有严格的时间性能的要求,使用递归的方法时都可以优先考虑分治递归的方法,除了二叉树的层序遍历以外。但然很多题目虽然也可以用变通递归来实现,但解题过程更难想到。
1.1 分治递归与普通递归的模板
Template 1: Traverse
public class Solution {
public void traverse(TreeNode root) {
if (root == null) {
return;
}
// do something with root
traverse(root.left);
// do something with root
traverse(root.right);
// do something with root
}
}
Tempate 2: Divide & Conquer
public class Solution {
public ResultType traversal(TreeNode root) {
// null or leaf
if (root == null) {
// do something and return;
}
// Divide
ResultType left = traversal(root.left);
ResultType right = traversal(root.right);
// Conquer
ResultType result = Merge from left and right.
return result;
}
}
1.2 经典的二叉树前中后遍历也可以用分治递归方法
/*method2 division and conquer*/
vector<int> preorderTraversal(TreeNode *root) {
vector<int> result;
if (root == NULL) {
return result;
}
//division
vector<int> left, right;
left = preorderTraversal(root->left);
right = preorderTraversal(root->right);
//conquer
result.push_back(root->val);
result.insert(result.end(), left.begin(), left.end());
result.insert(result.end(), right.begin(), right.end());
return result;
}
2 二叉树问题用非递归方法解决
二叉树问题用非递归方法解决一般是题目要求时间有限,不能使用递归方式,或者直接要求使用非递归方法。
2.1 二叉树前序遍历非递归
void preorder(TreeNode *root) {
if (root == NULL) {
return;
}
stack<TreeNode*> stk;
stk.push(root);
while (!stk.empty()) {
TreeNode *curt = stk.top();
cout<<curt->val<<" ";
stk.pop();
if (curt->right != NULL) {
stk.push(curt->right);
}
if (curt->left != NULL) {
stk.push(curt->left);
}
}
}
2.2 二叉树中序遍历非递归
二叉树中序遍历的非递归考的比较少,后序遍历的非递归考得就更少了。中序遍历的非递归除了使用栈以外,还需要维护一个当前指针root,具体思路如下:
- 首先使用root找到左下方第一个没有左子树的结点并且入栈,并把沿途结点也全部入栈;
- 访问当前栈顶元素,并把对当前结点的右子树进行步骤 1);
- 如果栈空结束结束该过程;
- 因为刚开始进行循环与最终结束循环时,栈都为空,为了使用开始状态时,能够进入循环,需要将循环判断改为(!stk.empty() || root != NULL),使用root != NULL进入循环;
void inorder(TreeNode *root) {
if (root == NULL) {
return;
}
stack<TreeNode*> stk;
while (!stk.empty() || root != NULL) {
while (root != NULL) {
stk.push(root);
root = root->left;
}
root = stk.top();
cout<<root->val<<" ";
stk.pop();
root = root->right;
}
}
2.3 二叉树的层序遍历
使用队列遍历,注意应该先将右子树放入,再将左子树放入,这样才能每层从左向右访问。
void levelorder(TreeNode *root) {
if (root == NULL) {
return NULL;
}
queue<TreeNode*> que;
que.push(root);
while (!que.empty()) {
TreeNode *node = que.front();
que.pop();
cout<<node->val<<" ";
if (node->left != NULL) {
que.push(node->left);
}
if (node->right != NULL) {
que.push(node->right);
}
}
}
2.4 图的层序遍历
说到二叉树层序遍历,那自然也不得不讨论一下逻辑完全相同的图的BFS,图的大部分问题都需要遍历来解决,而且优先也考虑使用BFS而非DFS,因为BFS一般找到访问过的点的情况要比DFS少。
void searchNode(vector<UndirectedGraphNode*>& graph, map<UndirectedGraphNode*, int>& values, UndirectedGraphNode* node, int target) {
if (node == NULL) {
return ;
}
queue<UndirectedGraphNode*> que;
que.push(node);
unordered_set<UndirectedGraphNode*> hash;
hash.insert(node);
while(!que.empty()) {
UndirectedGraphNode *cur = que.front();
que.pop();
cout<<cur->val;
for (int i = 0; i < cur->neighbors.size(); i++) {
if (hash.find(cur->neighbors[i]) == hash.end()) {
hash.insert(cur);
que.push(cur->neighbors[i]);
}
}
}
}
从上面的代码可以看出,图的BFS遍历与二叉树的遍历过程相比:
- 需要维护一个hash表来判断该邻居是否遍历过,只有没有遍历过的邻居才会加入队列进行之后的访问。
- 因为二叉树只有两个孩子,但是图的每个结点的邻居个数不确定,因此需要循环判断是否要将图的邻居是否入队。
3 本章遇到的值得回味题目
Lowest Common Ancestor
Binary Tree Maximum Path Sum
Validate Binary Search Tree
本题技巧:
- 为了简化代码,空节点可以初始化为最大值,与最小值 来方便后续的比较;
- 本题给出的测试样本中有些就是INT_MAX或者INT_MIN,因此我们空节点初始化时应该初始为LONG_MAX与LONG_MIN;