【算法】动态规划 斐波那契类型: 198. 打家劫舍

198. 打家劫舍

中等

你是一个专业的小偷,计划偷窃沿街的房屋。每间房内都藏有一定的现金,影响你偷窃的唯一制约因素就是相邻的房屋装有相互连通的防盗系统,如果两间相邻的房屋在同一晚上被小偷闯入,系统会自动报警。

给定一个代表每个房屋存放金额的非负整数数组,计算你 不触动警报装置的情况下 ,一夜之内能够偷窃到的最高金额。

示例 1:

输入:[1,2,3,1]
输出:4
解释:偷窃 1 号房屋 (金额 = 1) ,然后偷窃 3 号房屋 (金额 = 3)。
偷窃到的最高金额 = 1 + 3 = 4 。
示例 2:

输入:[2,7,9,3,1]
输出:12
解释:偷窃 1 号房屋 (金额 = 2), 偷窃 3 号房屋 (金额 = 9),接着偷窃 5 号房屋 (金额 = 1)。
偷窃到的最高金额 = 2 + 9 + 1 = 12 。

提示:

1 <= nums.length <= 100
0 <= nums[i] <= 400

分析

我们用动态规划来解决「打家劫舍」问题。


思路

  • dp[i] 为在考虑前 i+1 间房屋(下标 0…i)时,能偷到的最高金额。

  • 对于第 i 间房屋,有两种选择:

    1. 不偷i 间,此时最高金额等于 dp[i-1]
    2. i 间,则上一次只能偷到 i-2,所以累计为 dp[i-2] + nums[i]
  • 转移方程:

    dp[i] = max(dp[i-1], dp[i-2] + nums[i])
    
  • 边界条件:

    dp[0] = nums[0]
    dp[1] = max(nums[0], nums[1])
    

最终答案即为 dp[n-1]


方法一:O(n) 时间 + O(n) 空间

// C++ 实现
class Solution {
public:
    int rob(vector<int>& nums) {
        int n = nums.size();
        if (n == 0) return 0;
        if (n == 1) return nums[0];
        vector<int> dp(n, 0);
        dp[0] = nums[0];
        dp[1] = max(nums[0], nums[1]);
        for (int i = 2; i < n; ++i) {
            dp[i] = max(dp[i-1], dp[i-2] + nums[i]);
        }
        return dp[n-1];
    }
};
# Python 实现
class Solution:
    def rob(self, nums: List[int]) -> int:
        n = len(nums)
        if n == 0:
            return 0
        if n == 1:
            return nums[0]
        dp = [0] * n
        dp[0] = nums[0]
        dp[1] = max(nums[0], nums[1])
        for i in range(2, n):
            dp[i] = max(dp[i-1], dp[i-2] + nums[i])
        return dp[-1]
  • 时间复杂度:O(n)
  • 空间复杂度:O(n)

方法二:O(n) 时间 + O(1) 空间(滚动变量)

因为 dp[i] 只依赖 dp[i-1]dp[i-2],我们可以只用两个变量 prev1prev2 滚动维护:

// C++ 实现(O(1) 空间)
class Solution {
public:
    int rob(vector<int>& nums) {
        int n = nums.size();
        if (n == 0) return 0;
        if (n == 1) return nums[0];
        int prev2 = nums[0];                        // dp[i-2]
        int prev1 = max(nums[0], nums[1]);          // dp[i-1]
        int cur = prev1;
        for (int i = 2; i < n; ++i) {
            cur = max(prev1, prev2 + nums[i]);
            prev2 = prev1;
            prev1 = cur;
        }
        return cur;
    }
};
# Python 实现(O(1) 空间)
class Solution:
    def rob(self, nums: List[int]) -> int:
        n = len(nums)
        if n == 0:
            return 0
        if n == 1:
            return nums[0]
        prev2, prev1 = nums[0], max(nums[0], nums[1])
        cur = prev1
        for i in range(2, n):
            cur = max(prev1, prev2 + nums[i])
            prev2, prev1 = prev1, cur
        return cur
  • 时间复杂度:O(n)
  • 空间复杂度:O(1)

示例跑一遍

  • 对于 nums = [1,2,3,1]

    dp[0] = 1
    dp[1] = max(1,2) = 2
    dp[2] = max(dp[1], dp[0]+3) = max(2,4) = 4
    dp[3] = max(dp[2], dp[1]+1) = max(4,3) = 4
    

    返回 4。

  • 对于 nums = [2,7,9,3,1]

    dp[0] = 2
    dp[1] = max(2,7) = 7
    dp[2] = max(7,2+9) = 11
    dp[3] = max(11,7+3) = 11
    dp[4] = max(11,11+1) = 12
    

    返回 12。

两种方法都能在线性时间内、高效地得到答案,推荐在对空间敏感时使用滚动变量版本。
【算法】动态规划 斐波那契类型: 198. 打家劫舍_第1张图片
【算法】动态规划 斐波那契类型: 198. 打家劫舍_第2张图片

你可能感兴趣的:(算法/数据结构,算法,leetcode,动态规划)