题目链接: leetcode.cn/problems/mi…
题目描述:
给你一个整数数组 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
解析
给定一个整数数组 nums
和一个整数 x
。每次操作,你可以从数组的左边或右边移除一个元素,并将该元素的值从 x
中减去。需要注意的是,每次操作后都要修改数组以供后续操作使用。
如果可以通过一系列操作将 x
减到恰好为 0
,则返回所需的最小操作数;否则返回 -1
。
题目还是挺好理解的,但是可能读完题目之后没有什么思路,因为这题如果正面硬刚的话,结果展现的形式有多种,第一种就是可能全部为数组左边部分的数,第二种为全为数组右边的数,第三种就是左右数都有,第三种也是最难解决的情况,因为数组的左边和右边是分开的,不连续,很难找规律,不好优化。
正难则反试试,如果我们将数组的所有数都加在一起求一个总和 sum
,减去 x
,所得一个 target
, 我们的想法是,你让我求两边的数加起来等于 x
,那我把数组中间的所有数加起来等于总和减去 x
是不是就行了?题目的要求是找到最小操作数,也就是数量最少的个数加起来等于 x
, 所以我们转换思路之后求得是中间最长连续子数组加起来等于 target
。最后再返回一个数组长度减去一个子数组长度就是我们需要的结果。如下图,我们求得 target
为 12 之后只需要找出数组的连续子数组和为 12 ,问题就解决了。
那我们的问题就转化成了如何求最长连续子数组的和等于 target
,我们暴力解法就是两层循环,外面一层 left
,里面一层 right
,让一个 cur += nums[right]
,如果 cur
等于 target
的话就记录他的长度,然后返回一个最大的长度就可以了。但是这样肯定是超时的,其实我们在暴力解法中可以找到规律,如下图,如果我们从 left = 0 开始加到 right = 3 时,cur = 14,这时暴力解法就是 left = 1,然后 right = 1,在开始从头遍历。真的需要从头遍历吗? 其实是浪费时间的,因为我们上一步已经求出来区间 0-3 的和了,right = 3 的时候和大于 target。这不就是滑动窗口?
滑动窗口的话直接四步走
①进窗口:
right ++, cur += nums[right]
②出窗口:
判断 cur > target ? 如果大于,就 cur -= nums[left],然后 left++
③更新结果
判断当前连续子数组的长度是否大于之前的长度
④返回结果:
return nums.length - 连续子数组.length;
代码
class Solution {
public int minOperations(int[] nums, int x) {
int sum = 0;
for (int i : nums) sum += i;
//转换目标值
int target = sum - x;
if (target < 0) return -1;
int len = -1;
for (int left = 0, right = 0,cur = 0; right < nums.length; right++) {
//进窗口
cur += nums[right];
//出窗口
while (cur > target) cur -= nums[left++];
//更新结果
if (cur == target) len = Math.max(len, right - left + 1);
}
//返回值,此处需要判断长度是否为-1,因为我们默认值给的是-1,如果是默认值的话就说明无结果
return len == -1?len:nums.length - len;
}
}