题目描述
你是一个专业的小偷,计划偷窃沿街的房屋,每间房内都藏有一定的现金。这个地方所有的房屋都围成一圈,这意味着第一个房屋和最后一个房屋是紧挨着的。同时,相邻的房屋装有相互连通的防盗系统,如果两间相邻的房屋在同一晚上被小偷闯入,系统会自动报警。
给定一个代表每个房屋存放金额的非负整数数组,计算你在不触动警报装置的情况下,能够偷窃到的最高金额。
样例1
输入: [2,3,2]
输出: 3
解释: 你不能先偷窃 1 号房屋(金额 = 2),然后偷窃 3 号房屋(金额 = 2), 因为他们是相邻的。
样例2
输入: [1,2,3,1]
输出: 4
解释: 你可以先偷窃 1 号房屋(金额 = 1),然后偷窃 3 号房屋(金额 = 3)。
偷窃到的最高金额 = 1 + 3 = 4 。
算法
(动态规划) $O(n)$
此题和 LeetCode 198. 打家劫舍 类似,唯一的不同点是首尾相邻。
可以将此题拆为两个子问题,如$[2,7,9,3,1]$:
- 选头不选尾,$[2,7,9,3]$
- 选尾不选头,$[7,9,3,1]$
- 选取两种情况的最大值。
时间复杂度
时间复杂度$O(n)$,空间复杂度$O(n)$,可以优化为常数$O(1)$
C++ 代码
class Solution {
public:
int rob(vector<int>& nums) {
int n = nums.size();
if(n == 1) return nums[0]; // 判断第一个边界
vector<int> f(n+1, 0), g(n+1, 0);
// 第一个不选
for(int i = 2; i <= n; i++)
{
f[i] = max(f[i-1], g[i-1]); //当前没选,上一个选不选任意,取最大值
g[i] = f[i-1] + nums[i-1]; // 当前选了,上一个一定不选
}
int res = max(f[n], g[n]);
// 选第一个,最后一个不选
for(int i = 1; i <= n; i++)
{
f[i] = max(f[i-1], g[i-1]);
g[i] = f[i-1] + nums[i-1];
}
return max(res, f[n]); // 返回两个子问题的最大值
}
};
空间压缩为常数:
class Solution {
public:
int rob(vector<int>& nums) {
int n = nums.size();
if(n == 0) return 0;
if(n == 1) return nums[0]; // 判断边界
if(n == 2) return max(nums[0], nums[1]);
int first_g = f(nums, 0, n-2); // 选头不选尾
int first_f = f(nums, 1, n-1); // 选尾不选头
return max(first_f, first_g); // 返回两个子问题的最大值
}
int f(vector<int> &nums, int start, int end){
int n = nums.size();
int f = 0, g = 0; // f表示不选当前数字,g表示选当前数字
for(int i = start; i <= end; i++){
int last_f = f, last_g = g;
f = max(last_f, last_g);
g = nums[i] + last_f;
}
return max(f, g);
}
};
好活