LeetCode刷题(二):树的递归部分

树 – 递归部分

1.求树的高度 – :

  • 没什么难度,分别对左子树和右子树求高度,取两者的较大值。
  • +1是为了囊括结点本身的高度
public class MaxDepth_104 {
    public int maxDepth(TreeNode root) {
        if (root == null) return 0;
        // 分别递归求解左右子树的高度,注意最后需要加上结点本身的高度
        return Math.max(maxDepth(root.left), maxDepth(root.right)) + 1;
    }
}

2.判断一个树是否是平衡树

  • 当需要进行递归的值与结果值不同时,可以考虑通过一个成员变量作为结果

  • 建立一个额外的方法来实现递归,主方法用于返回结果

  • 在额外方法的某个判定条件中,会对成员变量的值进行更改

public class IsBalanced_110 {

    // 难点:没有考虑可以通过一个成员变量来作为结果
    private 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);
        // 存在不合理则将最后的结果置为false,否则继续完成递归
        if (Math.abs(l - r) > 1) result = false;
        return 1 + Math.max(l, r);
    }
}

3.两节点的最长路径

  • 递归的中间值与答案要求的不同,依然考虑用一个成员变量来替代
  • 不要求从根节点向下,那么路径应该是左右满足条件的路径之和
  • 这里长度是指边长,而L + R + 1是节点的个数,要求边的数量还需要-1
public class DiameterOfBinaryTree_543 {

    private int ans;

    public int diameterOfBinaryTree(TreeNode root) {
        depth(root);
        return ans;
    }

    private int depth(TreeNode root) {
        if (root == null) return 0;
        // 分别递归求解左右子树的深度
        int L = depth(root.left);
        int R = depth(root.right);
        // 核心如果是经过了根节点,其实求的就是根节点两条路径的深度之和
        // 加一是节点总数为L+R+1,-1是因为要求的是边数,所以减一
        ans = Math.max(L + R + 1 - 1, ans);
        // 还要加上结点本身
        return Math.max(L, R) + 1;
    }
}

4.翻转树

  • 如果要交换左右子树,通过一个哑结点作为副本,这样可以不改变原树的结构
  • 而左子树与右子树对调,只需要将以前的右半部分赋值给新的左半部分即可
public class InvertTree_226 {
    public TreeNode invertTree(TreeNode root) {
        if (root == null) return null;
        TreeNode dummy = new TreeNode(root.val);
        // 核心是通过递归方法,将以前的右子树,赋值给新的左子树
        dummy.left = invertTree(root.right);
        dummy.right = invertTree(root.left);
        return dummy;
    }
}

// 另一种解法, 思想差不多,就是对改变之前的树做一个备份
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;
}

5.归并两棵树

  • 递归合并的终止条件需要满足
  • 这里为了不改变原有树的结构,依然是通过创建一个新的树节点来实现

可以归纳一个结论 – 当不希望改变树的结构时,往往通过创建一个新的结点。然后修改并返回这个临时结点

public class MergeTrees_617 {
    public TreeNode mergeTrees(TreeNode t1, TreeNode t2) {
        // 合并的递归终止条件
        if (t1 == null && t2 == null) return null;
        if (t1 == null) return t2;
        if (t2 == null) return t1;
        // 当前结点的值是两个结点值相加
        TreeNode node = new TreeNode(t1.val + t2.val);
        // 树的合并是一一对应合并的
        node.left = mergeTrees(t1.left, t2.left);
        node.right = mergeTrees(t1.right, t2.right);
        return node;
    }
}

6.判断路径和是否等于一个数

  • 常规方法是按照之前的思路,要求的值和需要进行递归的方式不匹配,那么构造一个新的递归方法,并用成员变量作为结果
  • 注意根节点的判定方法 – 向下依次减少,当sum == 0时,发现刚好减到0,那么可以对result置为true。同时,问题是一个存在性问题,那么只需要有一个结点满足即可
  • 第二种解法带有一点离散数学的思想,详见注释。最后返回的递归结果是一个析取关系
public class HasPathSum {

    private boolean result = false;

    public boolean hasPathSum1(TreeNode root, int sum) {
        search(root, sum);
        return result;
    }

    // 常规方法,构建一个额外的递归
    public void search(TreeNode root, int sum) {
        if (root == null) return;
        sum -= root.val;
        if (sum == 0 && root.left == null && root.right == null)
            result = true;
        search(root.left, sum);
        search(root.right, sum);
    }

    // 简化版。通过离散数学关系分析,这是一个析取关系,只要有一个为true,则结果为true
    public boolean hasPathSum(TreeNode root, int sum){
        // 通过false终止递归
        if(root == null) return false;
        // 这里是与root val相等,而不是0,注意
        if(sum == root.val && root.left == null && root.right == null) 
            return true;
        // 结果为析取形式,只有一条路径满足长度需求,那么可以得到结果是正确的
        return hasPathSum(root.left, sum - root.val) || hasPathSum(root.right, sum - root.val);
    }

}

7.求路径和

  • 这里引入了一种新的方法 – 就是对根节点做特殊递归,然后对左右子树做一般递归,再将结果进行归并 (我命名为根节点特殊化)
  • 特殊化的原因是:除了根本身,其子树可能也会存在满足题目条件的情况,因此需要分开考虑
  • 其余注意事项见答案
public class PathSum_437 {

    public int pathSum(TreeNode root, int sum) {
        if (root == null) return 0;
        /**
         * 1.以根节点为root求存在的分支数
         * 2.分别将左子树和右子树求其满足的路径数,相加求总和
         */
        int ret = pathSumStartWithRoot(root, sum) + pathSum(root.left, sum) + pathSum(root.right, sum);
        return ret;
    }

    private int pathSumStartWithRoot(TreeNode root, int sum) {
        // root为空时,说明没有路径
        if (root == null) return 0;
        // 如果与sum相等,那么证明找到了一条,否则为0
        int ret = root.val == sum ? 1 : 0;
        // 分别记录左右子树作为根节点时向下遍历的路径条数
        // 关键点 --- 假定存在,那么向下遍历时则需要在此基础上减去对应值
        ret += pathSumStartWithRoot(root.left, sum - root.val) + pathSumStartWithRoot(root.right, sum - root.val);
        return ret;
    }

    public int pathSum1(TreeNode root, int sum) {
        return pathSum(root, sum, new int[1000], 0);
    }

    // 第二种解法!! 有待理解
    public int pathSum(TreeNode root, int sum, int[] array/*保存路径*/, int p/*指向路径终点*/) {
        if (root == null) {
            return 0;
        }
        int tmp = root.val;
        int n = root.val == sum ? 1 : 0;
        for (int i = p - 1; i >= 0; i--) {
            tmp += array[i];
            if (tmp == sum) {
                n++;
            }
        }
        array[p] = root.val;
        int n1 = pathSum(root.left, sum, array, p + 1);
        int n2 = pathSum(root.right, sum, array, p + 1);
        return n + n1 + n2;
    }

}

你可能感兴趣的:(LeetCode)