图解题库 / 一维 DP

198. 打家劫舍

中等 含交互动画

学完这道你应该能: 说清它为什么用「一维 DP」,而不是死记步骤; 合上页面,自己默写出核心代码; 用 30 秒把思路讲清楚。

题目描述

一排房子各有现金,不能偷相邻两家(会报警),求最多能偷到多少。

nums = [2, 7, 9, 3, 1]
输出 = 12 (偷 2 + 9 + 1)

思路解析

要是挨个枚举每家偷或不偷,再排除相邻冲突,组合数爆炸。其实你只需要想清楚:到每家时,偷它和不偷它哪个赚得多,然后把结果记下来。

到第 i 家,你有两种选择:不偷,那就继承 dp[i-1];或者,那只能接 dp[i-2],再加上 nums[i]。两者取大的:dp[i] = max(dp[i-1], dp[i-2] + nums[i])。

建表:表头是每家的现金。dp[i] 表示「到第 i 家为止最多能偷多少」。

dp[0] = 2:只有一家,那肯定偷:dp[0] = 2。

dp[1] = max(2,7):两家相邻只能选一个,偷金额大的那个 7:dp[1] = max(2, 7) = 7。

dp[2] · 偷 9:偷第 3 家(9)就得接 dp[0]=2,共 11;不偷的话是 7。取大的,11。

dp[3]:偷第 4 家(3)接 dp[1]=7 共 10,不如不偷的 11。所以 dp[3] = 11。

dp[4] · 偷 1:偷第 5 家(1)接 dp[2]=11 共 12,比 11 大。dp[4] = 12 就是答案。

「选当前 + 跳过相邻」和「不选当前 + 继承上一个」取最优——打家劫舍、删除并获得点数、按摩师都是这个套路。

参考代码(Python)

Python
1if not nums: return 0
2prev2, prev1 = 0, 0          # dp[i-2], dp[i-1]
3for x in nums:
4    cur = max(prev1, prev2 + x)   # 不偷 vs 偷
5    prev2, prev1 = prev1, cur     # 滚动前移
6return prev1

复杂度分析

  • 时间复杂度:O(n) —— 扫一遍
  • 空间复杂度:O(1) —— 滚动两个变量

套路模板

记住骨架:不选=继承 prev1、选=prev2+当前、两者取优、滚动前移。改一下「选的约束」就能套一大类一维 DP。

Python
1# 「每个元素选或不选、选了有约束」都套
2prev2, prev1 = 0, 0
3for x in nums:
4    cur = max(prev1,            # 不选当前
5              prev2 + x)        # 选当前(接更前面的)
6    prev2, prev1 = prev1, cur
7return prev1

易错点

  • 偷当前时接 dp[i-1] → ✅ 接 dp[i-2](相邻不能同偷,要跳过紧挨的那家)
  • 空数组不特判 → ✅ 先处理 nums 为空(否则返回值/下标越界)

以上文字与上方动画完全同一思路:先看动画建立直觉 → 读文字巩固 → 关掉页面自己默写一遍代码,能写出来才算真的掌握

下一题 →739. 每日温度 ← 返回题库