213. 打家劫舍 II

题目描述

你是一个专业的小偷,计划偷窃沿街的房屋,每间房内都藏有一定的现金。这个地方所有的房屋都围成一圈 ,这意味着第一个房屋和最后一个房屋是紧挨着的。同时,相邻的房屋装有相互连通的防盗系统,如果两间相邻的房屋在同一晚上被小偷闯入,系统会自动报警 。

给定一个代表每个房屋存放金额的非负整数数组,计算你 在不触动警报装置的情况下 ,今晚能够偷窃到的最高金额。

示例 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));
    }
};