1658. 将 x 减到 0 的最小操作数
1658. 将 x 减到 0 的最小操作数
难度中等
给你一个整数数组 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
函数签名:
func minOperations(nums []int, x int) int
分析
问题稍微绕了一点,等价于找到和为定值 sum-x
的最长连续子数组 ,如果找到了,结果就是总长度减去这个子数组的长度。
程序第一部分逻辑如下:
func minOperations(nums []int, x int) int {
target := -x
for _, v := range nums {
target += v
}
if target == 0 {
return len(nums)
}
if target < 0 {
return -1
}
// calMaxSubSizeWithSum 找到和为 target 的最长子数组的长度,不存在返回 -1
maxSize := calMaxSubSizeWithSum(target, nums)
if maxSize == -1 {
return -1
}
return len(nums) - maxSize
}
找到和为定值的最长连续子数组,这是一个经典的问题。
现在来看 calMaxSubSizeWithSum
函数,从朴素到最优的实现如下:
朴素解法
func calMaxSubSizeWithSum(target int, nums []int) int {
res := -1
for i := range nums {
sum := 0
for j := i; j < len(nums); j++ {
sum += nums[j]
if target == sum && j-i+1 > res {
res = j - i + 1
}
}
}
return res
}
时间复杂度 O(n^2)
,空间复杂度 O(1)
,超时了。
前缀和 + 哈希表
借助前缀和技巧,哈希表辅助,空间换时间:
func calMaxSubSizeWithSum(target int, nums []int) int {
res := -1
// 键为前缀和,值为前缀末尾的索引
dic := map[int]int{0: -1}
sum := 0
for i, v := range nums {
sum += v
if j, ok := dic[sum-target]; ok && i-j > res { // 子数组 [j+1:i] 的和为 target,长度大于 res
res = i - j
}
// 这里不用判断 sum 是否已经存在于dic,
// 因为数组里都是正整数,sum是不断递增的,dic 里肯定不存在
// 如果数组元素有负数,则需要判断,为了保持子数组最长,存入的前缀越短越好,判断如果已经有当前 sum 则不更新前缀末尾索引
dic[sum] = i
}
return res
}
时间复杂度 O(n)
,空间复杂度 O(n)
。
滑动窗口
基于双指针的滑动窗口解法。
用 left 和 right 两个指针代表窗口的左右边界,一开始两个指针都指向数组起始位置。
另用一个变量 sum 维护窗口里的元素和, 如果 sum < target,右边界右移,sum == target, 则更新结果且右边界右移;sum > target 则左指针右移。
如果数组中有负数,这个方法不能用,移动左右指针的贪心策略就错了。
总体来说 left 和 right 最坏情况下都遍历了一遍数组,复杂度是 O(n)
的。
func calMaxSubSizeWithSum(target int, nums []int) int {
res := -1
left, right, sum := 0, 0, 0
for right < len(nums) {
sum += nums[right]
right++
for sum > target && left < right {
sum -= nums[left]
left++
}
if target == sum && right-left > res {
res = right-left
}
}
return res
}
时间复杂度 O(n)
,空间复杂度 O(1)
。