LeetCode - 198. 打家劫舍

195 阅读2分钟

小知识,大挑战!本文正在参与“程序员必备小知识”创作活动。


原题:198. 打家劫舍
你是一个专业的小偷,计划偷窃沿街的房屋。每间房内都藏有一定的现金,影响你偷窃的唯一制约因素就是相邻的房屋装有相互连通的防盗系统,如果两间相邻的房屋在同一晚上被小偷闯入,系统会自动报警。
给定一个代表每个房屋存放金额的非负整数数组,计算你 不触动警报装置的情况下 ,一夜之内能够偷窃到的最高金额。

解题思路:

这是一个典型的动态规划题目,从题目要求中可以看出,不能有任意两个相邻的房屋被偷,因此,投了一个房屋后,与之相邻的房屋就不能被偷。

如果只有一个房屋,那这个房屋内的现金就是可以偷的最大值;如果有两个房屋,那么只能偷一个,两个之中现金较多的一个就是最大值。

当房屋多于两个的时候,假设有 n 个房屋,对于第 n 个房屋,有两种情况:

  1. 偷窃第 n 个房屋,那么第 n-1 个就不能偷窃,最大值就是第 n 个房屋的现金和前 n-2 个房屋可偷窃现金的最大值相加。
  2. 不偷窃第 n 个房屋,最大值就是前 n-1 个房屋课偷窃现金的最大值。

以上两个取较大的一个,就是结果。

我们可以创建一个 dp[n] 数组,其中 dp[i] 代表前 i 个房屋的最优结果,那么:

  • dp[0] = nums[0]
  • dp[1] = max(nums[0], nums[1])
  • dp[i] = max(dp[i-1], dp[i-2]+nums[i]

代码如下:

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

以上代码中,用一个数组保存了动态规划运算过程中的结果,但其实不难发现,当计算到第 i 个元素的时候,我们只需要考虑第 i-1 和第 i-2 个结果,因此,可以降低空间复杂度,只保存两个值。并且,前 i-1 的最优解和前 i-2 的最优解,就代表了偷 i-1 的情况和不偷 i-1 的情况下的最优解,因此我们用变量 y 和 n 来代表偷和不偷的情况,每次更新这两个值,最后取两者之间较大的值即可。

最终代码:

class Solution {
    public int rob(int[] nums) {
        int y = 0, n = 0;
        for (int num : nums) {
            int temp = n + num;
            n = Math.max(y, n);
            y = temp;
        }
        return Math.max(y, n);
    }
}