专题二 - 滑动窗口 - 1658. 将 x 减到 0 的最小操作数 | 中等难度

该文介绍了如何用滑动窗口方法解决LeetCode题目1658,通过转化问题求解最大连续子数组和等于x的限制,展示了从逆向思维和算法原理的角度分析解题过程。
摘要由CSDN通过智能技术生成

在这里插入图片描述

1658. 将 x 减到 0 的最小操作数 | 中等难度

1. 题目详情

给你一个整数数组 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

1. 原题链接

leetcode 1658. 将 x 减到 0 的最小操作数

2. 基础框架

● Cpp代码框架

class Solution {
public:
    int minOperations(vector<int>& nums, int x) {

    }
};

2. 解题思路

1. 题目分析

( 1 ) (1) (1) 本题要求 一个数组nums,其中的元素都大于等于1;每次从数组nums最左边或最右边移除一个数,如果移除元素的和可以恰好等于给定的x,求最小的操作数;如果怎样移除都不等于x就返回-1。
这里的移除操作实际上指的不是真正的删除,而是为了说明已经移除的元素不能再被移除(被使用)了。
( 2 ) (2) (2) 如果直接按照题目的思路进行模拟左右移除元素,我们会发现每一次移除具体是移除左边的元素还是移除右边的元素,这不好进行判断,代码也不好编写,思路也不清晰。

( 3 ) (3) (3) 正难则反:正向思考如何进行移除操作不好想,于是我们可以想想反向。
按照题目的要求,我们需要找的是左边和右边移除元素的个数最小且加和等于x。给定的数组是不变的,所以所有元素的和是确定的,设为sumNums,那么问题就可以转化为求中间区间元素的个数(长度)的最大值且加和sum == sumNums - x(即sum + x == sumNums)。

sumNumber - x是确定的值,整体设为target,那么上述转化又可以表示为:求nums最大连续子数组且加和sum == target。怎么样,熟悉不熟悉?滑动窗口来了。
在这里插入图片描述

2. 算法原理

( 1 ) (1) (1) 转化为求nums最大连续子数组且加和等于target的问题后,依然先思考如何暴力枚举解题,为滑动窗口出现铺路:
left为起始位置,right依次遍历,判断[left,right]范围内的子数组加和sum

如果sum < target,即sum < sumNums - x,说明可以继续加入新元素,right++

如果sum > target,即sum > sumNums - x,说明当前right位置元素加入后,加和sum已经不可能满足关系sum == target了,而数组nums中的元素都是nums[i] >= 1right及其之后的所有元素都会导致sum大于target,所以right没有后移的必要了,此时left可以右移1位,right回退到left位置继续遍历,重新计算sum

如果sum == target,即sum == sumNums - x,正好满足要求,更新对应的len(表示最大连续子数组的长度),之后right后面的所有元素也就不用再遍历了,因为数组nums中的元素都是nums[i] >= 1,所以再新加入元素就一定会使sum大于target,没有必要,之后操作同sum > target的末尾处理。

( 2 ) (2) (2) 在分析暴力解法的时候,每次以left为起始位置遍历子数组时,right都会回退到left位置,重新开始遍历和计算加和sum。能不能right不回退,而只是left右移呢?答案当然是:能!
为什么呢?
假设以left为起始位置开始遍历数组numsright在某个位置满足了sum >= target,之后left不右移,right也先不着急回退。我们定格此处分析:

因为right位置的元素加入之后才出现sum >= target的情况,所以[left, right - 1]范围内的加和sum1一定是小于target的,那么[left + 1, right - 1]范围内的子数组加和sum2又小于sum1,所以sum2也一定小于target

那么,对于right来说,有必要回退到left + 1位置重新遍历吗?
很明显没有必要:因为即使right重新遍历,那么从[left + 1, right - 1]区间一定是总是满足加和小于target的,那么right一定又会再次来到right回退之前的位置([left + 1, right]子数组加和可能恰好等于target,也可能大于target,也可能小于target),无论是那种情况,right都会至少来到上一次回退时候的位置。所以right可以不回退,而是继续在本位置(加和可能等于或大于target)或继续向后移动(加和小于target)。
( 3 ) (3) (3) 进行滑动窗口处理:

  1. 计算整个数组nums的加和sumNums;定义target = sumNums - x
  2. 特殊判断target是否小于0:如果小于0,说明数组加和小于x,一定不存在满足题意的条件,则直接返回-1;
  3. 初始化:定义记录最大连续子数组长度len = -1;定义滑动窗口边界left = 0, right = 0;定义连续子数组加和sum = 0
  4. 进窗口:新元素进入,sum += nums[right]
  5. 判断:sum > target时,(使sum趋近target,需要出元素)
  6. 出窗口:left左移从滑动窗口划出,sum -= nums[left], left++,直到sum <= target为止;
  7. 更新结果:只有在sum == target时,才更新len,保存最大的连续子数组的长度;
  8. right右移1位,为下一个元素进窗口做准备;
  9. 因为题目要的结果是在满足加和恰好等于x的左右两边元素的最小长度(个数),我们得到的是连续子数组的最大长度,所以返回nums.size() - len或-1;
    ( 4 ) (4) (4)
    在这里插入图片描述

3. 时间复杂度

暴力枚举 O ( n 2 ) O(n^2) O(n2)

滑动窗口 O ( n ) O(n) O(n)

leftright同向移动,直到结束,遍历一遍数组。

3. 代码实现

class Solution {
public:
    int minOperations(vector<int>& nums, int x) {
        int sumNums = 0;
        for(auto e : nums) sumNums += e;
        int target = sumNums - x;
        if(target < 0) return - 1;
        int l = 0, r = 0, sum = 0, n = nums.size(), len = -1;
        while(r < n){
            sum += nums[r];
            while(sum > target) sum -= nums[l++];
            if(sum == target) len = max(len, r - l + 1);
            r++;
        }
        return len == -1 ? len : n - len;
    }
};

4. 知识与收获

( 1 ) (1) (1) 解决本题关键的一点就是对问题进行转化,当一道题正着按照题目的要求十分难做、没有思路时,往往考虑问题的反面时能找到好想的思路,所谓正难则反就是这样。
( 2 ) (2) (2) 滑动窗口解题,对于具体的题目需要注意到不同题的细节处理,本题中所有元素都是大于等于1的,不会出现负数。sumNums - x可能出现负数情况,x也可能是0,此时最小操作数就是0,即移除0个元素。


T h e The The E n d End End

  • 12
    点赞
  • 15
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

re怠惰的未禾

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值