题目链接
题目描述
给你一个整数数组 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
代码解析
-
初始化与边界处理:
- 计算数组总和
total
和长度n
- 如果
total < x
,直接返回-1(无法通过移除元素使和为x) - 如果
total == x
,返回n(需要移除所有元素)
- 计算数组总和
-
目标转换:
- 计算目标子数组和
target = total - x
,我们需要找到和为target
的最长子数组
- 计算目标子数组和
-
滑动窗口维护:
window
记录当前窗口的和,left
为左指针,ans
记录最长子数组长度- 右指针
right
遍历数组,将元素加入窗口 - 当窗口和
window > target
时,左指针右移,缩小窗口 - 当窗口和
window == target
时,更新最长子数组长度
-
结果计算:
- 最少操作次数 = 数组长度 - 最长子数组长度
- 如果未找到符合条件的子数组,返回-1
关键技巧说明
-
问题转换:
- 将"两端移除元素使剩余和为x"转换为"寻找中间子数组和为total-x"
- 这样可以利用滑动窗口高效求解,避免枚举所有两端移除的组合
-
滑动窗口模型:
- 右指针扩展窗口,左指针收缩窗口,确保窗口和不超过target
- 窗口和等于target时,记录窗口长度,寻找最长可能的子数组
-
边界条件处理:
- 提前处理总和不足或等于x的情况,避免无效计算
- 确保左指针不超过右指针,避免越界
复杂度分析
- 时间复杂度:O(n),其中n是数组的长度。左右指针各遍历数组一次,总操作次数不超过2n。
- 空间复杂度:O(1),仅使用常数级额外空间。
示例详解
以输入nums = [1,1,4,2,3], x = 5
为例:
total = 1+1+4+2+3 = 11
,n = 5
target = 11 - 5 = 6
- 初始化
window=0
,left=0
,ans=-1
- right=0:
window += 1 → window=1
window <= 6
,不收缩窗口window != 6
,不更新ans
- right=1:
window += 1 → window=2
window <= 6
,不收缩窗口window != 6
,不更新ans
- right=2:
window += 4 → window=6
window == 6
,更新ans = max(-1, 2-0+1)=3
- right=3:
window += 2 → window=8
window > 6
,进入循环:window -= nums[0]=1 → window=7
,left=1
window > 6
,继续循环:window -= nums[1]=1 → window=6
,left=2
window == 6
,更新ans = max(3, 3-2+1)=2
?不,计算错误。正确计算应为:right=3
时,窗口是left=2
到right=3
,长度为3-2+1=2
,但之前ans=3
,所以ans
还是3
- right=4:
window += 3 → window=9
window > 6
,进入循环:window -= nums[2]=4 → window=5
,left=3
window < 6
,退出循环
window=5 != 6
,不更新ans
- 最终
ans=3
,最少操作次数为5-3=2
,符合示例输出。
总结
该解法通过巧妙的问题转换,将两端移除元素的问题转化为中间子数组求解问题,利用滑动窗口高效找到最长子数组,从而得到最少操作次数。这种思路在处理数组两端操作的问题时非常有效,能够将时间复杂度从O(n²)降低到O(n)。关键在于理解问题的本质,并找到合适的转换方式,再结合滑动窗口等算法技巧解决问题。