【算法学习】-【滑动窗口】-【将 x 减到 0 的最小操作数】

LeetCode原题链接: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、解题思路

如果本题按照示例所给的过程进行枚举的话,情况其实会很复杂且不好控制,所以我们就需要将问题转换一下,这也是本文想记录和分享的重要解题方法,那就是 "正难则反"

根据题目要求,我们需要通过从左右两端 开始把其中左边或右边 的数移除而判断是否能找到一个使x减为0的最小操作数(即要从两端开始找到几个数,使这个几个数的和加起来等于x ,且所找的这几个数的数量要最小 );
反过来 ,也就是说,需要在相对中间 的位置判断是否能找到一段最长的连续的数 (子数组)的和 等于 数组所有元素总和 - x

若能找到,那么由于中间找到的连续子数组是最长 的,此时用元素总个数减去中间的连续子数组的长度得到的就是所找的能使x减为0的最小操作数。

如示例3:
(1)"正着"操作

最佳解决方案是移除后三个元素和前两个元素(总共 5 次操作),将 x 减到 0 。

(2)"反着"操作

(PS:虽说是找到相对中间位置的连续最长子数组,但根据滑动窗口算法的执行过程本质还是从左端开始,是对暴力枚举的一种优化。)

综上所述 ,原问题就可以转换为:找一段长度最长的子数组且子数组的和等于一个值target ,此类问题看上去就非常的 "滑动窗口"。算法的具体实现和笔者另一篇同知识点的题解很类似:【算法学习】-【滑动窗口】-【长度最小的子数组】,第一次使用滑动窗口算法的朋友们可以先看看那篇文章。

下面简单说明一下算法实现的基本步骤:

(1)进窗口

创建两个指针begincur,初始都指向第一个元素;变量sum来判断当前总和是否等于 目标值target若小于 ,则进窗口,即让sum加上当前元素:sum+=nums[begin]若大于等于 ,则进行下一步
(2)出窗口和更新数据

出窗口前,若总和sum的值等于 目标值 targetcur - begin为本次枚举所得的子数组的长度,将其与上一次枚举所得的结果进行对比,取较大者len = len > cur - begin ? len : cur - begin。需要注意的是,此时不会在本次循环(等于目标值时)出窗口,而是再让下一个元素进窗口而大于目标值target后,才出窗口而准备进行下一次枚举。

2、具体代码

c 复制代码
	//找到中间最长连续子数组
	int maxSubArrayLen(int target, vector<int>& nums) 
    {
    	//由于是间接求,需要注意target可能小于零
        if(target < 0)
        {
            return 0;
        }
        
        int len = 0;
        int sum = 0;
        int begin = 0;
        int cur = 0;
        while(cur < nums.size())
        {        
            //进窗口
            sum += nums[cur++];

            //出窗口和更新
            if(sum == target)
            {
                len = len > cur - begin ? len : cur - begin; 
            }
            while(sum > target)
            {            
                sum -= nums[begin++];                     
            }

        }       
        return len;               
    }

	//最小操作数
    int minOperations(vector<int>& nums, int x) 
    {
        int sum = 0;
        for(auto e : nums)
        {
            sum += e;
        }

        if(sum - x == 0)
        {
            return nums.size();
        }
        
        int ret = maxSubArrayLen(sum - x, nums);
        if(ret == 0)
        {
            return -1;
        }
        
        return nums.size() - ret;
    

看完觉得有觉得帮助的话不妨点赞收藏鼓励一下,有疑问或看不懂的地方或有可优化的部分还恳请朋友们留个评论,多多指点,谢谢朋友们!🌹🌹🌹

相关推荐
海琴烟Sunshine2 小时前
leetcode 383. 赎金信 python
python·算法·leetcode
say_fall3 小时前
C语言编程实战:每日刷题 - day2
c语言·开发语言·学习
cynicme8 小时前
力扣3228——将 1 移动到末尾的最大操作次数
算法·leetcode
熬了夜的程序员8 小时前
【LeetCode】109. 有序链表转换二叉搜索树
数据结构·算法·leetcode·链表·职场和发展·深度优先
随意起个昵称8 小时前
【递归】二进制字符串中的第K位
c++·算法
mjhcsp9 小时前
C++ 循环结构:控制程序重复执行的核心机制
开发语言·c++·算法
立志成为大牛的小牛9 小时前
数据结构——四十一、分块查找(索引顺序查找)(王道408)
数据结构·学习·程序人生·考研·算法
蒙奇D索大9 小时前
【计算机网络】[特殊字符] 408高频考点 | 数据链路层组帧:从字符计数到违规编码,一文学透四大实现方法
网络·笔记·学习·计算机网络·考研
遇印记9 小时前
蓝桥java蜗牛
java·学习·蓝桥杯