解决树问题用的比较多的也是DFS算法,接下来介绍一下我之前容易错的几道有关树的遍历的题目:
nowCoder:二叉树的镜像
思路:操作给定的二叉树,将其变换为源二叉树的镜像。 递归的交换左右子树即可:
public void Mirror(TreeNode root) {
if(root==null){
return ;
}
TreeNode temp = null;
temp = root.left;
root.left = root.right;
root.right = temp;
Mirror(root.left);
Mirror(root.right);
}
101. 对称二叉树
思路:给定一个二叉树,检查它是否是镜像对称的。
public boolean isSymmetric(TreeNode root) {
if(root == null) return true;
return dfs(root.right,root.left);
}
public boolean dfs(TreeNode right,TreeNode left){
if(left == null && right == null) return true;
if(left == null || right == null || right.val != left.val) return false;
return dfs(right.right,left.left)&&dfs(right.left,left.right);
}
543. 二叉树的直径
思路:给定一棵二叉树,你需要计算它的直径长度。一棵二叉树的直径长度是任意两个结点路径长度中的最大值。这条路径可能穿过也可能不穿过根结点。
这题如果看懂了题目就很简单,也就是说 求每个节点的左右子树的高度和的最大值(因为最长路径是可能不经过根节点的),我们只需要遍历所有节点,然后维护一个全局最大值不断的更新它就行了:
public int max = 0;
public int diameterOfBinaryTree(TreeNode root) {
depth(root);
return max;
}
public int depth(TreeNode root) {
if (root == null) return 0;
int leftDepth = depth(root.left);
int rightDepth = depth(root.right);
max = Math.max(max, leftDepth + rightDepth);
return Math.max(leftDepth, rightDepth) + 1;
}
110. 平衡二叉树
思路:给定一个二叉树,判断它是否是高度平衡的二叉树。
我们都知道, 平衡二叉树是指二叉树每个节点 的左右两个子树的高度差的绝对值不超过1。那我们只需要遍历每个节点,然后比较每个节点的左右子树的高度查,维护一个boolean类型的标志即可,需要注意的是,这个题目间接考察如何计算二叉树的高度,所以递归函数的返回值应该是int类型用来计算高度值:
public boolean result = true;
public boolean isBalanced(TreeNode root) {
maxDepth(root);
return result;
}
public int maxDepth(TreeNode root) {
if (root == null) return 0;
int l = maxDepth(root.left);
int r = maxDepth(root.right);
if (Math.abs(l - r) > 1) result = false;
return 1 + Math.max(l, r);
}
113. 路径总和 II
思路:给定一个二叉树和一个目标和,找到所有从根节点到叶子节点路径总和等于给定目标和的路径。这一题的题解我已经写过哦,点这里参考哦(●ˇ∀ˇ●)
List<List<Integer>> res = new ArrayList<>();
public List<List<Integer>> pathSum(TreeNode root, int sum) {
List<Integer> list = new ArrayList<>();
dfs(root,sum,list);
return res;
}
public void dfs(TreeNode root,int sum,List<Integer> list){
if(root == null) return;
sum -= root.val;
list.add(root.val);
//if(sum<0) return;
if(sum == 0 && root.left == null && root.right == null) {
res.add(new ArrayList<>(list));
//return;
}
dfs(root.right,sum,list);
//list.remove(list.size()-1);
dfs(root.left,sum,list);
list.remove(list.size()-1);
}
437. 路径总和 III
思路:给定一个二叉树,它的每个结点都存放着一个整数值。找出路径和等于给定数值的路径总数。路径不需要从根节点开始,也不需要在叶子节点结束,但是路径方向必须是向下的(只能从父节点到子节点)。
这一题可以说我已经错过无数次了,主要是因为审题不清,首先该路径不一定是根节点开始叶子节点结束(✿◕‿◕✿),因此,我们在进行递归的时候不可以只从根节点开始!所以我们需要用到双重递归,首先遍历每一个节点,然后把每一个节点作为起点继续遍历其后续节点o(▽)q
public int pathnumber;
public int pathSum(TreeNode root, int sum) {
if(root == null) return 0;
Sum(root,sum);
pathSum(root.left,sum);
pathSum(root.right,sum);
return pathnumber;
}
public void Sum(TreeNode root, int sum){
if(root == null) return;
sum-=root.val;
if(sum == 0){
pathnumber++;
}
Sum(root.left,sum);
Sum(root.right,sum);
}
572. 另一个树的子树
思路:给定两个非空二叉树 s 和 t,检验 s 中是否包含和 t 具有相同结构和节点值的子树。s 的一个子树包括 s 的一个节点和这个节点的所有子孙。s 也可以看做它自身的一棵子树。
这一题的思路和上一题类似,也是用的双重递归,因为t树的根节点有可能是s树的任意节点,所以应该将s所有的节点作为起始节点遍历一边即可,但是这一题需要注意的是,这一题要求 s 的一个子树包括 s 的一个节点和这个节点的所有子孙:
public boolean isSubtree(TreeNode s, TreeNode t) {
if(s == null && t == null) return true;
if (s == null) return false;
return isSubtreeWithRoot(s, t) || isSubtree(s.left, t) || isSubtree(s.right, t);
}
public boolean isSubtreeWithRoot(TreeNode s, TreeNode t) {
if (t == null && s == null) return true;
if (t == null || s == null) return false;//其中某一个先结束时不可以的!!
if (t.val != s.val) return false;
return isSubtreeWithRoot(s.left, t.left) && isSubtreeWithRoot(s.right, t.right);
}
nowCoder:树的子结构
思路:输入两棵二叉树A,B,判断B是不是A的子结构。(ps:我们约定空树不是任意一个树的子结构)。这一题和上一题类似, 唯一的区别是没有“s 的一个子树包括 s 的一个节点和这个节点的所有子孙。”的限制,所以只需要修改一下递归函数的出口即可(只需遍历完子树即可返回true,不用再继续匹配子节点了):
public boolean HasSubtree(TreeNode root1, TreeNode root2) {
if (root1 == null || root2 == null)
return false;
return isSubtreeWithRoot(root1, root2) || HasSubtree(root1.left, root2) || HasSubtree(root1.right, root2);
}
public boolean isSubtreeWithRoot(TreeNode root1, TreeNode root2) {
if (root2 == null)//root2遍历完就行了,root1是否到叶子节点并不重要了
return true;
if (root1 == null)
return false;
if (root1.val != root2.val)
return false;
return isSubtreeWithRoot(root1.left, root2.left) && isSubtreeWithRoot(root1.right, root2.right);
}
nowCoder:对称的二叉树
思路:判断一个二叉树是否对称,只需递归的判断左右子树即可,思路和上一题是类似的:
boolean isSymmetrical(TreeNode pRoot) {
if (pRoot == null)
return true;
return isSymmetrical(pRoot.left, pRoot.right);
}
boolean isSymmetrical(TreeNode t1, TreeNode t2) {
if (t1 == null && t2 == null)
return true;
if (t1 == null || t2 == null)
return false;
if (t1.val != t2.val)
return false;
return isSymmetrical(t1.left, t2.right) && isSymmetrical(t1.right, t2.left);
}
111. 二叉树的最小深度
思路:给定一个二叉树,找出其最小深度。最小深度是从根节点到最近叶子节点的最短路径上的节点数量。
于是我按照求二叉树深度的方法:
public int minDepth(TreeNode root) {
return dfs(root);
}
public int dfs(TreeNode root){
if(root == null) return 0;
int left = dfs(root.left);
int right = dfs(root.right);
int min = Math.min(left,right);
return min+1;
}
这又是一个阅读理解题,因为我又又又败在题目上了,我枯了(っ °Д °;)っ。注意,题目要求的是叶子节点!所以测试用例【1,2】我的输出是1就错了,1并不是叶子节点!
所以应该再加一个判断如果根节点的左或右子树为空的话是构不成子树的!。
这一题真的是做一次错一次,再次强调一下!!当某一个节点有一个子节点为空时不可以用math.min直接取判断左右的最小!!要分情况!!
所以完整代码如下:
public int minDepth(TreeNode root) {
if (root == null) {
return 0;
} // null节点不参与比较
if (root.left == null && root.right != null) {
return 1 + minDepth(root.right);
} // null节点不参与比较
if (root.right == null && root.left != null) {
return 1 + minDepth(root.left);
}
return 1 + Math.min(minDepth(root.left), minDepth(root.right));
}
404. 左叶子之和
思路:统计所有左叶子结点的值,举个例子:
3
/ \
9 20
/ \
15 7
There are two left leaves in the binary tree, with values 9 and 15 respectively. Return 24.
首先,叶子节点要满足其没有子节点,并且自身也不为空,那么就分为几种情况,若没有右节点,则去左节点找,若有左节点则判断左节点是不是叶子节点,若不是则继续往左节点和右节点中找,完整递归的过程如下:
public int sumOfLeftLeaves(TreeNode root) {
if(root==null){
return 0;
}else if(root.left==null){
return sumOfLeftLeaves(root.right);
}else{
if(root.left.left==null&&root.left.right==null){
return root.left.val+sumOfLeftLeaves(root.right);
}else{
return sumOfLeftLeaves(root.left)+sumOfLeftLeaves(root.right);
}
}
}
其实可以简化一下,因为很多可能可以合并的:
public int sumOfLeftLeaves(TreeNode root) {
if(root==null) return 0;
int res = 0;
if(root.left!=null&&root.left.left==null&&root.left.right==null){
res = res+root.left.val;
}
return sumOfLeftLeaves(root.left) + sumOfLeftLeaves(root.right) + res;
}
接下来看一下我写的两个其他的易错答案:
int res = 0;//放在外面时里面不能重复累加
public int sumOfLeftLeaves(TreeNode root) {
if(root == null) return 0;
//int res = 0;
if(root.left != null && root.left.left == null && root.left.right == null){
res = root.left.val + sumOfLeftLeaves(root.right);
}else{
res = sumOfLeftLeaves(root.left) + sumOfLeftLeaves(root.right);
}
return res;
}
//int res = 0;//此时不可以放在外面,因为会导致重复累加
public int sumOfLeftLeaves(TreeNode root) {
if(root == null) return 0;
int res = 0;
if(root.left != null && root.left.left == null && root.left.right == null){
res = res + root.left.val + sumOfLeftLeaves(root.right);
}else{
res = res + sumOfLeftLeaves(root.left) + sumOfLeftLeaves(root.right);
}
return res;
}
所以最好的答案是!!:
public int sumOfLeftLeaves(TreeNode root) {
if(root == null) return 0;
if(root.left != null && root.left.left == null && root.left.right == null){
return root.left.val + sumOfLeftLeaves(root.right);
}else{
return sumOfLeftLeaves(root.left) + sumOfLeftLeaves(root.right);
}
}
687. 最长同值路径
思路:给定一个二叉树,找到最长的路径,这个路径中的每个节点具有相同值。 这条路径可以经过也可以不经过根节点。
1
/ \
4 5
/ \ \
4 4 5
Output : 2
先来展示我的奇葩答案,ԾㅂԾ,:
int max = 0;
public int longestUnivaluePath(TreeNode root) {
if(root == null) return 0;
return Math.max(dfs(root,root.val,1),Math.max(longestUnivaluePath(root.left),longestUnivaluePath(root.right)));
}
public int dfs(TreeNode root,int pre,int sum){
if(root == null) return 0;
if(pre == root.val){
sum++;
return Math.max(dfs(root.left,pre,sum),dfs(root.right,pre,sum))+sum;
}else{
sum = 1;
return Math.max(dfs(root.left,root.val,sum),dfs(root.right,root.val,sum))+sum;
}
}
我的想法是遍历每个节点,然后找他们的最长路径,这个答案错的很明显因为没有考虑左右子树会连成一条通路,执着的我是不会放弃的!坚决不参考别人的答案!( •̀ ω •́ )✧,于是我又修改了代码:
int max = 0;
public int longestUnivaluePath(TreeNode root) {
if(root == null) return 0;
return Math.max(dfs(root,root.val),Math.max(longestUnivaluePath(root.left),longestUnivaluePath(root.right)));
}
public int dfs(TreeNode root,int pre){
if(root == null) return 0;
int left = 0;
int right = 0;
if(root.left != null){
left = root.left.val == pre? dfs(root.left,pre) + 1:0;
}
if(root.right != null){
right = root.right.val == pre? dfs(root.right,pre) + 1:0;
}
max = Math.max(max,left+right);
return Math.max(left,right);
}
但是跑出来还是有问题,因为dfs函数的返回值并不是我们要的最大路径长,但是我们不能将dfs函数后的返回值改为max,因为这会影响更新,所以直接在主函数返回max就好啦!主函数改成这样就可以啦:
public int longestUnivaluePath(TreeNode root) {
if(root == null) return 0;
dfs(root,root.val);
longestUnivaluePath(root.left);
longestUnivaluePath(root.right);
return max;
}
也可以只用到一个参数:
int max = 0;
public int longestUnivaluePath(TreeNode root) {
if(root == null) return 0;
dfs(root);
longestUnivaluePath(root.left);
longestUnivaluePath(root.right);
return max;
}
public int dfs(TreeNode root){
if(root == null) return 0;
int left = 0;
int right = 0;
if(root.left != null && root.val == root.left.val){
left = dfs(root.left) + 1;
}
if(root.right != null && root.val == root.right.val){
right = dfs(root.right) + 1;
}
max = Math.max(max,right + left);
return Math.max(right,left);
}
但是这样一想,完全没必要把这三行代码放在主函数中呀,直接放在dfs函数里遍历不是更方便?
但是需要注意的是,删除其中两个调用longestUnivaluePath函数的代码是肯定不够的,还需要对dfs函数体进行修改,因为上面的dfs函数体一直比较的是和最初pre的值,但是如果不用双重递归的话,pre的值必须动态的变化,也就是只和前一个父节点比较,完整代码如下:
int ans;
public int longestUnivaluePath(TreeNode root) {
ans = 0;
arrowLength(root);
return ans;
}
public int arrowLength(TreeNode node) {
if (node == null) return 0;
int left = arrowLength(node.left);
int right = arrowLength(node.right);
int arrowLeft = 0, arrowRight = 0;
if (node.left != null && node.left.val == node.val) {
arrowLeft = left + 1;
}
if (node.right != null && node.right.val == node.val) {
arrowRight = right + 1;
}
ans = Math.max(ans, arrowLeft + arrowRight);
return Math.max(arrowLeft, arrowRight);
}
543. 二叉树的直径
思路:给定一棵二叉树,你需要计算它的直径长度。一棵二叉树的直径长度是任意两个结点路径长度中的最大值。这条路径可能穿过也可能不穿过根结点。
1
/ \
2 3
/ \
4 5
返回 3, 它的长度是路径 [4,2,1,3] 或者 [5,2,1,3]。
解题思路和上一题是一样的,可以将二叉树的直径转换为:二叉树的 每个节点的左右子树的高度和 的最大值。
public int max = 0;
public int diameterOfBinaryTree(TreeNode root) {
dfs(root);
return max;
}
public int dfs(TreeNode root){
if(root == null) return 0;
int left = dfs(root.left);
int right = dfs(root.right);
int sum = left + right;
max = Math.max(sum,max);
return Math.max(left,right) + 1;
}
671. 二叉树中第二小的节点
思路:给定一个非空特殊的二叉树,每个节点都是正数,并且每个节点的子节点数量只能为 2 或 0。如果一个节点有两个子节点的话,那么该节点的值等于两个子节点中较小的一个。给出这样的一个二叉树,你需要输出所有节点中的第二小的值。如果第二小的值不存在的话,输出-1 。例如:
Input:
2
/ \
2 5
/ \
5 7
Output: 5
首先我们直到根节点就是最小值,所欲从左右子树找最小节点即可:
public int findSecondMinimumValue(TreeNode root) {
if (root == null) return -1;
if (root.left == null && root.right == null) return -1;
int leftVal = root.left.val;
int rightVal = root.right.val;
if (leftVal == root.val) leftVal = findSecondMinimumValue(root.left);
if (rightVal == root.val) rightVal = findSecondMinimumValue(root.right);
if (leftVal != -1 && rightVal != -1) return Math.min(leftVal, rightVal);
if (leftVal != -1) return leftVal;
return rightVal;
}
还可以简化一下:
public int findSecondMinimumValue(TreeNode root) {
return myfun(root, root.val);
}
public int myfun(TreeNode root, int val) {
if (root == null) return -1;
if (root.val > val) return root.val;
int l = myfun(root.left, val);
int r = myfun(root.right, val);
if (l > val && r > val) return Math.min(l,r);
return Math.max(l,r);
}
最后一行返回最大值是因为不满足以上条件的一定是左右子树的值等于根节点的值,此时取一个较大的(这个数一定是等于根节点值或者比根节点值略大一点的,也就是所需结果) 。
236. 二叉树的最近公共祖先
思路:这一题和256类似,区别是256是一个搜索二叉树,这样可以利用二叉搜索树的特性,但是这一题是普通二叉树,所以得使用不同的方法。
首先对于根节点,如果是p或者q的话,可以直接返回该root,因为如果公共祖先是自己(pq),并不需要寻找另外一个,我们在执行前序遍历会先找上面的,后找下面的,我们会直接返回公共祖先。->
然后我们分别对左右节点进行递归,这就分为几种情况,如果左右节点递归后结果都不为null,就说明pq都出现了,这就是,公共祖先,此时不用考虑公共祖先是自己的情况,因为上面已经做过判断了。如果其中只有一个不为空则我们返回已经找到的那个值(存储在left,与right中),p或者q。根据刚才叙述的几种情况,我们用代码表示:
public TreeNode LowestCommonAncestor(TreeNode root, TreeNode p, TreeNode q) {
if (root == null) {
return root;
}
if (root == p || root == q) {//当找到p或者q的是时候就会返回pq
return root; }
TreeNode left = LowestCommonAncestor(root.left, p, q);//返回的结点进行保存,可能是null
TreeNode right = LowestCommonAncestor(root.right, p, q);//也可能是pq,还可能是公共祖先
if (left != null && right != null) {
return root;
} else if (left != null) {
return left;//还有一种可能就是,由下面返回的公共祖先,并将这个值一路返回到最表层
} else if (right != null) {
return right;
}
return null;
}
这样看起来有些乱,我们整理一下,简化一下代码:
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);
return left == null ? right : right == null ? left : root;
}