将 x 减到 0 的最小操作数

标签: 数组 哈希表 二分查找 前缀和 滑动窗口

难度: Medium

给你一个整数数组 nums 和一个整数 x 。每一次操作时,你应当移除数组 nums 最左边或最右边的元素,然后从 x 中减去该元素的值。请注意,需要 修改 数组以供接下来的操作使用。

如果可以将 x 恰好 减到 0 ,返回 最小操作数 ;否则,返回 -1

 

示例 1:

输入:nums = [1,1,4,2,3], x = 5
输出:2
解释:最佳解决方案是移除后两个元素,将 x 减到 0 。

示例 2:

输入:nums = [5,6,7,8,9], x = 4
输出:-1

示例 3:

输入:nums = [3,2,20,1,1,3], x = 10
输出:5
解释:最佳解决方案是移除后三个元素和前两个元素(总共 5 次操作),将 x 减到 0 。

 

提示:

  • 1 <= nums.length <= 105
  • 1 <= nums[i] <= 104
  • 1 <= x <= 109

Submission

运行时间: 85 ms

内存: 27.0 MB

class Solution:
    def minOperations(self, nums: List[int], x: int) -> int:
        tar = sum(nums) - x
        l = len(nums)
        if tar < 0:return -1
        elif tar == 0:return l
        res = 0
        left = 0
        cnt = 0
        for right in range(l):
            cnt += nums[right]
            while cnt > tar:
                cnt -= nums[left]
                left += 1
            if cnt == tar and right - left + 1 > res:res = right - left + 1 
        return l - res if res else -1

Explain

题解采用的是转化问题的思路:将问题转化为在数组中找到一个连续子数组,其和为 sum(nums) - x,这样子数组外的元素之和就刚好等于 x。这可以通过滑动窗口技术来实现。首先,若求出的目标和 tar 小于0,则说明不可能实现,返回-1。如果 tar 等于0,则说明整个数组的和刚好等于 x,直接返回数组长度。接下来使用两个指针 left 和 right 来定义一个窗口,并在数组上滑动这个窗口,尝试找到和为 tar 的最长子数组。每次右指针 right 向右移动,将 nums[right] 加到窗口的累计和 cnt 中。如果 cnt 超过了 tar,就移动左指针 left 直到 cnt 不超过 tar。每找到一个符合条件的窗口,就尝试更新结果 res(最长符合条件子数组的长度)。最后,返回整个数组长度减去这个最长的子数组长度,得到的就是最小的操作次数。如果没有找到任何符合条件的子数组,则返回 -1。

时间复杂度: O(n)

空间复杂度: O(1)

class Solution:
    def minOperations(self, nums: List[int], x: int) -> int:
        tar = sum(nums) - x  # 目标值为整个数组的和减去 x
        l = len(nums)  # 数组长度
        if tar < 0: return -1  # 如果目标值小于0,直接返回-1
        elif tar == 0: return l  # 如果目标值为0,返回数组长度
        res = 0  # 初始化最长子数组长度为0
        left = 0  # 初始化左指针
        cnt = 0  # 窗口内元素的累计和
        for right in range(l):  # 右指针从头到尾遍历数组
            cnt += nums[right]  # 将当前元素加入累计和
            while cnt > tar:  # 当累计和大于目标值时,移动左指针缩小窗口
                cnt -= nums[left]  # 移除左指针指向的元素的值
                left += 1  # 左指针向右移动
            if cnt == tar and right - left + 1 > res: res = right - left + 1  # 如果找到符合条件的子数组,更新最长子数组长度
        return l - res if res else -1  # 返回最小操作数,如果没有符合条件的子数组,返回-1

Explore

tar 是通过计算 sum(nums) - x 得到的,它代表了我们需要在数组中找到的子数组的和。如果 tar 小于0,意味着 x 大于整个数组的和,这说明你无法通过移除数组中的任何元素组合来得到 x,因为即使移除所有元素,得到的和也只是整个数组的和,而这个和小于 x。因此,当 tar 小于 0 时,不可能通过移除元素达到目标 x,所以返回 -1。

在这个问题中,我们的目标是通过移除元素使数组的和减少到 x。因此,我们需要找到一个和为 tar = sum(nums) - x 的子数组,以使得剩余部分的和正好为 x。找到最长的和为 tar 的子数组意味着剩余部分的总和(即移除的部分)是最小的,从而使得我们移除的元素数量也是最小的。如果找到的是一个较短的和为 tar 的子数组,剩余部分的和仍为 x,但未被包含在子数组中的元素数量会更多,这并不符合我们减少操作次数的目标。

在使用滑动窗口策略时,当窗口内的元素总和(cnt)超过目标和(tar)时,表明窗口内包含了过多的元素,使得总和超标。此时,通过向右移动左指针(即缩小窗口的左边界),可以逐渐减少窗口内的元素总和。这个操作有助于调整窗口的大小,直到窗口内元素的总和再次等于或小于目标和 tar。这是一个试错的过程,通过不断调整(扩大或缩小)窗口,我们可以找到一个正好使得总和等于 tar 的子数组,从而确定最长的符合条件的子数组长度。

当 tar 等于 0 时,这意味着 x 等于整个数组的和(sum(nums))。因此,为了使数组的和减少到 x,我们需要移除的元素总和为 0,这实际上意味着我们不需要移除任何元素。然而,题目要求是通过移除元素使和减少到 x,所以在这种情况下,我们应该移除整个数组,使最终数组为空,从而达到将数组和减少到 x 的目标。因此,返回整个数组的长度,即移除所有元素的操作次数,是最小的操作数。