213. 打家劫舍 II(House Robber II)¶
频次 ★★★★ · 难度 🟡 · 高频:美团
题目¶
198 进阶版——房屋首尾相连成环。
示例:
思路¶
环形拆成两个线性问题:因为首尾互斥,只需要分别求"去掉尾"和"去掉头"的两个线性打家劫舍,取最大值。
robRange(nums, 0, n-2):偷第一家,不偷最后一家robRange(nums, 1, n-1):不偷第一家,偷最后一家- 取两趟的 max
代码¶
public int rob(int[] nums) {
int n = nums.length;
if (n == 1) return nums[0];
return Math.max(robRange(nums, 0, n - 2), robRange(nums, 1, n - 1));
}
private int robRange(int[] nums, int l, int r) {
int prev2 = 0, prev1 = 0;
for (int i = l; i <= r; i++) {
int cur = Math.max(prev1, prev2 + nums[i]);
prev2 = prev1;
prev1 = cur;
}
return prev1;
}
环形拆解与区间合同(实测复盘)¶
首尾互斥 → 全方案按"首偷/首不偷"二分两世界:
A=robRange(0, n-2) B=robRange(1, n-1) n=1 特判(自己≠自己邻居)
198 公式参数化为 robRange(l, r) 工具——"复用而不重写递推"是工程要义
- n=1 哨兵:单栋返 nums[0](位掩码参考自己算自己邻居的教训——环的自己≠邻居)
- 区间合同二分:甲=闭区间
i<=r(配首尾真下标 n-2/n-1);乙=半开i<r(配 n-1/n,空区间 l==r 有自然写法、与 Java 标准库同频道)——比较符必须匹配调用合同(377 错题本已校准此句) - 滚动地基 prev=0 的语义="区间外未偷"——天然覆盖"区间只有一栋/空"的退化情形(与 dp[0] 前缀语义课同源)
- 198 例2 [2,7,9,3,1] 环形后掉到 11(2+9 vs 7+3)——环比线性必不赚(禁令更紧)
复杂度¶
- 时间:O(n)
- 空间:O(1)
边界条件¶
- n = 1:直接返回(环只有一间房屋)
- n = 2:取 max(两间只能偷一间)
变式¶
- 198. 打家劫舍:线性版
- 337. 打家劫舍 III:树形版
易错点¶
n == 1需要特判——如果直接进robRange两趟会返回错误结果robRange是左闭右闭区间,包含 r
面试追问¶
- 环形约束的本质? 就是让首尾不能同时选。分两趟是最简单的思路,也可以用状态机 DP 解决(多一个"是否偷第一家"的维度)
关联题¶
- 同套路:198. 打家劫舍 —— 线性版
- 进阶:300. 最长递增子序列 —— 另一种递推模式
- 知识点:"环形转线性"的通用技巧见动态规划