开启掘金成长之旅!这是我参与「掘金日新计划 · 12 月更文挑战」的第2天,点击查看活动详情
213. 打家劫舍2 题目描述:你是一个专业的小偷,计划偷窃沿街的房屋,每间房内都藏有一定的现金。这个地方所有的房屋都围成一圈 ,这意味着第一个房屋和最后一个房屋是紧挨着的。同时,相邻的房屋装有相互连通的防盗系统,如果两间相邻的房屋在同一晚上被小偷闯入,系统会自动报警。给定一个代表每个房屋存放金额的非负整数数组 ,计算在不触动警报装置的情况下 ,今晚能够偷窃到的最高金额。
| 示例1 | 示例2 |
|---|---|
| 输入: 输出: 解释:你不能先偷窃 号房屋(金额 = ),然后偷窃 号房屋(金额 = ), 因为他们是相邻的。 | 输入: 输出: 解释:你可以先偷窃 号房屋(金额 = ),然后偷窃 号房屋(金额 = )。偷窃到的最高金额 = 。 |
中规中矩的动态规划
本题比 198. 打家劫舍 仅仅多了一个条件,就是数据的首尾元素不能同时强。其实这是一个“烟雾弹”,首尾不能同时抢翻译过来就是,先计算 房屋区间内的最大金额,再计算 房屋区间内的最大金额,最后比较哪个多。
1、确定 dp 状态数组
定义 为前 房间内所能抢劫的最大金额。其中,
2、确定 dp 状态转移方程
如果房间数量大于两间,如何获取最高金额?对于第 间房子,有两种方式:
-
偷窃 间房屋,那就不能偷窃第 间房屋,最大总金额为前 个房屋的最大金额 加上第 间房屋的金额 ,即
-
不偷窃第 间房屋,最大总金额为前 个房屋的最大金额,即
故,,当 的情况下。
hold on!!! 收尾不能同时抢的逻辑体现在哪里?就在 初始状态 和 遍历顺序 中体现。
3、确定 dp 初始状态
抢第 个房间和第 个房间,总得放弃一个。因此我们定义两个参数 和 ,范围,其中 ,故初始值为
4、确定遍历顺序
-
抢首不抢尾,,遍历范围
-
抢尾不抢首,,遍历范围
5、确定最终返回值
回归状态定义,最终返回值为
6、代码示例
当房间个数小于等于 时,必定两两相连,所以三个房间选择金额最大的即可。
/**
* 空间复杂度 O(n),n是nums数组的长度
* 时间复杂度 O(n)
*/
function rob(nums: number[]): number {
const n = nums.length;
if(n < 3) return Math.max(...nums);
const dp1 = new Array(n - 1).fill(0); // 偷第0个不偷len-1个,遍历[0, len -1)
dp1[0] = nums[0];
dp1[1] = Math.max(nums[0], nums[1]);
const dp2 = new Array(n - 1).fill(0);// 不偷第0个偷len-1个,遍历[1, len)
dp2[0] = nums[1];
dp2[1] = Math.max(nums[1], nums[2]);
for(let i = 2; i < n - 1; i++) {
dp1[i] = Math.max(dp1[i - 2] + nums[i], dp1[i - 1])
}
for(let i = 3; i < n; i++) {
dp2[i - 1] = Math.max(dp2[i - 3] + nums[i], dp2[i - 2])
}
return Math.max(dp1[n - 2], dp2[n - 2]);
}
重复遍历逻辑提取 robInRange
/**
* 空间复杂度 O(n),n是nums数组的长度
* 时间复杂度 O(n)
*/
function rob(nums: number[]): number {
const n = nums.length;
if(n < 3) return Math.max(...nums);
return Math.max(robInRange(nums, 0, n - 1), robInRange(nums, 1, n));
}
/**
* 范围是[start,end),返回能抢到的最大值
*/
function robInRange(nums: number[], start: number, end: number): number {
const dp = new Array(end - start).fill(0);
dp[start] = nums[start];
dp[start + 1] = Math.max(nums[start], nums[start + 1]);
for(let i = start + 2; i < end; i++) {
dp[i] = Math.max(dp[i - 2] + nums[i], dp[i - 1]);
}
return dp[end - 1];
}
状态压缩
/**
* 空间复杂度 O(1)
* 时间复杂度 O(n),n是nums数组的长度
*/
function rob(nums: number[]): number {
const n = nums.length;
if(n < 3) return Math.max(...nums);
return Math.max(robInRange(nums, 0, n - 1), robInRange(nums, 1, n));
}
/**
* 范围是[start,end),返回能抢到的最大值
*/
function robInRange(nums: number[], start: number, end: number): number {
let first = nums[start];
let second = Math.max(nums[start], nums[start + 1]);
for(let i = start + 2; i < end; i++) {
[first, second] = [second, Math.max(first + nums[i], second)];
}
return second;
}