力扣hot100之198:打家劫舍
力扣hot100之198:打家劫舍

LeetCode 198. 打家劫舍

题目描述

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

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


示例分析

示例 1

输入: nums = [1,2,3,1]
输出: 4

解释:

  • 偷第 1 间房,可以拿到 1

  • 偷第 3 间房,可以拿到 3

  • 总金额为 4

不能连续偷第 2 间和第 3 间,因为它们是相邻的。


示例 2

输入: nums = [2,7,9,3,1]
输出: 12

解释:

  • 偷第 1 间房,得到 2

  • 偷第 3 间房,得到 9

  • 偷第 5 间房,得到 1

总金额为 12。


解题思路

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

因为每一间房屋都只有两种选择:

  1. 偷这一间

  2. 不偷这一间

而一旦偷了当前房屋,就不能偷前一间;如果不偷当前房屋,那么前一间偷不偷都无所谓。

所以这道题的关键就是:走到当前位置时,最大收益是多少

你给出的代码如下:

class Solution {
    public int rob(int[] nums) {
        int prev = 0;
        int curr = 0;
        for (int i : nums) {
            int temp = Math.max(curr, prev + i);
            prev = curr;
            curr = temp;
        }
        return curr;
    }
}

这份代码写得非常简洁,本质上就是把动态规划进行了空间优化。


状态定义

为了理解这份代码,我们先从最普通的动态规划思路开始。

dp[i] 表示:

偷到第 i 间房屋时,能够获得的最大金额。

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

1. 不偷第 i

如果不偷当前房屋,那么最大收益就是前一间房屋时的最大收益:

dp[i - 1]

2. 偷第 i

如果偷当前房屋,那么前一间就不能偷,所以最大收益为:

dp[i - 2] + nums[i]

状态转移方程

所以可以得到状态转移公式:

dp[i] = max(dp[i - 1], dp[i - 2] + nums[i])

这就是整道题最核心的地方。

意思非常简单:

  • 要么不偷当前房屋,沿用前面的最优解

  • 要么偷当前房屋,那么就只能接在前前间房屋的最优解后面

两种情况取最大值即可。


为什么可以用两个变量优化

观察状态转移方程:

dp[i] = max(dp[i - 1], dp[i - 2] + nums[i])

可以发现,计算 dp[i] 时,其实只会用到:

  • dp[i - 1]

  • dp[i - 2]

也就是说,我们没有必要真的开一个数组来保存所有状态,只需要两个变量就够了。

在你的代码中:

  • prev 表示前前一间房屋的最大收益

  • curr 表示前一间房屋的最大收益

然后用一个临时变量 temp 来表示当前房屋处理完之后的最大收益。


代码解析

1. 初始化

int prev = 0;
int curr = 0;

一开始还没有偷任何房子,所以最大收益都是 0。

这里可以理解为:

  • prev 对应 dp[i - 2]

  • curr 对应 dp[i - 1]


2. 遍历每一间房屋

