樹形DP

小纸条發表於2024-10-16

題目描述

給定一棵二叉樹,要求找到其中最大的二叉搜尋樹子樹,並返回該子樹的節點個數。

二叉搜尋樹的定義是:對於二叉樹的每個節點,左子樹的所有節點的值都小於該節點的值,而右子樹的所有節點的值都大於該節點的值。

輸入

一個二叉樹的根節點。

輸出

返回該二叉樹中最大的二叉搜尋樹子樹的節點個數。

示例

示例 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);
    }

相關文章