打家劫舍
打家劫舍的状态如何定义?
- 问题: 相邻房屋不能同时偷, 求最高金额 (198);
- 阶段: 按房屋顺序划分;
- 状态:
dp[i]为前i间房屋能偷到的最高金额; - 口径: 用"前
i间"而不是"偷第i间", 因为可以跳过当前房屋;
如何写出转移方程?
- 决策: 对第
i间房屋只有偷与不偷两种选择; - 不偷:
dp[i] = dp[i-1]; - 偷: 前
i-2间的结果加上本次金额,dp[i] = dp[i-2] + nums[i-1]; - 方程:
dp[i] = max(dp[i-1], dp[i-2] + nums[i-1]); - 初值:
dp[0] = 0,dp[1] = nums[0]; - 答案:
dp[n];
const rob = (nums) => {
const dp = new Array(nums.length + 1);
dp[0] = 0;
dp[1] = nums[0];
for (let i = 2; i <= nums.length; i++) {
dp[i] = Math.max(dp[i - 1], dp[i - 2] + nums[i - 1]);
}
return dp[nums.length];
};
- 复杂度: 时间
O(n), 空间O(n), 可用两个滚动变量降到O(1);
环形排列的打家劫舍如何处理?
- 问题: 房屋首尾相连, 第一间和最后一间也不能同时偷 (213);
- 拆解: 首尾不能同时选, 于是把环形拆成两个线性子问题;
- 子问题一: 只考虑
[0, n-2](不偷最后一间); - 子问题二: 只考虑
[1, n-1](不偷第一间); - 答案:
max(子问题一, 子问题二); - 特判: 只有一间房屋时直接返回该金额;
const rob2 = (nums) => {
const helper = (arr) => {
let prev = 0;
let curr = 0;
for (const x of arr) {
[prev, curr] = [curr, Math.max(curr, prev + x)];
}
return curr;
};
if (nums.length === 1) return nums[0];
return Math.max(helper(nums.slice(1)), helper(nums.slice(0, -1)));
};