本篇内容主要从递归角度出发,解决树经典算法问题,这些经典问题分为3类,分别是双指针问题:判断树是否相同、是否对称以及树的合并,路径问题:查找二叉树的全部路径、查找是否存在路径和为指定值的路径,反转问题:反转二叉树。这些问题深度巩固树的递归算法,易形成解决该类问题思路,且该类问题的解决思路有共通之处,值得认真复盘总结!
1.二叉树双指针
实际上就是定义两个变量来解决树种问题。
1.1判断两棵树是否相同
两棵树如何判断是否相同?很显然,当p,q两个树都是空,则他们相同;当p,q其中一个为空,另一个非空,则它们不相同;当p,q两个树都不空时,pq左右子树相同且pq值相同,则pq两棵树相同。
厘清这个逻辑之后,我们发现这就是一个简单的递归问题,递归出口已经明细:当p,q两个树都是空,则他们相同;当p,q其中一个为空,另一个非空,则它们不相同;递归逻辑:当p,q两个树都不空时,pq左右子树相同且pq值相同,则pq两棵树相同(后序遍历);当p,q两个树都不空时,pq左子树相同,pq值相同,pq右子树相同,则pq两棵树相同(中序遍历);当p,q两个树都不空时,pq值相同且pq左右子树相同,则pq两棵树相同(前序遍历)。
厘清思路,直接上代码!
public static boolean myIsSameTree(TreeNode p, TreeNode q) {
if(p == null && q == null) return true;
if(p == null || q == null) return false;
if(p.val != q.val) return false;
return myIsSameTree(p.left, q.left) && myIsSameTree(p.right, q.right);
}
1.2判断对称二叉树
对称二叉树,就是左右子树镜像相等,是不是这个问题又转变成了1.1判断两个树是否相同了,不过变化点在于,这里镜像相等需要的是p树的左孩子和q树的右孩子相等。
那么递归出口和递归逻辑依旧如1.1中所描述的那样,直接上代码!
public static boolean isSymmetric(TreeNode root) {
if(root == null) return true;
return check(root.left, root.right);
}
public static boolean check(TreeNode p, TreeNode q) {
if(p == null && q == null) return true;
if(p == null || q == null) return false;
if(p.val != q.val) return false;
return check(p.left, q.right) && check(p.right, q.left);
}
1.3合并二叉树
二叉树的合并问题,需要理解清楚以下几个情况:
1.树节点合并树节点:节点值相加
2.树节点合并空:新建一个树节点
3.空合并空:仍为空
很明显,递归出口已经找到:树节点合并空:新建一个树节点;空合并空:仍为空。递归逻辑:先合并根节点,再合并左右子树(前序遍历)!
厘清思路,直接上代码!
public static TreeNode myMergeTrees(TreeNode t1, TreeNode t2) {
if(t1 == null && t2 == null) return null;
if(t1 == null || t2 == null) return t1 == null ? t2 : t1;
TreeNode newNode = new TreeNode(t1.val + t2.val);
newNode.left = myMergeTrees(t1.left, t2.left);
newNode.right = myMergeTrees(t1.right, t2.right);
return newNode;
}
2.路径专题
2.1二叉树的所有路径
如何求二叉树的一条路径呢?当我们遍历到叶子节点的时候,记录遍历顺序,就可以得到一条树的路径。
那么,如何求二叉树的所有路径呢?我们需要记录当前访问的路径信息,并且能够恢复回到上一个树节点的路径信息。如何解决?答:这涉及了信息恢复,当然使用递归,压栈的时候保存当前环境信息和局部变量,出栈的时候恢复当前环境信息和局部变量,并且根据这个逻辑,路径信息应该保存在局部变量中,且该局部变量应该能随递归调用发生变化,显然应该使用形参来保存路径信息!
递归出口:树为空,则直接返回;树为叶子节点,使用”全局变量“保存路径信息。
递归逻辑:记录当前节点的路径信息,接着找左子树的路径,接着找右子树的路径(前/后序遍历)
厘清逻辑,直接上代码!
public static List<String> binaryTreePaths(TreeNode root) {
ArrayList<String> res = new ArrayList<>();
dfs(root, "", res);
return res;
}
private static void dfs(TreeNode root, String path, List<String> res){
if(root == null) return;
if(root.left == null && root.right == null){
path += root.val;
res.add(path);
return;
}
dfs(root.left, path + root.val + "->", res);
dfs(root.right, path + root.val + "->", res);
}
2.2路径总和
对于该问题而言,如果树为空,则不存在这样的路径;如果树仅有一个根节点,判断根节点的值是否等于target,不等于则不存在这样的路径;如果是非空树,则寻找左右子树中路径和为targe-val的路径。
那么,该递归问题的递归出口已经清晰:如果树为空,则不存在这样的路径;如果树仅有一个根节点,判断根节点的值是否等于target,不等于则不存在这样的路径。递归逻辑:如果是非空树,则寻找左或右子树中,是否存在路径和为targe-val的路径(前/后序遍历)。
厘清思路,直接上代码!
public static boolean myHasPathSum(TreeNode root, int sum) {
//空树不满足路径和
if(root == null) return false;
//叶子节点
if(root.left == null && root.right == null){
return sum == root.val;
}
//左子树路径和为sum-val右子树路径和为sum-val
return myHasPathSum(root.left, sum - root.val)
|| myHasPathSum(root.right, sum- root.val);
}
3.翻转问题
对于树的翻转,需要将左右子树进行翻转。
如果从下至上看,下层子树翻转后,只需要将上层父节点翻转即可(后序遍历)!厘清思路,直接上代码!
public static TreeNode invertTree_2(TreeNode root) {
if (root == null) {
return null;
}
TreeNode left = invertTree_2(root.left);
TreeNode right = invertTree_2(root.right);
root.left = right;
root.right = left;
return root;
}
如果从上至下看,顶层树节点进行翻转,然后子树在进行翻转(前序遍历)!厘清思路,直接上代码!
public static TreeNode myInvertTree_1(TreeNode root) {
if(root == null) return null;
TreeNode tmp = root.left;
root.left = root.right;
root.right = tmp;
myInvertTree_1(root.left);
myInvertTree_1(root.right);
return root;
}
OK,《算法通关村第八关——树经典算法青铜挑战笔记》结束,喜欢的朋友三联加关注!关注鱼市带给你不一样的算法小感悟!(幻听)
再次,感谢鱼骨头教官的学习路线!鱼皮的宣传!小y的陪伴!ok,拜拜,第八关第二幕见!