题目
给你一个整数数组 nums 和一个整数 x 。每一次操作时,你应当移除数组 nums 最左边或最右边的元素,然后从 x 中减去该元素的值。请注意,需要 修改 数组以供接下来的操作使用。
如果可以将 x 恰好 减到 0 ,返回 最小操作数 ;否则,返回 -1 。
示例 1:
- 输入:nums = [1,1,4,2,3], x = 5
- 输出:2
- 解释:最佳解决方案是移除后两个元素,将 x 减到 0 。
题解
这个题目很好理解,就是删除nums的前缀与后缀数,使前缀与后缀的和为x。由于可能会有多种情况,因此选择一个数目最小的可能。以示例来说nums列表要想满足要求,有两种方法,一种是前缀为[1,1]后缀为[3],一种是前缀为空,后缀为[2,3]。显然后缀为[2,3]的方法只需要删除两个数,因此结果显然是方法2满足要求。
如下图所示。
显然该题目的核心是确定前缀与后缀的边界,这显然可以使用双指针来示解。现在又面临一个问题,双指针是从两边到中间遍历还是从一侧遍历。这里从两侧往中间遍历无法确定在lsum与rsum之后小于x的时候到底是走左侧指针还是右侧指针。这里有一个技巧,前缀指针从-1开始也表示lsum开始为0,后缀是初始为整个数组从0开始rsum为整个数据的和。那么如果确定前缀与后缀指针的执行过程呢。有以下三种情况:
- 情况1: 如果 lsum+rsum=x,这是一组满足答案的解,可以与之前已有的最小解进行对比;
- 情况2:如果 lsum+rsum>x说明和过大,需要将后缀指针right向右移动一个位置;
- 情况3:如果 lsum+rsum<x说明和过小,需要将 left前缀指针向右移动一个位置。
那么我们其实可以直接写代码了:
class Solution:
def minOperations(self, nums: List[int], x: int) -> int:
n=len(nums)
total=sum(nums)
#特殊情况1:如果nums之后小于x则肯定返回-1
if total < x:
return -1
#特殊情况2:nums之和正好为x,返回nums的长度
if total==x:
return n
#特殊情况3:如果nums所有元素均大于x则返回-1
flag=0
for num in nums:
if num<=x:
flag=1
break
if flag==0:
return -1
#滑动窗口
right=0
lsum=0
rsum=total
#初始结果长度取n+1
ans=n+1
#left滑动遍历,这就是直接第三种情况lsum+rsum<x,left右移动
for left in range(-1,n-1):
if left!=-1:
lsum+=nums[left]
#情况2如果lsum+rsum>x,right右移
while right<n and lsum+rsum>x:
#右移一位
rsum-=nums[right]
right+=1
#情况1:
if lsum+rsum==x:
ans=min(ans,(left+1)+(n-right))
return -1 if ans>n else ans
计算复杂性
- 时间复杂度: O ( n ) O(n) O(n),其中 n n n是数组 n u m s nums nums的长度。 l e f t left left和 r i g h t right right均最多遍历整个数组一次。
- 空间复杂度: O ( 1 ) O(1) O(1)。