打家劫舍系列问题

打家劫舍系列问题

作者: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);
    }

更多

算法和数据结构笔记

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