对于树,经常用的算法有递归,回溯,BFS,DFS等。下面是一些用递归算法来解的题:
104,二叉树的最大深度,easy
104,二叉树的最大深度,easy
给定一个二叉树,找出其最大深度。
二叉树的深度为根节点到最远叶子节点的最长路径上的节点数。
说明: 叶子节点是指没有子节点的节点。
示例:
给定二叉树 [3,9,20,null,null,15,7],
3
/ \
9 20
/ \
15 7
返回它的最大深度 3 。
-
代码:
class Solution { public int maxDepth(TreeNode root) { if(root == null) return 0; int leftDepth = maxDepth(root.left); int rightDepth = maxDepth(root.right); return leftDepth >= rightDepth ? leftDepth + 1 : rightDepth + 1; } }
110,平衡二叉树,easy
给定一个二叉树,判断它是否是高度平衡的二叉树。
本题中,一棵高度平衡二叉树定义为:
一个二叉树每个节点 的左右两个子树的高度差的绝对值不超过 1 。
示例 1:
输入:root = [3,9,20,null,null,15,7]
输出:true
示例 2:
输入:root = [1,2,2,3,3,null,null,4,4]
输出:false
示例 3:
输入:root = []
输出:true
-
思想:先判断root 为根节点的树是不是平衡二叉树,(即比较左右子树的高度差是否不超过1),再判断以root.left 和 root.right 为根节点的树是不是平衡二叉树。
-
代码:
class Solution { public boolean isBalanced(TreeNode root) { if(root == null) return true; int l = depth(root.left); int r = depth(root.right); //判断当前根节点的树是否为平衡二叉树 if(Math.abs(l - r) > 1) return false; else return (isBalanced(root.left) && isBalanced(root.right)); } //获取整棵树的高度 public int depth(TreeNode root){ if(root == null) return 0; return Math.max(depth(root.left),depth(root.right)) + 1; } }
543,二叉树的直径,easy
给定一棵二叉树,你需要计算它的直径长度。一棵二叉树的直径长度是任意两个结点路径长度中的最大值。这条路径可能穿过也可能不穿过根结点。
示例 :
给定二叉树
1
/ \
2 3
/ \
4 5
返回 3, 它的长度是路径 [4,2,1,3] 或者 [5,2,1,3]。
注意:两结点之间的路径长度是以它们之间边的数目表示。
-
方法:DFS递归。
-
思路:直径 = 任意两个结点路径长度中的最大值,可以看做树内的某一节点的左子树节点数l + 右子树节点数r - 1,所有节点的l + r - 1中的最大值即为直径。定义一个递归函数计算经过的左右子树的节点数l + r,函数返回给定节点为根的子树的深度。递归搜索每个节点并设一个全局变量 ans 记录 l + r 的最大值,最后返回 ans 即为树的直径。
-
代码:
class Solution { int ans = 0; public int diameterOfBinaryTree(TreeNode root) { depth(root); return ans; } //返回最大的 L+R public int depth(TreeNode root){ if(root == null) return 0; int l = depth(root.left); int r = depth(root.right); ans = Math.max(ans, l + r);//找到最大直径 return Math.max(l, r) + 1;//树的深度 } }
226,翻转二叉树,easy
翻转一棵二叉树。
示例:
输入:
4
/ \
2 7
/ \ / \
1 3 6 9
输出:
4
/ \
7 2
/ \ / \
9 6 3 1
-
方法一:递归。
-
思路:分别对左右子树都进行翻转,再交换。
-
代码:
class Solution { public TreeNode invertTree(TreeNode root) { //递归 if(root == null) return null; TreeNode leftTree = root.left;//保存原来的左子树 root.left = invertTree(root.right); root.right = invertTree(leftTree); return root; } }
-
方法二:借助栈(DFS)。
-
思路:先将根节点压入栈。栈非空时,弹出栈顶节点,如果弹出节点的左右子节点有非空,将其压入栈,并进行交换,重复此过程直到栈空。
-
代码:
class Solution { public TreeNode invertTree(TreeNode root) { if(root == null) return null; Stack<TreeNode> stack = new Stack<>(); stack.push(root); while(!stack.isEmpty()){ TreeNode node = stack.pop(); if(node.left != null){ stack.push(node.left); } if(node.right != null){ stack.push(node.right); } TreeNode temp = node.right; node.right = node.left; node.left = temp; } return root; } }
617,合并二叉树,easy
给定两个二叉树,想象当你将它们中的一个覆盖到另一个上时,两个二叉树的一些节点便会重叠。
你需要将他们合并为一个新的二叉树。合并的规则是如果两个节点重叠,那么将他们的值相加作为节点合并后的新值,否则不为 NULL 的节点将直接作为新二叉树的节点。
示例 1:
输入:
Tree 1 Tree 2
1 2
/ \ / \
3 2 1 3
/ \ \
5 4 7
输出:
合并后的树:
3
/ \
4 5
/ \ \
5 4 7
注意: 合并必须从两个树的根节点开始。
-
方法一:递归。(dfs)
-
思路:新建一棵树,如果原来两棵树的节点都存在,直接相加;如果有一个不存在,返回另一个节点。递归节点的左右子节点,作为新树的左右子树。
-
代码:
class Solution { public TreeNode mergeTrees(TreeNode t1, TreeNode t2) { if (t1 == null && t2 == null) return null; if(t1 == null || t2 == null){ return t1 == null ? t2 : t1; } TreeNode newTree = new TreeNode(t1.val + t2.val); newTree.left = mergeTrees(t1.left,t2.left); newTree.right = mergeTrees(t1.right,t2.right); return newTree; } }
112,路径总和,easy
给定一个二叉树和一个目标和,判断该树中是否存在根节点到叶子节点的路径,这条路径上所有节点值相加等于目标和。
说明: 叶子节点是指没有子节点的节点。
示例:
给定如下二叉树,以及目标和 sum = 22,
5
/ \
4 8
/ / \
11 13 4
/ \ \
7 2 1
返回 true, 因为存在目标和为 22 的根节点到叶子- 方法一:递归(dfs)
-
思路:以当前根节点为例,如果为null,返回false;如果为叶子节点,判断当前节点值是否与sum相等并返回;如果不是叶子节点,递归搜索左右子节点。
-
代码:
class Solution { public boolean hasPathSum(TreeNode root, int sum) { if(root == null) return false; if(root.left == null && root.right == null) return root.val == sum; return hasPathSum(root.left,sum - root.val) || hasPathSum(root.right, sum - root.val); } }
113,路径总和Ⅱ,midium
给定一个二叉树和一个目标和,找到所有从根节点到叶子节点路径总和等于给定目标和的路径。
说明: 叶子节点是指没有子节点的节点。
示例:
给定如下二叉树,以及目标和 sum = 22,
5
/ \
4 8
/ / \
11 13 4
/ \ / \
7 2 5 1
返回:
[
[5,4,11,2],
[5,8,4,5]
]
-
方法:递归回溯(dfs)。
-
思路:
- 回溯条件:
- 节点为空— 如果当前节点为空,说明节点没有孩子,循着这条路径,已经找不到符合条件的路径。
- 节点为叶子节点— 如果当前节点是叶子节点并且它的值满足题目要求,则它所在的路径就是满足要求的。
- 回溯条件:
-
代码:
class Solution { List<List<Integer>> res = new ArrayList<>(); public List<List<Integer>> pathSum(TreeNode root, int sum) { dfs(root, sum, res, new ArrayList<>()); return res; } public void dfs(TreeNode root, int sum, List<List<Integer>> res, List<Integer> list){ if(root == null) return; //把当前节点值加入到list中 list.add(root.val); //叶子节点且此节点值=sum if(root.left == null && root.right == null && root.val == sum) res.add(new ArrayList(list)); //还没到叶子节点,继续从左右节点向下找 dfs(root.left, sum - root.val, res, list); dfs(root.right, sum - root.val, res, list); //防止分支污染,遍历完当前节点的左子树、右子树,说明经过这个节点的路径已经被遍历完,因此要回溯到当前节点的父节点 list.remove(list.size() - 1); } }
572,另一个树的子树,easy
给定两个非空二叉树 s 和 t,检验 s 中是否包含和 t 具有相同结构和节点值的子树。s 的一个子树包括 s 的一个节点和这个节点的所有子孙。s 也可以看做它自身的一棵子树。
示例 1:
给定的树 s:
3
/ \
4 5
/ \
1 2
给定的树 t:
4
/ \
1 2
返回 true,因为 t 与 s 的一个子树拥有相同的结构和节点值。
示例 2:
给定的树 s:
3
/ \
4 5
/ \
1 2
给定的树 t:
4
/ \
1 2
返回 false。
-
方法:建立一个递归函数,判断两棵树是否相等。
-
代码:
class Solution { public boolean isSubtree(TreeNode s, TreeNode t) { //第二棵树为空,一定是子树 if(t == null) return true; //第一棵树为空,没有子树 if(s == null) return false; //递归比较t是否是s的左子树和右子树的一部分,或s与t是两棵相同的树 return isSubtree(s.left, t) || isSubtree(s.right, t) || isSameTree(s, t); } //判断两棵树是否相同 public boolean isSameTree(TreeNode s, TreeNode t){ if(s == null && t == null) return true; //如果其中有一个节点为空或两节点值不相等时,返回false if(s == null || t == null || s.val != t.val) return false; //递归比较左子树和右子树是否相同 return isSameTree(s.left, t.left) && isSameTree(s.right, t.right); } }
101,对称二叉树,easy
给定一个二叉树,检查它是否是镜像对称的。
例如,二叉树 [1,2,2,3,4,4,3] 是对称的。
1
/ \
2 2
/ \ / \
3 4 4 3
但是下面这个 [1,2,2,null,3,null,3] 则不是镜像对称的:
1
/ \
2 2
\ \
3 3
-
方法一:递归。
-
思路:建立一个递归函数,移动两个指针遍历这棵树,判断根节点的左右子树是否对称。在主函数调用此递归函数,参数为根的左子节点和右子节点。
-
代码:
class Solution { public boolean isSymmetric(TreeNode root) { if(root == null) return true; return check(root.left, root.right); } //用两个指针检查树是否对称 public boolean check(TreeNode p, TreeNode q){ if(p == null && q == null) return true; if(p == null || q == null || p.val != q.val) return false; return check(p.left, q.right) && check(p.right, q.left); } }
111,二叉树的最小深度,easy
给定一个二叉树,找出其最小深度。
最小深度是从根节点到最近叶子节点的最短路径上的节点数量。
说明:叶子节点是指没有子节点的节点。
示例 1:
输入:root = [3,9,20,null,null,15,7]
输出:2
示例 2:
输入:root = [2,null,3,null,4,null,5,null,6]
输出:5
-
方法一:递归。
- 结束条件:当root为空,返回0
- 递归体:若左右子树皆空,返回1;若左子树和右子树有非空的,记录其最小路径,最后返回结果为最小路径 ➕ 1
-
代码:
class Solution { public int minDepth(TreeNode root) { if(root == null) return 0; //1.当root的左右子树都为空 if(root.left == null && root.right == null) return 1; int mindep = Integer.MAX_VALUE; //2.root的左子树或右子树有不为空,计算其最小路径 if(root.left != null){ mindep = Math.min(minDepth(root.left), mindep); } if(root.right != null){ mindep = Math.min(minDepth(root.right), mindep); } //返回最小路径+1 return mindep + 1; } }
-
错误原因:想法是dfs 来递归左右子树找它们的最小路径,然后取最小。但这样可能在 [2,null,3,null,4,null,5,null,6] 结构中不成立,因为root的左子树为空,所以要加一个判断。
404,左叶子之和,easy
计算给定二叉树的所有左叶子之和。
示例:
3
/ \
9 20
/ \
15 7
在这个二叉树中,有两个左叶子,分别是 9 和 15,所以返回 24
-
方法一:递归dfs
-
思路:所有左叶子节点之和,需要遍历整棵树,采用dfs。
-
递归出口:节点为空或为叶子节点
-
递归条件:
-
如果左子树不为空,判断左子节点是否为叶子节点:若不是,递归调用左子节点
- 如果右子树为空,结果不变;若不为空,上面的结果+ 递归调用右子节点
-
否则左子树为空,右子树一定不为空,只需判断右子节点是不是叶子节点,若不是,递归调用右子节点
-
最后返回结果
-
-
-
代码:
class Solution { public int sumOfLeftLeaves(TreeNode root) { if(root == null || isLeaf(root)) return 0; return dfs(root); } public int dfs(TreeNode root){ if(root == null || isLeaf(root)) return 0; int res = Integer.MAX_VALUE; if(root.left != null){ res = isLeaf(root.left) ? root.left.val : dfs(root.left); if(root.right != null) res += isLeaf(root.right) ? 0 : dfs(root.right); } else{ res = isLeaf(root.right) ? 0 : dfs(root.right); } return res; } public boolean isLeaf(TreeNode node){ return node.left == null && node.right == null; } }
更简洁的版本:
public int sumOfLeftLeaves(TreeNode root) { if (root == null) return 0; if (isLeaf(root.left)) return root.left.val + sumOfLeftLeaves(root.right); return sumOfLeftLeaves(root.left) + sumOfLeftLeaves(root.right); } private boolean isLeaf(TreeNode node){ if (node == null) return false; return node.left == null && node.right == null; }
-
另一题:一棵树所有左子节点的值
class Solution { public int sumOfLeftLeaves(TreeNode root) { if(root == null) return 0; if(root.left == null && root.right == null) return 0; int res = Integer.MAX_VALUE; if(root.left != null && root.right == null){ res = sumOfLeftLeaves(root.left) + root.left.val; } if(root.left == null && root.right != null){ res = sumOfLeftLeaves(root.right); } if(root.left != null && root.right != null){ res = sumOfLeftLeaves(root.left) + root.left.val + sumOfLeftLeaves(root.right); } return res; } }
687,最长同值路径,medium
给定一个二叉树,找到最长的路径,这个路径中的每个节点具有相同值。 这条路径可以经过也可以不经过根节点。
注意:两个节点之间的路径长度由它们之间的边数表示。
示例 1:
输入:
5
/ \
4 5
/ \ \
1 1 5
输出:
2
示例 2:
输入:
1
/ \
4 5
/ \ \
4 4 5
输出:
2
-
方法一:递归。(dfs)
-
思路:
-
最长路径分为2种情况:
- 以root为起点,经过左子树或右子树,如(2)
- 不以root为起点,root为中间点,如(1)
-
辅助函数helper,计算以每一个节点为起点的最长同值路径maxLength,在过程中可以得到以root为根节点的树的最长同值路径ans。
-
代码:
class Solution { int ans; //以root为根节点的树的最长同值路径 public int longestUnivaluePath(TreeNode root) { if(root == null) return 0; helper(root); return ans; } //以root为起点的最长同值路径 public int helper(TreeNode root){ if(root == null) return 0; int maxLength = 0; //以左、右子节点为起点的最长同值路径 int leftLength = helper(root.left); int rightLength = helper(root.right); //如果左、右子树都非空 且 root.val == root.left.val == root.right.val,更新ans if(root.left != null && root.right != null && root.val == root.left.val && root.val == root.right.val){ ans = Math.max(ans, leftLength + rightLength + 2); } int leftPath = 0; int rightPath = 0; //如果左右子树有非空且子节点的值与root的值相等,以根节点为起点的最长同值路径为leftPath,rightPath中的最大值 if(root.left != null && root.left.val == root.val){ leftPath = leftLength + 1; } if(root.right != null && root.right.val == root.val){ rightPath = rightLength + 1; } //取左右子树的最长同值路径的最大值 maxLength = Math.max(leftPath,rightPath); //更新ans ans = Math.max(ans, maxLength); return maxLength; } }
简洁版:
private int path = 0; public int longestUnivaluePath(TreeNode root) { dfs(root); return path; } private int dfs(TreeNode root){ if (root == null) return 0; int left = dfs(root.left); int right = dfs(root.right); int leftPath = root.left != null && root.left.val == root.val ? left + 1 : 0; int rightPath = root.right != null && root.right.val == root.val ? right + 1 : 0; path = Math.max(path, leftPath + rightPath); return Math.max(leftPath, rightPath); }
671,二叉树中第二小的节点,easy
给定一个非空特殊的二叉树,每个节点都是正数,并且每个节点的子节点数量只能为 2 或 0。如果一个节点有两个子节点的话,那么该节点的值等于两个子节点中较小的一个。
更正式地说,root.val = min(root.left.val, root.right.val)
总成立。
给出这样的一个二叉树,你需要输出所有节点中的第二小的值。如果第二小的值不存在的话,输出 -1 。
示例 1:
输入:root = [2,2,5,null,null,5,7]
输出:5
解释:最小的值是 2 ,第二小的值是 5 。
示例 2:
输入:root = [2,2,2]
输出:-1
解释:最小的值是 2, 但是不存在第二小的值。
-
题意解析:每个树的根节点的值都为这棵树所有节点最小的值,所有节点中第二小的值即只比根节点大的值。
-
方法一:递归。
- 递归出口:节点为空,返回 -1。
- 递归体:
- 左右子节点都为空,返回 -1。
- 根节点取的是左子节点的值,递归左子节点得到只比这个值大的值(或 -1,即此节点为叶子节点)
- 根节点取的是右子节点的值,递归右子节点得到只比这个值大的值(或 -1,即此节点为叶子节点)
- 结果:
- left 、right 如果都不为 -1,取最小值并返回
- left 不为 -1(right 为 -1),返回left
- 否则(left为 -1),返回right(-1 或 root.right.val或递归结果)
-
代码:
class Solution { public int findSecondMinimumValue(TreeNode root) { if(root == null) return -1; if(root.left == null && root.right == null) return -1; int left = root.left.val; //如果左子节点是最小值,递归左子节点,得到以左子节点为根的树的第二小的值 或 -1 if(left == root.val){ left = findSecondMinimumValue(root.left);//-1 或 以root.left为根的树的第二小的值 } int right = root.right.val; //如果右子节点是最小值,递归右子节点,得到以右子节点为根的树的第二小的值 或 -1 if(right == root.val){ right = findSecondMinimumValue(root.right);//-1 或 以root.right为根的树的第二小的值 } //如果两边的树递归结果都不为-1(都正常),返回它们的最小值,即在root为根的树中只比root.val大 if(left != -1 && right != -1) return Math.min(left,right); //如果左子树正常,返回左子树 if(left != -1) return left; //否则返回右子树递归值(-1 或 以root.right为根的树的第二小的值) else return right; } }