1.题目分析
leedcode题目链接:https://leetcode.cn/problems/minimum-operations-to-reduce-x-to-zero/submissions/
给你一个整数数组 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
2.算法原理
1.问题转化:
就本题来讲,如果我们直接从正面去写,减减数组左边,减减数组右边,将会非常非常麻烦,有非常多的情况需要考虑。这里我们来学习一个思路,如果问题从正面难以解决,我们就尝试从反面入手。
如图,线段表示一个数组,正面做的思路就是,找到两个区间a
和b
,让a + b = x
即可,但是这样实在太麻烦了。正难则反,我们来看一下a
和b
中间的那段区域,它是一段连续的区域,它满足的条件是:长度等于数组总长度减去a
段和b
段的长度。我们用sum_windows
来表示中间区域的数据和,sum_nums
表示数组所有数据的和,则它满足sum_windows = sum_nums - x
。
将正面问题转化:找出最长的子数组的长度,所有元素的和正好等于sum_nums - x
。然后让数组的总长度n
减去这个子数组的长度即可。
2.利用滑动窗口解题:
随便以一个数组为例:
让left
和right
先都指向下标为0的位置,然后让rigth++
,找最佳的位置。
设一个目标值target = sum_nums - x
,当right
移动到一个位置后,发现sum_windows >= target
,此时,终止进窗口的操作,开始判断sum_windows
是等于target
还是大于target
。
如果是等于,就更新结果,然后出窗口;如果是大于,那就不更新结果,直接出窗口。
出窗口的操作就是让left
向后移,移动left
之前要减去从窗口中去掉的那个数据的值sum_windows -= num[left]
。直到sum_windows
小于target
后,再停止出窗口操作。然后重复进窗口操作。
3.编写代码
1.写法1:
class Solution {
public:
int minOperations(vector<int>& nums, int x)
{
int left = 0, right = 0;
int n = nums.size();
int ret = INT_MAX;
int sum_nums = 0; // 表示整个数组元素的和
for(int i = 0; i < n; i++)
sum_nums += nums[i];
int target = sum_nums - x; // 目标值
int sum_windows = 0; // 窗口中子数组元素的和
while(right < n && left <= right)
{
while(right < n && sum_windows < target) // 进窗口
{
sum_windows += nums[right];
right++;
}
while(left <= right && sum_windows >= target) // 出窗口
{
if(sum_windows == target) // 判断,是否要更新结果
ret = min(ret, n - (right - left));
sum_windows -= nums[left];
left++;
}
}
return ret == INT_MAX? -1 : ret;
}
};
2.写法2:
class Solution {
public:
int minOperations(vector<int>& nums, int x)
{
int sum = 0;
for(int a : nums) sum += a;
int target = sum - x;
// 细节问题
if(target < 0) return -1; // 题中说所有数据都大于等于1
int ret = -1;
for(int left = 0, right = 0, tmp = 0; right < nums.size(); right++)
{
tmp += nums[right]; // 进窗口
while(tmp > target) // 判断
tmp -= nums[left++]; // 出窗口
if(tmp == target) // 更新结果
ret = max(ret, right - left + 1);
}
if(ret == -1) return ret;
else return nums.size() - ret;
}
};