DP算法问题写这些题就够了 198. 打家劫舍II 【第二题】

213. 打家劫舍 II

题目描述

你是一个专业的小偷,计划偷窃沿街的房屋。每间房内都藏有一定的现金。这个地方所有的房屋都围成一圈,这意味着第一个房屋和最后一个房屋是紧挨着的。同时,相邻的房屋装有相互连通的防盗系统,如果两间相邻的房屋在同一晚上被小偷闯入,系统会自动报警。

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

示例

示例 1:

输入

nums = [2, 3, 2]

输出

3

解释
你不能先偷窃 1 号房屋(金额 = 2),然后偷窃 3 号房屋(金额 = 2),因为它们是相邻的。

示例 2:

输入

nums = [1, 2, 3, 1]

输出

4

解释
你可以先偷窃 1 号房屋(金额 = 1),然后偷窃 3 号房屋(金额 = 3)。
偷窃到的最高金额 = 1 + 3 = 4。

示例 3:

输入

nums = [1, 2, 3]

输出

3

解题思路

这是一个打家劫舍问题的变种,由于房屋围成一个圆形,导致第一个房屋和最后一个房屋相邻。因此,我们需要考虑两种情况:

  1. 偷第一个房屋的情况:这时,不能偷最后一个房屋,因此问题转换为从第一个房屋到倒数第二个房屋的最大偷窃金额。
  2. 不偷第一个房屋的情况:这时,问题转换为从第二个房屋到最后一个房屋的最大偷窃金额。

对于每种情况,我们可以使用动态规划来解决。具体的,假设 dp[i][0] 表示偷第 i 个房屋时的最大金额,dp[i][1] 表示不偷第 i 个房屋时的最大金额。我们使用状态转移方程来计算最大值。

最终结果为这两种情况的最大值。

代码实现

from typing import List

class Solution:
    def rob(self, nums: List[int]) -> int:
        n = len(nums)
        if n == 0:  # 如果没有房屋,返回 0
            return 0
        if n == 1:  # 如果只有一个房屋,返回该房屋金额
            return nums[0]
        
        # 分别处理两种情况
        # 情况 1: 偷第一个房屋,不偷最后一个房屋
        a = nums[0:n-1]
        # 情况 2: 不偷第一个房屋,偷最后一个房屋
        b = nums[1:n]
        
        # 对情况 1 进行动态规划
        dp1 = [[0, 0] for _ in range(n)]
        for i in range(1, n):
            dp1[i][0] = dp1[i-1][1] + a[i-1]
            dp1[i][1] = max(dp1[i-1][0], dp1[i-1][1])
        
        # 对情况 2 进行动态规划
        dp2 = [[0, 0] for _ in range(n)]
        for i in range(1, n):
            dp2[i][0] = dp2[i-1][1] + b[i-1]
            dp2[i][1] = max(dp2[i-1][0], dp2[i-1][1])
        
        # 返回两种情况的最大值
        return max(dp1[n-1][0], dp1[n-1][1], dp2[n-1][0], dp2[n-1][1])

复杂度分析

  • 时间复杂度O(n),其中 n 是房屋的数量。我们分别对两种情况进行动态规划,每次遍历一次 nums 数组,因此时间复杂度是线性的。
  • 空间复杂度O(n),我们使用两个 dp 数组来存储每种情况下的结果,因此空间复杂度是 O(n)

优化

如果我们想要优化空间复杂度,考虑到每次计算 dp[i] 时只需要用到 dp[i-1]dp[i-2],可以将 dp 数组压缩为常数空间,利用两个变量来存储前两次的状态。

优化后的代码实现

class Solution:
    def rob(self, nums: List[int]) -> int:
        n = len(nums)
        if n == 0:
            return 0
        if n == 1:
            return nums[0]
        
        # 情况 1: 偷第一个房屋,不偷最后一个房屋
        a = nums[0:n-1]
        # 情况 2: 不偷第一个房屋,偷最后一个房屋
        b = nums[1:n]
        
        # 对情况 1 进行动态规划
        prev2, prev1 = 0, 0
        for num in a:
            curr = max(prev1, prev2 + num)
            prev2 = prev1
            prev1 = curr
        
        max1 = prev1
        
        # 对情况 2 进行动态规划
        prev2, prev1 = 0, 0
        for num in b:
            curr = max(prev1, prev2 + num)
            prev2 = prev1
            prev1 = curr
        
        max2 = prev1
        
        # 返回两种情况的最大值
        return max(max1, max2)

测试用例

测试用例 1:
nums = [2, 3, 2]
solution = Solution()
print(solution.rob(nums))  # 输出:3
测试用例 2:
nums = [1, 2, 3, 1]
solution = Solution()
print(solution.rob(nums))  # 输出:4
测试用例 3:
nums = [1, 2, 3]
solution = Solution()
print(solution.rob(nums))  # 输出:3

总结

在本问题中,由于房屋是围成一圈的,因此我们需要将问题转化为两种情况来考虑:一种是偷第一个房屋,一种是偷最后一个房屋。通过动态规划来求解每种情况的最大偷窃金额,最终返回两种情况的最大值。

通过优化空间复杂度,我们能够进一步提高代码的效率。

你可能感兴趣的:(DP算法入门刷题题单题解,算法)