LeetCode 1658. 将 x 减到 0 的最小操作数

5 阅读5分钟

题目链接

1658. 将X减到0的最少操作次数

题目描述

给你一个整数数组 nums 和一个整数 x 。每一步操作中,你可以从数组的左端或右端移除一个元素,直到最后剩下的元素和为 x 。请你返回需要执行的最少操作次数。如果无法做到,返回 -1

示例

  • 输入:nums = [1,1,4,2,3], x = 5

    • 输出:2
    • 解释:移除两端的1和3,剩余元素和为4+2=6?不,等一下。哦,实际正确操作是移除最左边的1和最右边的3,剩余元素是[1,4,2],和为7?哦不,可能我理解错了。实际上正确的解法是:我们需要让剩余元素和为x=5。正确操作是移除最左边的1和最右边的3,剩余元素是[1,4,2],和为7,这不对。哦,原来我的思路有误。正确的做法是:我们需要找到中间的一个子数组,其和为total - x = (1+1+4+2+3) -5=6。最长这样的子数组是[1,1,4],和为6,长度为3。因此最少操作次数是5-3=2,即移除两端的元素,剩余中间子数组。
  • 输入:nums = [5,2,3,1,1], x = 5

    • 输出:1
    • 解释:移除最左边的5,剩余元素和为2+3+1+1=7≠5?哦,不。正确思路是:total=5+2+3+1+1=12,target=12-5=7。最长子数组和为7的是[2,3,1,1],长度为4。所以最少操作次数是5-4=1,即移除最左边的5。
  • 输入:nums = [3,2,20,1,1,3], x = 10

    • 输出:5
    • 解释:total=3+2+20+1+1+3=30,target=30-10=20。最长子数组和为20的是[20],长度为1。所以最少操作次数是6-1=5,即移除除了20之外的所有元素。

解法分析:滑动窗口法

核心思路

本题的关键在于将问题转换:从数组两端移除元素使剩余和为x,等价于在数组中找到一个中间子数组,使得其和为total - x(其中total是数组总和)。找到这样的最长子数组后,最少操作次数即为数组长度减去该子数组的长度。

代码实现

class Solution:
    def minOperations(self, nums: List[int], x: int) -> int:
        total = sum(nums)
        n = len(nums)
        
        # 边界情况处理
        if total < x:
            return -1
        if total == x:
            return n
        
        target = total - x
        window = 0  # 当前窗口的和
        left = 0    # 滑动窗口左指针
        ans = -1    # 最长子数组长度
        
        for right in range(n):
            # 扩展右指针,累加当前元素到窗口
            window += nums[right]
            
            # 收缩左指针,直到窗口和不超过target
            while window > target and left <= right:
                window -= nums[left]
                left += 1
            
            # 如果窗口和等于target,更新最长子数组长度
            if window == target:
                ans = max(ans, right - left + 1)
        
        # 计算最少操作次数:数组长度 - 最长子数组长度
        return n - ans if ans != -1 else -1

代码解析

  1. 初始化与边界处理

    • 计算数组总和total和长度n
    • 如果total < x,直接返回-1(无法通过移除元素使和为x)
    • 如果total == x,返回n(需要移除所有元素)
  2. 目标转换

    • 计算目标子数组和target = total - x,我们需要找到和为target的最长子数组
  3. 滑动窗口维护

    • window记录当前窗口的和,left为左指针,ans记录最长子数组长度
    • 右指针right遍历数组,将元素加入窗口
    • 当窗口和window > target时,左指针右移,缩小窗口
    • 当窗口和window == target时,更新最长子数组长度
  4. 结果计算

    • 最少操作次数 = 数组长度 - 最长子数组长度
    • 如果未找到符合条件的子数组,返回-1

关键技巧说明

  1. 问题转换

    • 将"两端移除元素使剩余和为x"转换为"寻找中间子数组和为total-x"
    • 这样可以利用滑动窗口高效求解,避免枚举所有两端移除的组合
  2. 滑动窗口模型

    • 右指针扩展窗口,左指针收缩窗口,确保窗口和不超过target
    • 窗口和等于target时,记录窗口长度,寻找最长可能的子数组
  3. 边界条件处理

    • 提前处理总和不足或等于x的情况,避免无效计算
    • 确保左指针不超过右指针,避免越界

复杂度分析

  • 时间复杂度:O(n),其中n是数组的长度。左右指针各遍历数组一次,总操作次数不超过2n。
  • 空间复杂度:O(1),仅使用常数级额外空间。

示例详解

以输入nums = [1,1,4,2,3], x = 5为例:

  1. total = 1+1+4+2+3 = 11n = 5
  2. target = 11 - 5 = 6
  3. 初始化window=0, left=0, ans=-1
  4. right=0
    • window += 1 → window=1
    • window <= 6,不收缩窗口
    • window != 6,不更新ans
  5. right=1
    • window += 1 → window=2
    • window <= 6,不收缩窗口
    • window != 6,不更新ans
  6. right=2
    • window += 4 → window=6
    • window == 6,更新ans = max(-1, 2-0+1)=3
  7. right=3
    • window += 2 → window=8
    • window > 6,进入循环:
      • window -= nums[0]=1 → window=7left=1
      • window > 6,继续循环:
        • window -= nums[1]=1 → window=6left=2
    • window == 6,更新ans = max(3, 3-2+1)=2?不,计算错误。正确计算应为:right=3时,窗口是left=2right=3,长度为3-2+1=2,但之前ans=3,所以ans还是3
  8. right=4
    • window += 3 → window=9
    • window > 6,进入循环:
      • window -= nums[2]=4 → window=5left=3
      • window < 6,退出循环
    • window=5 != 6,不更新ans
  9. 最终ans=3,最少操作次数为5-3=2,符合示例输出。

总结

该解法通过巧妙的问题转换,将两端移除元素的问题转化为中间子数组求解问题,利用滑动窗口高效找到最长子数组,从而得到最少操作次数。这种思路在处理数组两端操作的问题时非常有效,能够将时间复杂度从O(n²)降低到O(n)。关键在于理解问题的本质,并找到合适的转换方式,再结合滑动窗口等算法技巧解决问题。