1. 题号和题目名称
- 打家劫舍 II
2. 题目叙述
你是一个专业的小偷,计划偷窃沿街的房屋,每间房内都藏有一定的现金。这个地方所有的房屋都 围成一圈 ,这意味着第一个房屋和最后一个房屋是紧挨着的。同时,相邻的房屋装有相互连通的防盗系统,如果两间相邻的房屋在同一晚上被小偷闯入,系统会自动报警 。
给定一个代表每个房屋存放金额的非负整数数组,计算你 在不触动警报装置的情况下 ,今晚能够偷窃到的最高金额。
3. 模式识别
这是一个动态规划问题。由于房屋围成一圈,偷窃第一间房屋和最后一间房屋不能同时发生,因此可以将问题拆分成两个子问题:
- 不偷窃第一间房屋,计算从第二间房屋到最后一间房屋能偷窃到的最大金额。
- 不偷窃最后一间房屋,计算从第一间房屋到倒数第二间房屋能偷窃到的最大金额。
最后取这两个子问题结果的最大值。
4. 考点分析
- 动态规划思想:需要找出状态转移方程来解决问题。
- 边界条件处理:处理好房屋数量较少时的边界情况。
- 问题拆分:将环形问题拆分成两个线性问题。
5. 所有解法
解法一:动态规划拆分问题
将问题拆分成两个线性的打家劫舍问题,分别计算不包含第一间房屋和不包含最后一间房屋的最大偷窃金额,然后取最大值。
解法二:暴力枚举(不推荐)
枚举所有可能的偷窃方案,判断是否触发警报并记录最大金额,但时间复杂度非常高,为指数级。
6. 最优解法(动态规划拆分问题)的 C 语言代码
#include <stdio.h>
#include <stdlib.h>
// 计算线性数组的最大偷窃金额
// nums: 代表每个房屋存放金额的数组
// start: 数组的起始索引
// end: 数组的结束索引
int robRange(int* nums, int start, int end) {
int prev = 0; // 前一个状态的最大偷窃金额
int curr = 0; // 当前状态的最大偷窃金额
for (int i = start; i <= end; i++) {
int temp = curr;
// 当前状态的最大偷窃金额为不偷当前房屋(curr)和偷当前房屋(prev + nums[i])的最大值
curr = (prev + nums[i]) > curr ? (prev + nums[i]) : curr;
prev = temp;
}
return curr;
}
// 计算环形房屋的最大偷窃金额
// nums: 代表每个房屋存放金额的数组
// numsSize: 数组的长度
int rob(int* nums, int numsSize) {
if (numsSize == 0) return 0; // 如果没有房屋,最大偷窃金额为 0
if (numsSize == 1) return nums[0]; // 如果只有一间房屋,最大偷窃金额就是该房屋的金额
// 不偷窃第一间房屋,计算从第二间房屋到最后一间房屋的最大偷窃金额
int robWithoutFirst = robRange(nums, 1, numsSize - 1);
// 不偷窃最后一间房屋,计算从第一间房屋到倒数第二间房屋的最大偷窃金额
int robWithoutLast = robRange(nums, 0, numsSize - 2);
// 返回两种情况的最大值
return robWithoutFirst > robWithoutLast ? robWithoutFirst : robWithoutLast;
}
7. 复杂度分析
- 时间复杂度: O ( n ) O(n) O(n),其中 n n n 是房屋的数量。因为只需要遍历数组两次。
- 空间复杂度: O ( 1 ) O(1) O(1),只使用了常数级的额外空间。