Leetcode2560. 打家劫舍 IV

Every day a Leetcode

题目来源:2560. 打家劫舍 IV

解法1:二分答案 + 动态规划

给定数组 nums,从中选择一个长度至少为 k 的子序列 A,要求 A 中没有任何元素在 nums 中是相邻的。

最小化 max⁡(A)。

看到「最大化最小值」或者「最小化最大值」就要想到二分答案,这是一个固定的套路。

对于本题,「偷走的最大金额」越小,能偷的房子就越少,反之越多。

一般地,二分的值越小,越不能/能满足要求;二分的值越大,越能/不能满足要求。有单调性的保证,就可以二分答案了。

把二分中点 mid 记作 mx,定义 dp[i] 表示从 nums[0] 到 nums[i] 中偷金额不超过 mx 的房屋,最多能偷多少间房屋。如果 dp[n−1]≥k 则表示答案至多为 mx,否则表示答案必须超过 mx。

用「选或不选」来分类讨论:

  • 不选 nums[i]:dp[i]=dp[i−1];
  • 选 nums[i],前提是 nums[i]≤mx:dp[i]=dp[i−2]+1。

这两取最大值,即:dp[i]=max(dp[i−1],dp[i−2]+1)。

代码:

/*
 * @lc app=leetcode.cn id=2560 lang=cpp
 *
 * [2560] 打家劫舍 IV
 */

// @lc code=start

// 二分答案 + 动态规划

class Solution
{
public:
    int minCapability(vector<int> &nums, int k)
    {
        int n = nums.size();
        int left = *min_element(nums.begin(), nums.end());
        int right = *max_element(nums.begin(), nums.end());

        // mx 是二分猜测的窃取能力
        auto check = [&](int mx, int k) -> bool
        {
            // dp[i]: 从 nums[0, i] 中偷金额不超过 mx 的房屋,最多能偷多少间房屋
            vector<long long> dp(n, 0);
            // 初始化
            dp[0] = nums[0] <= mx ? 1 : 0;
            if (dp[0] || nums[1] <= mx)
                dp[1] = 1;
            // 状态转移
            for (int i = 2; i < n; i++)
            {
                if (nums[i] > mx)
                    dp[i] = dp[i - 1];
                else
                    dp[i] = max(dp[i - 1], dp[i - 2] + 1);
            }
            return dp[n - 1] >= k;
        };

        while (left < right)
        {
            int mid = left + (right - left) / 2;
            if (check(mid, k))
                right = mid;
            else
                left = mid + 1;
        }
        return left;
    }
};
// @lc code=end

结果:

Leetcode2560. 打家劫舍 IV_第1张图片

复杂度分析:

时间复杂度:O(nlogU),其中 n 为数组 nums 的长度,U=max(nums)。

空间复杂度:O(n),其中 n 为数组 nums 的长度。

解法2:二分答案 + 贪心

也可以用贪心做。

考虑到只需要计算个数,在从左到右遍历的情况下只要当前房子可以偷,就立刻偷。

严格证明如下:

Leetcode2560. 打家劫舍 IV_第2张图片

代码:

// 二分答案 + 贪心

class Solution
{
public:
    int minCapability(vector<int> &nums, int k)
    {
        int n = nums.size();
        int left = *min_element(nums.begin(), nums.end());
        int right = *max_element(nums.begin(), nums.end());

        // mx 是二分猜测的窃取能力
        auto check = [&](int mx, int k) -> bool
        {
            int count = 0;
            for (int i = 0; i < n; i++)
                if (nums[i] <= mx)
                {
                    count++;
                    i++; // 跳过下一间房子
                }
            return count >= k;
        };

        while (left < right)
        {
            int mid = left + (right - left) / 2;
            if (check(mid, k))
                right = mid;
            else
                left = mid + 1;
        }
        return left;
    }
};

结果:

Leetcode2560. 打家劫舍 IV_第3张图片

复杂度分析:

时间复杂度:O(nlogU),其中 n 为数组 nums 的长度,U=max(nums)。

空间复杂度:O(1)。

你可能感兴趣的:(Every,day,a,LeetCode,leetcode,数据结构与算法,C++,动态规划,二分)