递归
以先序遍历为例。
按照递归三部曲来解决:
1、确定递归函数的参数和返回值:
首先要合入两个二叉树,那么参数至少是要传入两个二叉树的根节点,返回值就是合并之后二叉树的根节点。
代码如下:
TreeNode* mergeTrees(TreeNode* t1, TreeNode* t2) {
2、确定终止条件:
因为是传入了两个树,那么就有两个树遍历的节点t1 和 t2,如果t1 == NULL 了,两个树合并就应该是 t2 了(如果t2也为NULL也无所谓,合并之后就是NULL)。
反过来如果t2 == NULL,那么两个数合并就是t1(如果t1也为NULL也无所谓,合并之后就是NULL)。
代码如下:
if (t1 == NULL) return t2; // 如果t1为空,合并之后就应该是t2
if (t2 == NULL) return t1; // 如果t2为空,合并之后就应该是t1
3、确定单层递归的逻辑:
单层递归的逻辑就比较好写了,这里我们重复利用一下t1这个树,t1就是合并之后树的根节点(就是修改了原来树的结构)。
那么单层递归中,就要把两棵树的元素加到一起。
t1->val += t2->val;
接下来t1 的左子树是:合并 t1左子树 t2左子树之后的左子树。
t1 的右子树:是 合并 t1右子树 t2右子树之后的右子树。
最终t1就是合并之后的根节点。
代码如下:
t1->left = mergeTrees(t1->left, t2->left);
t1->right = mergeTrees(t1->right, t2->right);
return t1;
此时前序遍历,完整代码就写出来了,如下:
class Solution {
public:
TreeNode* mergeTrees(TreeNode* t1, TreeNode* t2) {
if (t1 == NULL) return t2; // 如果t1为空,合并之后就应该是t2
if (t2 == NULL) return t1; // 如果t2为空,合并之后就应该是t1
// 修改了t1的数值和结构
t1->val += t2->val; // 中
t1->left = mergeTrees(t1->left, t2->left); // 左
t1->right = mergeTrees(t1->right, t2->right); // 右
return t1;
}
};
Java 代码:
/**
* Definition for a binary tree node.
* public class TreeNode {
* int val;
* TreeNode left;
* TreeNode right;
* TreeNode() {}
* TreeNode(int val) { this.val = val; }
* TreeNode(int val, TreeNode left, TreeNode right) {
* this.val = val;
* this.left = left;
* this.right = right;
* }
* }
*/
class Solution {
public TreeNode mergeTrees(TreeNode root1, TreeNode root2) {
if(root1 == null) return root2;
if(root2 == null) return root1;
root1.val = root1.val + root2.val;
root1.left = mergeTrees(root1.left, root2.left);
root1.right = mergeTrees(root1.right, root2.right);
return root1;
}
}
迭代
class Solution {
// 使用队列迭代
public TreeNode mergeTrees(TreeNode root1, TreeNode root2) {
if (root1 == null) return root2;
if (root2 ==null) return root1;
Queue<TreeNode> queue = new LinkedList<>();
queue.offer(root1);
queue.offer(root2);
while (!queue.isEmpty()) {
TreeNode node1 = queue.poll();
TreeNode node2 = queue.poll();
// 此时两个节点一定不为空,val相加
node1.val = node1.val + node2.val;
// 如果两棵树左节点都不为空,加入队列
if (node1.left != null && node2.left != null) {
queue.offer(node1.left);
queue.offer(node2.left);
}
// 如果两棵树右节点都不为空,加入队列
if (node1.right != null && node2.right != null) {
queue.offer(node1.right);
queue.offer(node2.right);
}
// 若node1的左节点为空,直接赋值
if (node1.left == null && node2.left != null) {
node1.left = node2.left;
}
// 若node1的右节点为空,直接赋值
if (node1.right == null && node2.right != null) {
node1.right = node2.right;
}
}
return root1;
}
}
二叉搜索树是一个有序树:
- 若它的左子树不空,则左子树上所有结点的值均小于它的根结点的值;
- 若它的右子树不空,则右子树上所有结点的值均大于它的根结点的值;
- 它的左、右子树也分别为二叉搜索树
递归法:
/**
* Definition for a binary tree node.
* public class TreeNode {
* int val;
* TreeNode left;
* TreeNode right;
* TreeNode() {}
* TreeNode(int val) { this.val = val; }
* TreeNode(int val, TreeNode left, TreeNode right) {
* this.val = val;
* this.left = left;
* this.right = right;
* }
* }
*/
class Solution {
public TreeNode searchBST(TreeNode root, int val) {
if(root == null || root.val == val) return root;
if(root.val > val) return searchBST(root.left,val);
else return searchBST(root.right,val);
}
}
迭代法:
由于二叉搜索树的有序性,不需要回溯,按照其顺序性搜索即可。
/**
* Definition for a binary tree node.
* public class TreeNode {
* int val;
* TreeNode left;
* TreeNode right;
* TreeNode() {}
* TreeNode(int val) { this.val = val; }
* TreeNode(int val, TreeNode left, TreeNode right) {
* this.val = val;
* this.left = left;
* this.right = right;
* }
* }
*/
class Solution {
public TreeNode searchBST(TreeNode root, int val) {
while(root != null){
if(root.val == val) break;
if(root.val > val) root = root.left;
else root = root.right;
}
return root;
}
}
中序遍历下,输出的二叉搜索树节点的数值是有序序列。
/**
* Definition for a binary tree node.
* public class TreeNode {
* int val;
* TreeNode left;
* TreeNode right;
* TreeNode() {}
* TreeNode(int val) { this.val = val; }
* TreeNode(int val, TreeNode left, TreeNode right) {
* this.val = val;
* this.left = left;
* this.right = right;
* }
* }
*/
class Solution {
Deque<Integer> queue = new ArrayDeque<>();
public boolean isValidBST(TreeNode root) {
inOrder(root);
Integer before = queue.remove();
while(!queue.isEmpty()){
Integer now = queue.remove();
if(before >= now) return false;
before = now;
}
return true;
}
public void inOrder(TreeNode root){
if(root == null) return;
if(root.left!=null) inOrder(root.left);
queue.add(root.val);
if(root.right!=null) inOrder(root.right);
}
}
在递归中直接比较:
1.考虑参数和返回值:
因为可能出现比较一半直接判定false,所以需要返回值,返回值类型是boolean;
参数是TreeNode root;
(用全局变量记录preNode)
2.终止条件
(1)null ; return true
(2) preNode.val >= root.val ; return false
3.每一层的逻辑
左中右 进行 中序遍历
注意在中的位置 更新preNode
/**
* Definition for a binary tree node.
* public class TreeNode {
* int val;
* TreeNode left;
* TreeNode right;
* TreeNode() {}
* TreeNode(int val) { this.val = val; }
* TreeNode(int val, TreeNode left, TreeNode right) {
* this.val = val;
* this.left = left;
* this.right = right;
* }
* }
*/
class Solution {
TreeNode preNode;
public boolean isValidBST(TreeNode root) {
if(root == null) return true;
boolean left = isValidBST(root.left);
if(preNode!=null && preNode.val >= root.val) return false;
preNode = root;
boolean right = isValidBST(root.right);
return left && right;
}
}
Deque<Integer> queue = new ArrayDeque<>();
public int getMinimumDifference(TreeNode root) {
inOrder(root);
Integer pre = queue.remove();
Integer min = Integer.MAX_VALUE;
while(!queue.isEmpty()){
Integer now = queue.remove();
if(min > Math.abs(now - pre)){
min=Math.abs(now - pre);
}
pre = now;
}
return min;
}
public void inOrder(TreeNode root){
if(root == null) return;
inOrder(root.left);
queue.add(root.val);
inOrder(root.right);
}
遇到在二叉搜索树上求什么最值,求差值之类的,都要思考一下二叉搜索树可是有序的,要利用好这一特点。
一种比较麻烦,空间复杂度O(n)的写法,这种方法无论是不是二叉搜索树都可以,没有利用二叉搜索树的性质:
/**
* Definition for a binary tree node.
* public class TreeNode {
* int val;
* TreeNode left;
* TreeNode right;
* TreeNode() {}
* TreeNode(int val) { this.val = val; }
* TreeNode(int val, TreeNode left, TreeNode right) {
* this.val = val;
* this.left = left;
* this.right = right;
* }
* }
*/
class Solution {
Map<Integer,Integer> map = new HashMap<>();
public int[] findMode(TreeNode root) {
preOrder(root);
Integer max = 0;
for(Map.Entry<Integer,Integer> item : map.entrySet()){
if(max < item.getValue()) max = item.getValue();
}
List<Integer> res = new ArrayList<>();
for(Map.Entry<Integer,Integer> item :map.entrySet()){
if(item.getValue() == max) res.add(item.getKey());
}
int[] resArray = new int[res.size()];
for(int i=0; i<res.size() ;i++){
resArray[i]=res.get(i);
}
return resArray;
}
public void preOrder(TreeNode root){
if(root == null) return;
map.put(root.val,map.getOrDefault(root.val,0)+1);
preOrder(root.left);
preOrder(root.right);
}
}
使用O(1)的空间复杂度解决:
在递归过程中记录
/**
* Definition for a binary tree node.
* public class TreeNode {
* int val;
* TreeNode left;
* TreeNode right;
* TreeNode() {}
* TreeNode(int val) { this.val = val; }
* TreeNode(int val, TreeNode left, TreeNode right) {
* this.val = val;
* this.left = left;
* this.right = right;
* }
* }
*/
class Solution {
int count;
int maxCount;
List<Integer> res;
TreeNode pre;
public int[] findMode(TreeNode root) {
count = 0;
maxCount = 0;
res = new ArrayList<>();
pre = null;
inOrder(root);
int[] resArray = new int[res.size()];
for(int i=0; i<res.size() ; i++){
resArray[i] = res.get(i);
}
return resArray;
}
public void inOrder(TreeNode root){
if(root == null) return;
inOrder(root.left);
int rootValue = root.val;
if(pre == null || rootValue == pre.val){
count++;
}else{
count = 1;
}
pre = root;
if(count == maxCount){
res.add(rootValue);
}else if(count > maxCount){
maxCount = count;
res.clear();
res.add(rootValue);
}
inOrder(root.right);
}
}
思路
遇到这个题目首先想的是要是能自底向上查找就好了,这样就可以找到公共祖先了。
那么二叉树如何可以自底向上查找呢?
回溯啊,二叉树回溯的过程就是从低到上。
后序遍历(左右中)就是天然的回溯过程,可以根据左右子树的返回值,来处理中节点的逻辑。
判断逻辑是 如果递归遍历遇到q,就将q返回,遇到p 就将p返回,那么如果 左右子树的返回值都不为空,说明此时的中节点,一定是q 和p 的最近祖先。并且这种判断逻辑包含了p是q的祖先结点或者q是p的祖先结点的特殊情况。
递归三部曲:
- 确定递归函数返回值以及参数
需要递归函数返回值,来告诉我们是否找到节点q或者p,那么返回值为bool类型就可以了。
但我们还要返回最近公共节点,可以利用上题目中返回值是TreeNode * ,那么如果遇到p或者q,就把q或者p返回,返回值不为空,就说明找到了q或者p。
TreeNode* lowestCommonAncestor(TreeNode* root, TreeNode* p, TreeNode* q)
- 确定终止条件
遇到空的话,因为树都是空了,所以返回空。
如果 root == q,或者 root == p,说明找到 q p ,则将其返回,这个返回值,后面在中节点的处理过程中会用到,那么中节点的处理逻辑,下面讲解。
if (root == q || root == p || root == NULL) return root;
- 确定单层递归逻辑
本题函数有返回值,是因为回溯的过程需要递归函数的返回值做判断。本题需要遍历整棵树而不是部分树(一条边)
搜索一条边的写法:
if (递归函数(root->left)) return ;
if (递归函数(root->right)) return ;
搜索整个树写法:
left = 递归函数(root->left); // 左
right = 递归函数(root->right); // 右
left与right的逻辑处理; // 中
在递归函数有返回值的情况下:如果要搜索一条边,递归函数返回值不为空的时候,立刻返回,如果搜索整个树,直接用一个变量left、right接住返回值,这个left、right后序还有逻辑处理的需要,也就是后序遍历中处理中间节点的逻辑(也是回溯)。
TreeNode* left = lowestCommonAncestor(root->left, p, q);
TreeNode* right = lowestCommonAncestor(root->right, p, q);
if (left == NULL && right != NULL) return right;
else if (left != NULL && right == NULL) return left;
else { // (left == NULL && right == NULL)
return NULL;
}
总结
这道题目刷过的同学未必真正了解这里面回溯的过程,以及结果是如何一层一层传上去的。
那么我给大家归纳如下三点:
-
求最小公共祖先,需要从底向上遍历,那么二叉树,只能通过后序遍历(即:回溯)实现从底向上的遍历方式。
-
在回溯的过程中,必然要遍历整棵二叉树,即使已经找到结果了,依然要把其他节点遍历完,因为要使用递归函数的返回值(也就是代码中的left和right)做逻辑判断。
-
要理解如果返回值left为空,right不为空为什么要返回right,为什么可以用返回right传给上一层结果。
可以说这里每一步,都是有难度的,都需要对二叉树,递归和回溯有一定的理解。
/**
* Definition for a binary tree node.
* public class TreeNode {
* int val;
* TreeNode left;
* TreeNode right;
* TreeNode(int x) { val = x; }
* }
*/
class Solution {
public TreeNode lowestCommonAncestor(TreeNode root, TreeNode p, TreeNode q) {
if(root == null || root == p || root == q) return root;
TreeNode left = lowestCommonAncestor(root.left,p,q);
TreeNode right = lowestCommonAncestor(root.right,p,q);
if(left != null && right != null) return root;
else if(left != null) return left;
else return right;
}
}
一层的逻辑如果写具体一些,是这样的:
if(left != null && right != null) return root;
else if(left==null && right !=null) return right;
else if (left!=null && right == null) return left;
else return null;
本节总结参考:本周小结!(二叉树系列四)