由于水平原因,博客大部分内容摘抄于网络,如有错误或者侵权请指出,本人将尽快修改

树形DP

题目描述

给定一棵二叉树,要求找到其中最大的二叉搜索树子树,并返回该子树的节点个数。

二叉搜索树的定义是:对于二叉树的每个节点,左子树的所有节点的值都小于该节点的值,而右子树的所有节点的值都大于该节点的值。

输入

一个二叉树的根节点。

输出

返回该二叉树中最大的二叉搜索树子树的节点个数。

示例

示例 1:

输入:
      10
     /  \
    5    15
   / \     \
  1   8    7

输出: 3
解释: 最大的二叉搜索树子树有 3 个节点,即子树 [5,1,8]。

示例 2:

输入:
    4
   / \
  2   6
 / \   \
1   3   7

输出: 6
解释: 整棵树本身就是一个二叉搜索树。

提示:

  • 树上节点数的范围是 [1, 10^4]
  • 节点值的范围是 [-10^4, 10^4]

解题思路

要解决这个问题,我们需要递归遍历每一个节点,判断以该节点为根的子树是否为二叉搜索树。如果是,计算这个子树的节点个数;如果不是,继续递归判断其左右子树。

  1. 后序遍历:对于每一个节点,首先递归处理其左子树和右子树,然后通过当前节点的值与左右子树的最大最小值来判断以当前节点为根的树是否满足二叉搜索树的性质。

  2. 维护信息:我们需要在每个递归节点返回四个关键信息:

    • 当前子树是否是一个二叉搜索树。
    • 当前子树的节点总数。
    • 当前子树的最小值(用于向上判断父节点是否满足二叉搜索树条件)。
    • 当前子树的最大值。

    通过这些信息,我们可以方便地向上层节点判断当前子树的合法性,并记录符合条件的最大二叉搜索树的节点个数。

  3. 动态规划:可以通过后序遍历的方式自底向上逐层处理树的节点,动态维护最大的子树节点数。

    public static class TreeNode {
        public int val;
        public TreeNode left;
        public TreeNode right;

        public TreeNode(int val) {
            this.val=val;
        }
    }


    public static int largestBSTSubtree(TreeNode root) {
        return f(root).maxBstSize;
    }

    public static class Info {
        public long max;
        public long min;
        public boolean isBst;
        public int maxBstSize;

        public Info(long a, long b, boolean c, int d) {
            max = a;
            min = b;
            isBst = c;
            maxBstSize = d;
        }
    }

    public static Info f(TreeNode x) {
        if (x == null) {
            return new Info(Long.MIN_VALUE, Long.MAX_VALUE, true, 0);
        }
        Info infol = f(x.left);
        Info infor = f(x.right);

        long max = Math.max(x.val, Math.max(infol.max, infor.max));
        long min = Math.min(x.val, Math.min(infol.min, infor.min));
        boolean isBst = infol.isBst && infor.isBst && infol.max < x.val && x.val < infor.min;
        int maxBSTSize;
        if (isBst) {
            maxBSTSize = infol.maxBstSize + infor.maxBstSize + 1;
        } else {
            maxBSTSize = Math.max(infol.maxBstSize, infor.maxBstSize);
        }
        return new Info(max, min, isBst, maxBSTSize);
    }
  1. 打家劫舍 III 是一道来自 LeetCode 的动态规划问题,其题目描述如下:

题目描述

小偷又发现了一个新的可行窃的地区。这个地区只有一个入口,我们称之为“根”。除了“根”之外,每栋房子有且只有一个“父”房子与之相连。经过侦察,小偷发现这个地方的所有房屋的排列类似于一棵二叉树。如果两个直接相连的房子在同一天晚上被打劫,房屋将自动报警。

题目要求

给定二叉树的根节点,计算在不触动警报的情况下,小偷一晚能够盗取的最高金额。

输入输出

  • 输入:二叉树的根节点,其中每个节点的值代表该房屋中的金额。
  • 输出:一个整数,表示小偷能够盗取的最高金额。

示例

  • 示例 1

    • 输入:root = [3,2,3,null,3,null,1]
      • 二叉树结构:
          3
         / \
        2   3
        \    \
         3   1
      
    • 输出:7
    • 解释:小偷一晚能够盗取的最高金额 = 3 + 3 + 1 = 7。
  • 示例 2

    • 输入:root = [3,4,5,1,3,null,1]
      • 二叉树结构:
          3
         / \
        4   5
       / \   \ 
      1   3   1
      
    • 输出:9
    • 解释:小偷一晚能够盗取的最高金额 = 4 + 5 = 9(注意不能同时偷取相连的房屋)。

解题思路

这个问题可以通过动态规划(DP)来解决。对于每个节点,有两种情况:

  1. 偷当前节点:如果偷当前节点,则不能偷其左右子节点,但可以偷其孙子节点(即左右子节点的子节点)。因此,当前节点的最大值为当前节点的值加上其左右子节点的“不偷”状态的最大值之和。
  2. 不偷当前节点:如果不偷当前节点,则可以偷其左右子节点(以及它们的子节点)。因此,当前节点的最大值为左右子节点的“偷”和“不偷”状态中的最大值之和。
    class Result{
        int rob;
        int noRob;

        public Result(int rob, int noRob) {
            this.rob=rob;
            this.noRob=noRob;
        }
    }
    public int rob(TreeNode root) {
        Result result = robSub(root);
        return Math.max(result.rob,result.noRob);
    }

    private Result robSub(TreeNode root) {
        if (root==null){
            return new Result(0,0);
        }
        Result l = robSub(root.left);
        Result r = robSub(root.right);
        int a = root.val+l.noRob+r.noRob;
        int b = Math.max(l.rob,l.noRob)+Math.max(r.rob,r.noRob);
        return new Result(a,b);
    }
posted @ 2024-10-16 00:04  小纸条  阅读(3)  评论(0编辑  收藏  举报