198. 打家劫舍
大约 3 分钟
198. 打家劫舍
📝 题目描述
你是一个专业的小偷,计划偷窃沿街的房屋。每间房内都藏有一定的现金,影响你偷窃的唯一制约因素就是相邻的房屋装有相互连通的防盗系统,如果两间相邻的房屋在同一晚上被小偷闯入,系统会自动报警。
给定一个代表每个房屋存放金额的非负整数数组,计算你 不触动警报装置的情况下,一夜之内能够偷窃到的最高金额。
📌 示例
示例 1
输入:
[1,2,3,1]输出:
4解释:偷窃 1 号房屋(金额 = 1),然后偷窃 3 号房屋(金额 = 3)。 偷窃到的最高金额 = 1 + 3 = 4。
示例 2
输入:
[2,7,9,3,1]输出:
12解释:偷窃 1 号房屋(金额 = 2),偷窃 3 号房屋(金额 = 9),接着偷窃 5 号房屋(金额 = 1)。 偷窃到的最高金额 = 2 + 9 + 1 = 12。
🚀 最优解:动态规划
🔍 思路解析
核心规则:不能偷相邻房屋,因此对于第 i 间房,有两种选择:
- 不偷第
i间房:最高金额 = 偷到第i-1间房的最高金额; - 偷第
i间房:最高金额 = 偷到第i-2间房的最高金额 + 第i间房的金额。
因此,状态转移方程为:
dp[i] = max(dp[i-1], dp[i-2] + nums[i])其中 dp[i] 表示偷到第 i 间房时的最高金额。
✅ 完整代码(Java)
import java.util.Scanner;
public class Main {
public static void main(String[] args) {
Scanner scanner = new Scanner(System.in);
// 读取数组长度
int n = scanner.nextInt();
int[] nums = new int[n];
// 读取数组元素
for (int i = 0; i < n; i++) {
nums[i] = scanner.nextInt();
}
Solution solution = new Solution();
int result = solution.rob(nums);
System.out.println(result);
scanner.close();
}
}
class Solution {
public int rob(int[] nums) {
int n = nums.length;
// 边界情况:只有1间房时直接返回
if (n == 1) return nums[0];
// dp数组:dp[i]表示前i+1间房的最高偷窃金额
int[] dp = new int[n];
dp[0] = nums[0];
dp[1] = Math.max(nums[0], nums[1]);
// 从第2间房开始递推
for (int i = 2; i < n; i++) {
dp[i] = Math.max(dp[i-1], dp[i-2] + nums[i]);
}
return dp[n - 1];
}
}🧠 通俗易懂解释
可以把问题理解为“选房子但不能选邻居,怎么选钱最多”:
- 只有1间房:只能偷这一间,金额是它本身;
- 有2间房:选金额大的那间;
- 有3间房:要么偷第3间+第1间的钱,要么不偷第3间(只偷第2间),选两者中多的那个;
- 以此类推,每多一间房,都对比“不偷它(延续前一间的最大金额)”和“偷它(加上前前间的最大金额)”哪个更划算。
比如示例2的数组 [2,7,9,3,1]:
- 第1间:2 → 最高2;
- 第2间:max(2,7)=7 → 最高7;
- 第3间:max(7, 2+9)=11 → 最高11;
- 第4间:max(11, 7+3)=11 → 最高11;
- 第5间:max(11, 11+1)=12 → 最高12(对应偷第1、3、5间)。
⏱ 时间复杂度
O(n)
只需遍历数组一次,每间房的计算是常数时间。
💾 空间复杂度
O(n)
使用了长度为 n 的dp数组存储中间状态(可优化为O(1),只需用两个变量记录前两间房的最大金额)。
对于当前i 是要 i,i - 2 or i - 1 打劫或者不打劫
