该练习为Leetcode经典题目,以简单为主,加了理解的注释
代码标准参考 http://www.cyc2018.xyz/
1. 树的高度 104
public int maxDepth(TreeNode root){
// 递归root左右子树, 返回最大值+1
if(root == null) return 0;
return Math.max(maxDepth(root.left), maxDepth(root.right)) + 1;
}
2. 平衡树 Balanced Binary Tree 110
// 左右子树高度差小于等于1,返回boolean
private boolean result = true;
public boolean isBalanced(TreeNode root) {
maxDepth(root);
return result;
}
// 递归遍历左右子树,在后序遍历位置 判断高度差是否小于1
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);
}
3. 两个节点的最长路径 Diameter of Binary Tree 543
private int max = 0
public int diameterOfBinaryTree(TreeNode root){
depth(root);
return max;
}
// 递归遍历左右子树,在后序遍历位置维护全局变量
private int depth(TreeNode root){
if(root == null) return 0;
int leftDepth = depth(root.left);
int rightDepth = depth(root.right);
max = Math.max(max, leftDpeth + rightDepth);
return Math.max(leftDepth, rightDepth) + 1;
}
4. 翻转树 226 Invert Binary Tree
//解法1
public TreeNode invertTree(TreeNode root) {
if (root == null) return null;
TreeNode left = root.left; // 后面的操作会改变 left 指针,因此先保存下来
root.left = invertTree(root.right);
root.right = invertTree(left);
return root;
}
// 解法2
public TreeNode invertTree(TreeNode root){
if(root == null) return null;
// 按照定义反转二叉树
TreeNode left = invertTree(root.left);
TreeNode right = invertTree(root.right);
// 按照定义指向翻转后的树
root.left = right;
root.right = left;
return root;
}
5. 归并两棵树 617 Merge Two Binary Tree
public TreeNode mergeTrees(TreeNode t1, TreeNode t2){
//base case 空树,单棵树
if(t1 == null && t2 == null) return null;
if(t1 == null) return t2;
if(t2 == null) return t1;
// 前序遍历位置,先操作根节点,再操作左右子树
TreeNode root = new TreeNode(t1.val + t2.val);
root.left = mergeTrees(t1.left, t2.left);
root.right = mergeTrees(t1.right, t2.right);
return root;
}
6. 判断路径和是否等于一个数 112 Pathsum
// 路径和定义为 从 root 到 leaf所有节点的和
public boolean hasPathSum(TreeNode root, int sum){
// base case: 空树 和 根节点满足
if(root == null) return false;
if(root.left == null && root.right == null && root.val == sum) return true;
// 遍历左右子树
return hasPathSum(root.left, sum - root.val) || hasPathSum(root.right, sum - root.val);
}
7. 统计路径和等于一个数的路径数量 437 Path Sum
// 路径不一定以 root 开头,也不一定以 leaf 结尾,但是必须连续。
public int pathSum(TreeNode root, int sum){
if(root == null) return 0;
// 先从根节点开始,再遍历左右子树
int result = pathSumStartWithRoot(root, sum) + pathSum(root.left, sum) + pathSum(root.right ,sum);
return result;
}
private int pathSumStartWithRoot(TreeNode root, int sum){
if(root == null) return 0;
int ret = 0;
if(root.val == sum) ret++;
// 遍历左右子树
ret += pathSumStartWith(root.left, sum - root.val) + pathSumStartWith(root.right, sum - root.val);
return ret;
}
// 解法2
HashMap<Integer, Integer> preSumCount = new HashMap<>();
int pathSum, targetSum;
int res = 0;
public int pathSum(TreeNode root, int targetSum) {
if (root == null) {
return 0;
}
this.pathSum = 0;
this.targetSum = targetSum;
this.preSumCount.put(0, 1);
traverse(root);
return res;
}
void traverse(TreeNode root) {
if (root == null) {
return;
}
// 前序遍历位置
pathSum += root.val;
// 从二叉树的根节点开始,路径和为 pathSum - targetSum 的路径条数
// 就是路径和为 targetSum 的路径条数
res += preSumCount.getOrDefault(pathSum - targetSum, 0);
// 记录从二叉树的根节点开始,路径和为 pathSum 的路径条数
preSumCount.put(pathSum, preSumCount.getOrDefault(pathSum, 0) + 1);
traverse(root.left);
traverse(root.right);
// 后序遍历位置
preSumCount.put(pathSum, preSumCount.get(pathSum) - 1);
pathSum -= root.val;
}
8. 子树 Subtree of Anoter Tree 572
// 给两棵树,问第二棵是否是第一颗的子树
public boolean isSubtree(TreeNode s, TreeNode t){
if(s == null) return false;
return isSubtreeWithRoot(s, t) || isSubtree(s.left, t) || isSubtree(s.right, t);
}
private boolean isSubtreeWithRoot(TreeNode s, TreeNode t){
if( t == null && s == null) return true;
if( t == null || s == null) return false;
return isSubtreeWithRoot(s.left, t.left) && isSubtreeWithRoot(s.right, t.right);
}
9. 树的对称 Symmetric Tree 101
public boolean isSymmetric(TreeNode root){
if(root == null) return true;
return isSymmetric(root.left ,root.right);
}
private boolean isSymmetrc(TreeNode t1, TreeNode t2){
if(t1 == null && t2 == null ) return true;
if(t1 == null || t2 == null) return false;
// 左子树的左节点和右子树的右节点比较;右节点和左节点比较
return isSymmetric(t1.left, t2.right) && isSymmetric(t1.right, t2.left);
}
10. 最小路径 Minimum Depth of Binary Tree 111
// 树的根节点到叶子节点的最小路径长度
// 先找左右子树的高度,比较最小值,+1
public int minDepth(TreeNode root){
if(root == null) return 0;
int left = minDepth(root.left);
int right = minDepth(root.right);
if(left == 0 || right == 0) return left + right + 1;
return Math.min(left, right )+ 1;
}
11. 统计左叶子节点的和 404 sum of left leaves
public int sumOfLeftLeaves(TreeNode root){
if(root == null) return 0;
// 如果根节点的左节点是叶子节点直接返回left + 遍历右子树
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;
}
// 解法2,常规递归方法
class Solution {
public int sumOfLeftLeaves(TreeNode root) {
traverse(root);
return sum;
}
// 记录左叶子之和
int sum = 0;
// 二叉树遍历函数
void traverse(TreeNode root) {
if (root == null) {
return;
}
if (root.left != null &&
root.left.left == null && root.left.right == null) {
// 找到左侧的叶子节点,记录累加值
sum += root.left.val;
}
// 递归框架
traverse(root.left);
traverse(root.right);
}
}
12. 相同节点值的最大路径长度 Longest Univalue Path 687
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);
// 实现函数定义,等于左右子树最长数值长度的最大值加上root节点本身
return Math.max(leftPath, rightPath);
}
// 解法2
class Solution {
int res = 0;
public int longestUnivaluePath(TreeNode root) {
if (root == null) {
return 0;
}
// 在后序遍历的位置更新 res
maxLen(root, root.val);
return res;
}
// 定义:计算以 root 为根的这棵二叉树中,从 root 开始值为 parentVal 的最长树枝长度
private int maxLen(TreeNode root, int parentVal) {
if (root == null) {
return 0;
}
// 利用函数定义,计算左右子树值为 root.val 的最长树枝长度
int leftLen = maxLen(root.left, root.val);
int rightLen = maxLen(root.right, root.val);
// 后序遍历位置顺便更新全局变量
// 同值路径就是左右同值树枝长度之和
res = Math.max(res, leftLen + rightLen);
// 如果 root 本身和上级值不同,那么整棵子树都不可能有同值树枝
if (root.val != parentVal) {
return 0;
}
// 实现函数的定义:
// 以 root 为根的二叉树从 root 开始值为 parentVal 的最长树枝长度
// 等于左右子树的最长树枝长度的最大值加上 root 节点本身
return 1 + Math.max(leftLen, rightLen);
}
}
13. 间隔遍历 House Robber 337 打家劫舍,一维数组动态规划
Map<TreeNode, Integer> cache = new HashMap<>();
public int rob(TreeNode root){
if(root == null) return 0;
// 动态规划,利用备忘录消除重叠子问题
if(cache.containsKey(root)) return cache.get(root);
// 抢劫,间隔一个
int val1 = root.val;
if(root.left != null) val1 += rob(root.left.left) + rob(root.left.right);
if(root.right != null) val1 += rob(root.right.left) + rob(root.right.right);
// 没抢的
int val2 = rob(root.left) + rob(root.right);
// 然后比较已抢劫和没抢的哪个钱多
int res = Math.max(val1, val2);
cache.put(root, res);
return res;
}
14. 找出二叉树中第二小的节点 Second Minimum Node In a Binary Tree 671
// 一个节点要么具有0个或者2个子节点。如果有子节点,那么根节点是最小节点
public int findSecondMinimumValue(TreeNode root){
if(root == null) return -1;
if(root.left == null && root.right == null) return -1;
// base case
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;
}