题目描述
你是一个专业的小偷,计划偷窃沿街的房屋,每间房内都藏有一定的现金。这个地方所有的房屋都围成一圈 ,这意味着第一个房屋和最后一个房屋是紧挨着的。同时,相邻的房屋装有相互连通的防盗系统,如果两间相邻的房屋在同一晚上被小偷闯入,系统会自动报警 。
给定一个代表每个房屋存放金额的非负整数数组,计算你 在不触动警报装置的情况下 ,今晚能够偷窃到的最高金额。
示例 1 :
输入 :nums = 2,3,2
输出 :3
解释:你不能先偷窃 1 号房屋(金额 = 2),然后偷窃 3 号房屋(金额 = 2), 因为他们是相邻的。
示例 2 :
输入 :nums = 1,2,3,1
输出 :4
解释 :你可以先偷窃 1 号房屋(金额 = 1),然后偷窃 3 号房屋(金额 = 3)。
偷窃到的最高金额 = 1 + 3 = 4 。
算法思想
这道题和 打家劫舍 I 的唯一区别就是所有房屋围成一圈,第一个房屋和最后一个房屋相邻。因此可以把这道题转换成打家劫舍 I
对于下标为 000 的屋子,可以偷也可以不偷:
-
如果偷了下标为 000 屋子,那下标为 111 的屋子和下标为 n−1n-1n−1 的屋子就不能偷,因此答案就是在 2,n−22, n-22,n−2 区间上做打家劫舍 I ,得到最大金额,再加上 nums0nums0nums0
-
如果下标为 000 的屋子,那下标为 111 的屋子和下标为 n−1n-1n−1 的屋子是能偷的,因此答案就是在 1,n−11, n-11,n−1 区间上做打家劫舍 I 得到的最大金额
-
最终答案就是它们之间的最大值

对于 打家劫舍 I,思路如下:
创建 dpdpdp 数组,确定状态表示:以 iii 为结尾,dpidpidpi 就表示到达 iii 位置时的最高金额,由于 iii 位置可偷可不偷,所以继续细分,创建两个数组 fff 和 ggg,fififi 表示偷了 iii 位置后的最大金额,gigigi 表示不偷 iii 位置时的最大金额
推导状态转移方程:对于 fififi,它要偷 iii 位置,所以 fififi 应该等于 0,i−10, i-10,i−1 区间上的最大金额再加上 numsinumsinumsi。既然偷了 iii 位置,i−1i-1i−1 位置肯定不偷,所以 0,i−10, i - 10,i−1 区间上的最大金额就是 gi−1gi-1gi−1,fi=gi−1+numsifi = gi-1 + numsifi=gi−1+numsi。对于 gigigi,它不偷 iii 位置,所以 i−1i-1i−1 位置可偷可不偷,如果偷了,gi=fi−1gi = fi-1gi=fi−1,也就是偷了 i−1i-1i−1 位置的最大金额,如果不偷,gi=gi−1gi = gi-1gi=gi−1,也就是不偷 i−1i-1i−1 位置的最大金额,gigigi 要的是最大值,所以 gi=max(gi−1,fi−1)gi = max(gi-1, fi-1)gi=max(gi−1,fi−1)
初始化:根据状态转移方程,f0f0f0 和 g0g0g0 填的时候会越界,要初始化,由于 f0f0f0 表示偷了 000 位置的最大金额,所以 f0=nums0f0 = nums0f0=nums0,g0g0g0 表示不偷 000 位置的最大金额,前面没有其他值了,所以 g0=0g0 = 0g0=0
填表顺序:从左到右,两个一起填
返回值:max(fn−1,gn−1)max(fn-1, gn-1)max(fn−1,gn−1)
代码
cpp
class Solution {
public:
int func(vector<int>& nums, int begin, int end)
{
if (begin > end) return 0; // 区间不存在时,没有最大金额
int n = nums.size();
vector<int> f(n, 0);
vector<int> g(n, 0);
f[begin] = nums[begin];
for (int i = begin + 1;i <= end;++i)
{
f[i] = g[i - 1] + nums[i];
g[i] = max(f[i - 1], g[i - 1]);
}
return max(f[end], g[end]);
}
int rob(vector<int>& nums)
{
int n = nums.size();
// 选0位置时的最高金额,不选0位置时的最高金额之间的最大值
return max(func(nums, 2, n - 2) + nums[0], func(nums, 1, n - 1));
}
};