动态规划详细题解——力扣198.打家劫舍
力扣198.打家劫舍

【LeetCode 198】打家劫舍(Java 动态规划详细题解)
一、题目描述
你是一个专业的小偷,计划偷窃沿街的房屋。每间房内都藏有一定的现金。
唯一的限制是:
相邻的两间房屋装有连通的防盗系统,如果在同一晚上闯入相邻的两间房屋,系统会自动报警。
给定一个数组 nums,表示每个房屋的金额,计算在不触动警报的情况下,最多能偷取的金额。
二、示例
示例 1:
输入: [1,2,3,1]
输出: 4
解释: 偷 1 号房屋 (金额 = 1),再偷 3 号房屋 (金额 = 3),总金额 = 4。
示例 2:
输入: [2,7,9,3,1]
输出: 12
解释: 偷 1、3、5 号房屋,金额 = 2 + 9 + 1 = 12。
三、思路分析
本题是一个典型的动态规划问题。
思考过程如下:
设:
dp[i]表示偷到第 i 间房屋时能获得的最大金额。
对于第 i 间房,有两种选择:
- 不偷第 i 间房:
那么最大金额就是前一间的最大值,即dp[i-1] - 偷第 i 间房:
因为相邻房不能同时偷,所以上一个能偷的是i-2,此时金额为dp[i-2] + nums[i]
因此,状态转移方程为:
dp[i] = max(dp[i-1], dp[i-2] + nums[i])
初始状态:
dp[0] = nums[0]
dp[1] = max(nums[0], nums[1])
四、代码实现(Java)
class Solution {public int rob(int[] nums) {int n = nums.length;if (n == 0) return 0;if (n == 1) return nums[0];// 初始化前两个状态int[] dp = new int[n];dp[0] = nums[0];dp[1] = Math.max(nums[0], nums[1]);// 动态规划迭代for (int i = 2; i < n; i++) {dp[i] = Math.max(dp[i - 1], dp[i - 2] + nums[i]);}return dp[n - 1];}
}

五、空间优化(O(1) 实现)
其实我们不需要整个数组,只需要保存 dp[i-1] 和 dp[i-2]。
因此可以进一步优化:
class Solution {public int rob(int[] nums) {int n = nums.length;if (n == 0) return 0;if (n == 1) return nums[0];int prev2 = nums[0]; // dp[i-2]int prev1 = Math.max(nums[0], nums[1]); // dp[i-1]for (int i = 2; i < n; i++) {int current = Math.max(prev1, prev2 + nums[i]);prev2 = prev1;prev1 = current;}return prev1;}
}

六、复杂度分析
| 项目 | 分析 |
|---|---|
| 时间复杂度 | O(n),只需一次遍历数组 |
| 空间复杂度 | O(1),使用常数个变量 |
七、举例推导
以 nums = [2,7,9,3,1] 为例:
| 房屋编号 | 金额 | 计算过程 | dp 值 |
|---|---|---|---|
| 0 | 2 | dp[0]=2 | 2 |
| 1 | 7 | max(2,7)=7 | 7 |
| 2 | 9 | max(7,2+9)=11 | 11 |
| 3 | 3 | max(11,7+3)=11 | 11 |
| 4 | 1 | max(11,11+1)=12 | 12 |
最终结果:dp[4] = 12
八、扩展思考
本题是打家劫舍系列的第一题。后续还可以学习:
| 题号 | 名称 | 特点 |
|---|---|---|
| 198 | 打家劫舍 | 直线排列 |
| 213 | 打家劫舍 II | 房屋围成环 |
| 337 | 打家劫舍 III | 房屋为二叉树结构 |
掌握这题后,理解后续题型会更加容易。
九、总结
- 关键在于理解“不能偷相邻房屋” → 状态转移方程;
- 动态规划是最优子结构与重叠子问题的结合;
- 空间优化技巧常用于此类连续依赖的 DP 问题。