for (int i : nums) {

这里的 i 表示当前房屋中的金额。


3. 计算当前房屋的最优解

int temp = Math.max(curr, prev + i);

这里就是状态转移方程的压缩版:

当前最优解 = max(不偷当前房屋, 偷当前房屋)
          = max(curr, prev + i)
  • curr:表示不偷当前房屋,直接沿用前面的最优解

  • prev + i:表示偷当前房屋,那么就只能加上前前一间房屋的最优解


4. 更新状态

prev = curr;
curr = temp;

状态向前推进一位:

  • 原来的 curr 变成下一轮的 prev

  • 当前算出来的 temp 变成下一轮的 curr


5. 返回结果

return curr;

循环结束后,curr 就是最终答案。


示例推演

我们以数组:

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

为例,来完整模拟一下。

初始状态:

prev = 0
curr = 0

遍历到 2

temp = max(0, 0 + 2) = 2
prev = 0
curr = 2

遍历到 7

temp = max(2, 0 + 7) = 7
prev = 2
curr = 7

遍历到 9

temp = max(7, 2 + 9) = 11
prev = 7
curr = 11

遍历到 3

temp = max(11, 7 + 3) = 11
prev = 11
curr = 11

遍历到 1

temp = max(11, 11 + 1) = 12
prev = 11
curr = 12

最终答案为:

12

为什么不能贪心

有些同学第一次做这题时,可能会想:

  • 当前房屋金额大就偷

  • 当前房屋金额小就不偷

这种贪心思路是不行的。

因为当前房屋是否值得偷,不仅取决于它本身的金额,还取决于它和前面房屋组合之后的总收益。

比如:

[2,1,1,2]

如果只看眼前,很容易做出错误选择。 正确答案是偷第 1 间和第 4 间,总金额为 4。

所以这题必须用动态规划,从整体最优的角度考虑。


复杂度分析

时间复杂度

O(n)

只遍历了一次数组。

空间复杂度

O(1)

没有使用额外的数组,只用了常数个变量。


和普通 DP 写法对比

普通 DP 写法

class Solution {
    public int rob(int[] nums) {
        if (nums.length == 0) return 0;
        if (nums.length == 1) return nums[0];

        int[] dp = new int[nums.length];
        dp[0] = nums[0];
        dp[1] = Math.max(nums[0], nums[1]);

        for (int i = 2; i < nums.length; i++) {
            dp[i] = Math.max(dp[i - 1], dp[i - 2] + nums[i]);
        }

        return dp[nums.length - 1];
    }
}

这种写法更容易理解,但需要一个额外数组来存储状态。


空间优化写法

你给出的写法把 dp[i - 1]dp[i - 2] 压缩成了两个变量:

class Solution {
    public int rob(int[] nums) {
        int prev = 0;
        int curr = 0;
        for (int i : nums) {
            int temp = Math.max(curr, prev + i);
            prev = curr;
            curr = temp;
        }
        return curr;
    }
}

这种写法更加简洁,也是面试中更推荐掌握的写法。


总结

这道题的核心就是这条状态转移方程:

dp[i] = max(dp[i - 1], dp[i - 2] + nums[i])

理解这条式子之后,整道题就非常清楚了:

  1. 不偷当前房屋,就继承前一个状态

  2. 偷当前房屋,就加上前前一个状态

  3. 两者取最大值

  4. 由于只依赖前两个状态,所以可以用两个变量优化空间

这是一道非常经典的入门动态规划题,也是很多后续 DP 题的基础。 只要把这题吃透,后面很多“选或不选”的题目都会顺手很多。


如果对您有帮助的话,能否支持一下博主?
暂无评论

发送评论 编辑评论


				
|´・ω・)ノ
ヾ(≧∇≦*)ゝ
(☆ω☆)
(╯‵□′)╯︵┴─┴
 ̄﹃ ̄
(/ω\)
∠( ᐛ 」∠)_
(๑•̀ㅁ•́ฅ)
→_→
୧(๑•̀⌄•́๑)૭
٩(ˊᗜˋ*)و
(ノ°ο°)ノ
(´இ皿இ`)
⌇●﹏●⌇
(ฅ´ω`ฅ)
(╯°A°)╯︵○○○
φ( ̄∇ ̄o)
ヾ(´・ ・`。)ノ"
( ง ᵒ̌皿ᵒ̌)ง⁼³₌₃
(ó﹏ò。)
Σ(っ °Д °;)っ
( ,,´・ω・)ノ"(´っω・`。)
╮(╯▽╰)╭
o(*////▽////*)q
>﹏<
( ๑´•ω•) "(ㆆᴗㆆ)
😂
😀
😅
😊
🙂
🙃
😌
😍
😘
😜
😝
😏
😒
🙄
😳
😡
😔
😫
😱
😭
💩
👻
🙌
🖕
👍
👫
👬
👭
🌚
🌝
🙈
💊
😶
🙏
🍦
🍉
😣
Source: github.com/k4yt3x/flowerhd
颜文字
Emoji
小恐龙
花!
上一篇
下一篇