Skip to content

打家劫舍

LeetCode 官方题目链接

题目描述

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

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

示例 1:

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

示例 2:

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

思路拆解

这是一道经典的动态规划问题。

动态规划

  1. 定义状态dp[i] 表示前 i 间房屋能偷窃到的最高金额。
  2. 状态转移方程
    • 对于第 i 间房屋,我们有两种选择:
      • 不偷:那么最大金额就是前 i-1 间房屋的最大金额,即 dp[i-1]
      • :那么我们不能偷第 i-1 间房屋,所以最大金额是前 i-2 间房屋的最大金额加上当前房屋的金额,即 dp[i-2] + nums[i]
    • 我们取两者的最大值:dp[i] = Math.max(dp[i-1], dp[i-2] + nums[i])
  3. 初始条件
    • dp[0] = nums[0] (只有一间房,必偷)
    • dp[1] = Math.max(nums[0], nums[1]) (两间房,偷金额大的那一间)

空间优化

同样地,我们发现 dp[i] 只依赖于前两个状态,所以可以用两个变量滚动更新,将空间复杂度优化到 $O(1)$。

代码实现

javascript
/**
 * @param {number[]} nums
 * @return {number}
 */
var rob = function(nums) {
    if (nums.length === 0) return 0;
    if (nums.length === 1) return nums[0];
    
    // prev2 代表 dp[i-2]
    // prev1 代表 dp[i-1]
    let prev2 = nums[0];
    let prev1 = Math.max(nums[0], nums[1]);
    
    for (let i = 2; i < nums.length; i++) {
        const current = Math.max(prev1, prev2 + nums[i]);
        prev2 = prev1;
        prev1 = current;
    }
    
    return prev1;
};

运行演示

假设 nums = [2, 7, 9, 3, 1]

  1. 初始化:

    • prev2 = 2 (偷第0间)
    • prev1 = max(2, 7) = 7 (偷第1间)
  2. i = 2 (val = 9):

    • current = max(7, 2 + 9) = 11
    • prev2 = 7, prev1 = 11
  3. i = 3 (val = 3):

    • current = max(11, 7 + 3) = 11
    • prev2 = 11, prev1 = 11
  4. i = 4 (val = 1):

    • current = max(11, 11 + 1) = 12
    • prev2 = 11, prev1 = 12

返回 12

复杂度分析

  • 时间复杂度:$O(n)$,遍历一次数组。
  • 空间复杂度:$O(1)$,只使用了常数个变量。

Power by VitePress