每周一题 352 简简单单的贪心法

plus2047 于 2023-07-09 发布

本周四道题目都比较简单,第三题第四题难度相似,我们一起来看一下第四题。

Problem 4

可以认为,我们需要对每个子数组(共有 n-k+1 个子数组)确定一个数 x(也即这个子数组的操作次数),数组内每个数都要减去 x。最终目标是所有元素变成 0

这个问题非常眼熟,很容易发现,只有一个子数组能覆盖第一个数,所以这个子数组需要减去的数是确定的。一旦第一个数组的减数确定,第二个数组的减数也能被第二个数和第一个数组的减数确定,以此类推,可以贪心的从左向右(也可以从右向左)逐个确定每个子数组的减数。在这个过程中,任意一个数字变成负数就意味着无法完成。

但是,确定了一个子数组的减数之后,如果模拟的逐个从子数组的数中减去这个数,最终复杂度是 n^2 不可接受。比较容易想到的一个解决办法是,我们把会覆盖下一个数的所有减数放在一个双端队列中,并维护这个队列中数字和,这样只需要遍历一次输入 O(n) 的复杂度就能解决问题。

class Solution:
    def checkArray(self, nums: List[int], k: int) -> bool:
        n = len(nums)
        q = collections.deque()
        total = 0
        
        for i in range(n):
            if i >= k:
                # 从第 `k` 个数开始,已经离开了最左侧的一个区间
                # 所以把最左侧的减数 pop 掉
                total -= q.popleft()
            if nums[i] < total or i > n - k and nums[i] != total:
                # 第一种情况:当前数字会变成负数,则再也无法变成 0 了
                # 第二种情况:如果当前已经是最后 k - 1 个数字,则无法向右打开新的子数组
                # 所以此时只要不是恰好变为 0 就会失败
                return False
            if i <= n - k:
                # 如果不是最后 k - 1 个数字,则以该数字为最左侧端点,打开一个子数组
                # 该子数组对应的减数必须恰好令 nums[i] 变为 0
                # 注意 nums[i] 首先需要减去已经打开的子数组减数之和
                q.append(nums[i] - total)
                total = nums[i]
        
        return True