打家劫舍

题目

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

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

示例 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 。

提示:

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

来源:力扣(LeetCode)
链接:https://leetcode-cn.com/problems/house-robber
著作权归领扣网络所有。商业转载请联系官方授权,非商业转载请注明出处。

解题思路

1. 动态规划(Dynamic Programming,DP)

该问题可归结为动态规划的简单入门问题——House Robber(小偷问题)。

动态规划的典型特征是每阶段的状态随决策改变,

解题步骤

  • 划分子问题
    缩小原问题的规模。关键点是不能选择相邻的两个房间,最小的规模是有 3 个房屋,

  • 子问题间的递推关系

    • k = 0 返回 0;

    • k = 1 返回该房屋金额;

    • k = 2 二选一的情况,返回较大金额;

    • k = 3 此时考虑,若选择偷窃第 3 间,偷窃总金额为第 3 间和第 1 间总金额;选择偷窃第 2 间,偷窃总金额为第 2 间金额;

    • k = 4 此时考虑,若选择偷窃第 4 间,偷窃总金额为第 4 间房屋金额和前 2 间最高金额之和;选择偷窃第 3 间,偷窃总金额为前 3 间房屋最高金额(k = 3 的情况);

    综上,偷窃前 k 间房屋的最高金额,要么是第 k 间房屋金额和前 k - 2 间房屋最高金额之和,要么是前 k - 1 间房屋最高金额(k > 2),选择的条件是取两种情况中的金额较大值。

    注:暴力分析情况太多,且每种情况方法类似但没有规律可循。不考虑 k = 0, 1, 2 的情况,其他情况不是简单的奇数项偶数项组合,如 nums [2,1,3,5] 是偷窃第 1 间和第 4 间最优,每种情况可以用排列组合去掉相邻的再作比较,但是实现起来较为繁琐。

  • 计算
    dp 数组也称“子问题数组”,dp 数组中的每一个元素都对应一个子问题的解,也就是 dp[k] 即 k 间房屋偷窃最大金额的情况。

通过上面的分析,前 k 间房屋最大金额是由前 k - 1间房屋最大金额和与前 k - 2 间房屋最大金额之和来决定的,依赖于前面偷窃的情况,将得到的递归结果存储在数组中,方便下一次更新调用结果。

注: 子问题的性质

  • 原问题可以用子问题表示
    子问题有参数 k ,当 k = n 时也就是原问题。
  • 最优子结构
    问题的最优解包含子问题的最优解,能够通过子问题的最优解推导出问题的最优解,从模型角度来看,也就是后面阶段的状态可以由前面状态推导出来。

2. 滚动数组

在动态数组基础上,我们知道前 k 间房屋最大金额是由前 k - 1间房屋最大金额和与前 k - 2 间房屋最大金额相关,与前 k - 3间房屋无关,实际上只需两个变量即可实现数据所需存储。

提交代码

动态数组

class Solution:
    def rob(self, nums: List[int]) -> int:
        if not nums:return 0
        if len(nums) == 1:return nums[0]

        l = len(nums)
        dp = [0] * l
        dp[0] = nums[0]
        dp[1] = max(nums[0],nums[1])
        for i in range(2,l):
            dp[i] = max(nums[i] + dp[i - 2],dp[i - 1])
        return dp[l - 1]
  • 时间复杂度 O(N)
    经过一次 dp 数组遍历。
  • 空间复杂度 O(N)
    将递归结果存储在 dp 数组中,数组长度同 nums。

滚动数组

class Solution:
    def rob(self, nums: List[int]) -> int:
        if not nums:return 0
        if len(nums) == 1:return nums[0]

        first = nums[0]
        second = max(nums[0],nums[1])
        for i in range(2,len(nums)):
            first,second = second,max(nums[i] + first,second)
        return second
  • 时间复杂度 O(N)
    经过一次 dp 数组遍历。
  • 空间复杂度 O(1)
    将递归结果存储在 dp 数组中,数组长度同 nums。

学习总结

  1. 动态规划 dp 数组的解决计算方法,定义子问题,写出递推关系,找到计算子问题的方法,原问题就按着从左至右的顺序得到解决;
  2. 对新问题通常已有发展较成熟的解决思路,较为典型,通过进一步的学习并结合题目练习,对打开解题思路很有帮助;
  3. 在已有方法上尽可能做优化和延伸,此题中滚动数组的方法是对动态数组方法在空间上的进一步优化。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值