Problem: 198. 打家劫舍
文章目录
整体思路
这段代码同样旨在解决 “打家劫舍” 问题,但它采用的是一种 自底向上(Bottom-Up)的动态规划 方法,也称为 迭代法 或 制表法 (Tabulation)。这种方法通过构建一个DP表(f
数组),从最小的子问题开始,逐步计算出最终的解,避免了递归的开销。
该实现有一个巧妙之处,即通过调整DP数组的索引,使得循环内的状态转移方程无需处理边界条件,代码更为简洁。
状态定义与索引映射:
- 算法定义了一个DP数组
f
。这里的f[k]
被设计为表示“在考虑前k-1
间房屋时能偷到的最高金额”。 - 这是一个索引偏移的技巧。具体来说:
f[0]
和f[1]
可以看作是“哨兵”或基础情况,代表没有房屋可偷,金额为0。f[2]
将代表考虑第一间房(nums[0]
)时的最大金额。f[i+2]
将代表考虑前i+1
间房(nums[0...i]
)时的最大金额。- 最终,
f[n+1]
将代表考虑所有n
间房(nums[0...n-1]
)时的最大金额。
- 算法定义了一个DP数组
基础情况 (Base Cases):
- Java中
new int[n + 2]
数组默认所有元素都初始化为0。 f[0] = 0
和f[1] = 0
恰好满足我们的基础情况:在有0间或-1间房(不存在)时,最大收益为0。
- Java中
状态转移:
- 算法使用一个
for
循环,从i = 0
遍历到n-1
,这个i
对应的是nums
数组的索引。 - 在循环的每一步,计算
f[i+2]
的值,它代表考虑nums[0...i]
时的最大收益。 - 状态转移方程是
f[i + 2] = Math.max(f[i + 1], f[i] + nums[i])
。 - 我们来解析这个方程:
f[i+1]
:根据我们的定义,它代表考虑nums[0...i-1]
时的最大收益。这对应了不偷当前房屋nums[i]
的情况。f[i] + nums[i]
:f[i]
代表考虑nums[0...i-2]
时的最大收益。这对应了偷当前房屋nums[i]
的情况(因为偷了i
,就不能偷i-1
,所以只能从i-2
的状态转移过来)。
- 这个方程完美地表达了“偷”与“不偷”当前房屋
i
的选择。
- 算法使用一个
返回结果:
- 当循环结束后,
f
数组已经从f[2]
填充到了f[n+1]
。 - 我们需要的最终答案,即考虑所有
n
间房屋的最大收益,就存储在f[n+1]
中。
- 当循环结束后,
完整代码
class Solution {
/**
* 计算在不偷相邻房屋的情况下,能偷窃到的最高金额。
* @param nums 每个房屋的金额数组
* @return 最高总金额
*/
public int rob(int[] nums) {
int n = nums.length;
// f: 动态规划数组。f[k] 表示考虑前 k-1 间房屋能偷到的最大金额。
// 数组大小为 n+2 是为了使用 f[0] 和 f[1] 作为哨兵,简化循环内的逻辑。
int[] f = new int[n + 2];
// 循环变量 i 对应 nums 数组的索引。
for (int i = 0; i < n; i++) {
// 状态转移方程:
// 计算 f[i+2],它代表考虑房屋 nums[0...i] 时的最大收益。
// 这个收益取决于对当前房屋 nums[i] 的选择:
// 1. 不偷 nums[i]: 最大收益等于考虑 nums[0...i-1] 时的收益,即 f[i+1]。
// 2. 偷 nums[i]: 最大收益等于 nums[i] 加上考虑 nums[0...i-2] 时的收益,即 f[i] + nums[i]。
// 我们取这两者中的较大值。
f[i + 2] = Math.max(f[i + 1], f[i] + nums[i]);
}
// 循环结束后,f[n+1] 存储的是考虑所有 n 间房屋 (nums[0...n-1]) 时的最大收益。
return f[n + 1];
}
}
时空复杂度
时间复杂度:O(N)
- 循环:算法的核心是一个
for
循环,它从i = 0
遍历到n-1
。这个循环执行了N
次。 - 循环内部操作:
- 在循环的每一次迭代中,执行的都是基本的数组访问、
Math.max
和加法运算。 - 这些操作的时间复杂度都是 O(1)。
- 在循环的每一次迭代中,执行的都是基本的数组访问、
综合分析:
算法由 N
次 O(1) 的操作组成。因此,总的时间复杂度是 N * O(1)
= O(N)。
空间复杂度:O(N)
- 主要存储开销:算法创建了一个名为
f
的整型数组来存储动态规划的所有中间状态。 - 空间大小:该数组的长度是
n + 2
,其大小与输入N
成线性关系。 - 其他变量:
n
,i
等变量都只占用常数级别的空间,即 O(1)。
综合分析:
算法所需的额外空间主要由 f
数组决定。因此,其空间复杂度为 O(N)。
空间优化提示:
观察状态转移方程 f[i+2] = Math.max(f[i+1], f[i] + nums[i])
,可以发现 f[i+2]
的计算只依赖于前两个状态 f[i+1]
和 f[i]
。因此,我们不需要存储整个 f
数组,只需用两个变量来滚动记录前两个状态即可。这样可以将空间复杂度优化到 O(1)。