打家劫舍系列問題

打家劫舍系列問題

作者:Grey

原文地址:打家劫舍系列問題

LeetCode 198. 打家劫舍

主要思路

定義和原始數組一樣長的dp數組,

int[] dp = new int[dp]

dp[i]的含義是:[0...i]區間內,得到最大的金額是多少。

顯然有

// [0...0]範圍內,最大金額就是`arr[0]`
dp[0] = arr[0];
// [0...1]範圍內,最大金額要不選`arr[0]`,要不選`arr[1]`,不能同時選,因爲會報警
dp[1] = Math.max(arr[0],arr[1]);

針對普遍位置dp[i],有兩個方案:

方案1,不選擇i位置,這樣的話dp[i] = dp[i-1],即:i位置的答案,只依賴i-1位置的答案。

方案2,選擇i位置,這樣的話dp[i] = arr[i] + dp[i - 2],即:i位置的答案,依賴i位置的值加上i-2位置的答案。

以上兩種方案取最大值,就是i當前位置的最大獲取金額數。

dp[i] = Math.max(dp[i - 1], arr[i] + dp[i - 2]);

完整代碼

    public static int rob(int[] arr) {
        if (arr == null || arr.length == 0) {
            return 0;
        }
        if (arr.length == 1) {
            return arr[0];
        }
        if (arr.length == 2) {
            return Math.max(arr[0], arr[1]);
        }
        final int n = arr.length;
        int[] dp = new int[n];
        dp[0] = arr[0];
        dp[1] = Math.max(arr[0], arr[1]);
        for (int i = 2; i < n; i++) {
            dp[i] = Math.max(dp[i - 1], arr[i] + dp[i - 2]);
        }
        return dp[n - 1];
    }

時間複雜度O(N),空間複雜度O(N),根據如上代碼,可以看到,整個dp數組是有遞推關係的,所以,可以進行壓縮數組優化,僅需要幾個變量就可以實現上述過程,優化後的代碼如下

    public static int rob2(int[] arr) {
        if (arr == null || arr.length == 0) {
            return 0;
        }
        if (arr.length == 1) {
            return arr[0];
        }
        if (arr.length == 2) {
            return Math.max(arr[0], arr[1]);
        }
        final int n = arr.length;
        int prePre = arr[0];
        int pre = Math.max(arr[0], arr[1]);
        int max = pre;
        for (int i = 2; i < n; i++) {
            int cur = Math.max(pre, prePre + arr[i]);
            prePre = pre;
            pre = cur;
            max = Math.max(cur, max);
        }
        return max;
    }

以上優化後的算法,時間複雜度O(N),空間複雜度O(1)

LeetCode 213. 打家劫舍 II

主要思路

之前的問題不是環形數組,本題是環形數組,所以,最後一個位置和第一個位置有強關聯關係,所以我們分兩部分來解

第一部分,不包括最後一個位置。

第二部分,一定要包括最後一個位置。

上述兩個部分的最大值,就是整體的要求的答案。

第一部分,不包括最後一個位置,那麼整個區間就是下標從[0....n-2],調用上一個非環形數組問題的解法。

        final int n = arr.length;
        // 以下情況是考慮最後一個位置
        int prePre = arr[1];
        int pre = Math.max(arr[1], arr[2]);
        int max = pre;
        for (int i = 3; i < n; i++) {
            int cur = Math.max(pre, prePre + arr[i]);
            prePre = pre;
            pre = cur;
            max = Math.max(cur, max);
        }

第二部分,包括最後一個位置,那麼整個區間就是下標從[1...n-1],調用上一個非環形數組問題的解法。

        // 以下情況是不考慮最後一個位置
        prePre = arr[0];
        pre = Math.max(arr[0], arr[1]);
        for (int i = 2; i < n - 1; i++) {
            int cur = Math.max(pre, prePre + arr[i]);
            prePre = pre;
            pre = cur;
            max = Math.max(cur, max);
        }

最後max即爲環形數組下的答案。完整代碼見

    public static int rob(int[] arr) {
        if (arr == null || arr.length == 0) {
            return 0;
        }
        if (arr.length == 1) {
            return arr[0];
        }
        if (arr.length == 2) {
            return Math.max(arr[0], arr[1]);
        }
        if (arr.length == 3) {
            return Math.max(Math.max(arr[0], arr[1]), arr[2]);
        }
        final int n = arr.length;
        // 以下情況是考慮最後一個位置
        int prePre = arr[1];
        int pre = Math.max(arr[1], arr[2]);
        int max = pre;
        for (int i = 3; i < n; i++) {
            int cur = Math.max(pre, prePre + arr[i]);
            prePre = pre;
            pre = cur;
            max = Math.max(cur, max);
        }
        // 以下情況是不考慮最後一個位置
        prePre = arr[0];
        pre = Math.max(arr[0], arr[1]);
        for (int i = 2; i < n - 1; i++) {
            int cur = Math.max(pre, prePre + arr[i]);
            prePre = pre;
            pre = cur;
            max = Math.max(cur, max);
        }
        return max;
    }

LeetCode 337. 打家劫舍 III

主要思路

由於變成了樹狀,所以,對於任意子樹,只需要考慮兩種情況即可:

情況1,包含子樹頭節點的情況下,最大收益是多少。

情況2,不包含子樹頭節點的情況下,最大收益是多少。

定義數據結構

    public class Info {
        // 選頭節點
        public int yes;
        // 不選頭節點
        public int no;

        public Info(int y, int n) {
            yes = y;
            no = n;
        }
    }

同時定義遞歸函數

public Info p(TreeNode root) {
    // TODO
}

這個遞歸函數的含義是:以root爲頭節點的樹的最大收益是多少。可能性有如下幾種

可能性1,最大值包含了root節點,那麼對於root的左子樹left和右子樹right,需要獲取到不包含其左右子樹頭節點的情況下,最大收益是多少。

 int yes = root.val + left.no + right.no;

可能性2,最大值不包含root節點,那麼對於root的最有子樹,只要給出左右子樹獲取到的最大收益就可以了,不需要考慮左右子樹頭節點是否包含進來的問題。

int no = Math.max(left.yes, left.no) + Math.max(right.yes, right.no);

最後Math.max(yes,no)就是以root爲頭的數得到的最大收益是多少。

完整代碼如下

    public int rob(TreeNode root) {
        Info info = p(root);
        return Math.max(info.yes, info.no);
    }

    public class Info {
        // 選頭節點
        public int yes;
        // 不選頭節點
        public int no;

        public Info(int y, int n) {
            yes = y;
            no = n;
        }
    }

    public Info p(TreeNode root) {
        if (root == null) {
            return new Info(0, 0);
        }
        Info left = p(root.left);
        Info right = p(root.right);
        int yes = root.val + left.no + right.no;
        int no = Math.max(left.yes, left.no) + Math.max(right.yes, right.no);
        return new Info(yes, no);
    }

更多

算法和數據結構筆記

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章