【算法学习】-【滑动窗口】-【将 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;
    

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

相关推荐
_GR18 分钟前
每日OJ题_牛客_牛牛冲钻五_模拟_C++_Java
java·数据结构·c++·算法·动态规划
蜡笔小新星20 分钟前
Python Kivy库学习路线
开发语言·网络·经验分享·python·学习
ROBIN__dyc30 分钟前
表达式
算法
攸攸太上33 分钟前
JMeter学习
java·后端·学习·jmeter·微服务
无限大.34 分钟前
c语言实例
c语言·数据结构·算法
六点半88838 分钟前
【C++】速通涉及 “vector” 的经典OJ编程题
开发语言·c++·算法·青少年编程·推荐算法
@haihi1 小时前
冒泡排序,插入排序,快速排序,选择排序
数据结构·算法·排序算法
quaer1 小时前
Open-Sora全面开源?
开发语言·算法·机器学习·matlab·矩阵
Hello.Reader1 小时前
TopK算法在大数据重复数据分析中的应用与挑战
大数据·算法·数据分析
Ljubim.te1 小时前
Linux基于CentOS学习【进程状态】【进程优先级】【调度与切换】【进程挂起】【进程饥饿】
linux·学习·centos