198. 打家劫舍(中等)

Blind 75 › 198. 打家劫舍(中等) (第 2 章)
文章工具

题目

沿街一排放着 n 间房,每间藏着一笔钱,用数组 nums 给出。规则只有一条:相邻两间不能同时偷,否则会触发报警。问这一晚最多能到手多少钱。

换个说法:从数组里挑一组下标互不相邻的位置,使这些位置上的数字之和最大。

自造示例:nums = [10, 4, 3, 12] → 22(偷 nums[0]=10 和 nums[3]=12)。nums = [2, 1, 1, 2] → 4(偷 nums[0]=2 和 nums[3]=2)。

约束:1 ≤ n ≤ 100;0 ≤ nums[i] ≤ 400;所有金额为非负整数。

思路

一句话:每间房只有「偷」和「不偷」两种选择;求「前 i 间的最优」时,答案只依赖前两步,可以一路滚动。

先想最直白的:枚举所有“互不相邻”的下标组合,求和取最大。每个位置都分出偷/不偷两条路,像一棵二叉树,规模大约是 O(2n),n=100 时彻底不可行;即使只枚举合法组合,数量也是斐波那契级别,仍是指数增长。

那能不能贪心:每次都挑金额最大的房,再把相邻的划掉?不行。比如 [2, 3, 2],贪心先拿中间的 3,两边都不能碰,只得 3;实际上拿两头的 2 + 2 = 4 更优。

关键观察:问题有重叠子问题和无后效性。考虑最后一间 i:

  • 偷 i:那么 i-1 一定不能偷,答案 = 「前 i-2 间的最优」 + nums[i]。
  • 不偷 i:答案 = 「前 i-1 间的最优」。

于是 dp[i] = max(dp[i-1], dp[i-2] + nums[i])。而 dp[i] 只依赖前两项,用两个变量滚动即可,连 dp 数组都不需要。

解法

维护两个变量 prev2(到 i-2 为止的最优)和 prev1(到 i-1 为止的最优),从左到右扫一遍:

  1. 算当前最优:cur = max(prev2 + nums[i], prev1)。
  2. 滚动:prev2 = prev1,prev1 = cur。
  3. 扫完返回 prev1。

以 [10, 4, 3, 12] 为例:

下标 inums[i]prev2 + nums[i]prev1cur = max(…)
0100 + 10 = 10010
140 + 4 = 41010
2310 + 3 = 131013
31210 + 12 = 221322

最后 prev1 = 22,对应方案是偷下标 0 和 3。

下图把上面 3 个步骤串成流程,并逐步演示:

198. 打家劫舍(中等) 解法步骤流程图
图① 解法步骤流程图(共 3 步)。
198. 打家劫舍(中等) 按步骤逐步执行的动画
动图② 按步骤依次推进(循环播放)。

C++

C++ 片段需补全 main() 函数才能运行在实训台打开 →
class Solution {
public:
    int rob(vector<int>& nums) {
        int prev2 = 0;          // 到 i-2 为止的最优
        int prev1 = 0;          // 到 i-1 为止的最优
        for (int x : nums) {
            int cur = max(prev2 + x, prev1);   // 偷当前 / 不偷当前,取更大
            prev2 = prev1;
            prev1 = cur;
        }
        return prev1;
    }
};

Python

Python 3 示例