滑动窗口(4)_将x减到0的最小操作数

个人主页:C++忠实粉丝
欢迎 点赞👍 收藏✨ 留言✉ 加关注💓本文由 C++忠实粉丝 原创

滑动窗口(4)_将x减到0的最小操作数

收录于专栏【经典算法练习
本专栏旨在分享学习算法的一点学习笔记,欢迎大家在评论区交流讨论💌

目录

1. 题目链接:

2. 题目描述 :

3. 解法 :

    解法一(暴力枚举) :

    算法思路 :

    具体步骤 :

    代码展示 :

    结果分析 :

    对暴力算法的反思与优化

    解法二(滑动窗口) :

    算法思路 :

    图解流程 :

    代码展示 :

    结果分析 :


1. 题目链接:

OJ链接:将x减到0的最小操作数

2. 题目描述 :

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

3. 解法 :

    解法一(暴力枚举) :

    算法思路 :

我们可以发现这道题如果说按照题目的意思来写将会变得非常困难!x依次从开始和结尾取数相减,直到x等于0.那具体取了几个开头元素几个结尾元素我们不得而知,所以这里我们需要对题目进行转化: 找出最长的子数组的长度(len),所有元素的和正好等于 sum - x(这里的sum就是整个数组的和),我们最后的结果就是n-len
通过我们这样一转化,我们就可以使用暴力枚举,找出最长子数组的长度!

    具体步骤 :

  1. 用两层循环遍历整个数组
  2. 用tmp记录两个指针区域见的总和
  3. 让右边的指针一直++,直到tmp >= target(sum - x),后面就不需要再遍历了,因为数组中的数>=1
  4. 如果此时tmp == target,记录下此时的最长子数组的长度
  5. i++,j=i.重复上述步骤

    代码展示 :

class Solution {
public:
    int minOperations(vector<int>& nums, int x) {
        int sum = 0;
        for(auto s : nums) sum += s;
        int n = nums.size(), ret = -1, target = sum - x;
        //x == sum 的情况
        if(target == 0) return n;
        //小优化
        if(target < 1 && target != 0) return ret;
        for(int i = 0; i < n; i++)
        {
            int tmp = 0;
            for(int j = i; j < n; j++)
            {
                tmp += nums[j];
                if(tmp == target) ret = max(ret, j - i + 1);
                if(tmp > target) break;

            }
        }
        if(ret == -1) return ret;
        else return n - ret;
    }
};

    结果分析 :

老样子分析一下题目所给的范围:

题目vector数组中数的范围是在[0, 10^5]之内,而我们暴力算法的时间复杂度为O(N^2),总的数据级别超过10^10,这样系统会判定超时!!!

    对暴力算法的反思与优化

10个暴力算法9个通过不了,但是暴力算法能给我们带来一些优化的思路:

就比如我们这道题,当我们暴力算法遍历到下面这种情况时:i++,j = i再重新继续遍历数组

我们就可以发现j其实不需要动,因为i++后,我们的tmp肯定减小,如果i++后,tmp还是大于target的话,我们只需让i一直++即可,因为就算让j重新等于i,那么同样会在j之前的位置大于target,我们这里就使用了滑动窗口让i,j之间的数永远小于target,这是一个动态维护的区间.

 

    解法二(滑动窗口) :

    算法思路 :

题目要求的是数组[左端 + 右端]两段连续的,和为x的最短数组,信息量稍微多一些,不易清理思路;我们就可以转化成求数组内一段连续的,和为sum(nums - x) 的最长数组(和暴力算法一样).此时,就是熟悉的[滑动窗口]问题了

    图解流程 :

1.转化问题: 求target = sum(nums) - x.如果target < 0,问题无解

2.初始化左右指针left = 0, right = 0(滑动窗口区间表示为(left, right),左右区间是否

开闭很重要,必须设定与代码一致),记录当前滑动窗口内数组和的变量sum = 0,

记录当前满足条件数组的最大区间长度ret = -1

3. 当right小于等于数组长度时, 一直循环:
        a. 如果sum < target, 右移右指针, 直至变量和大于等于target, 或右指针已经移到头
        b. 如果sum > target, 右移左指针, 直至变量和小于等于target, 或左指针已经移到头
        c. 如果经过前两步的左右移动使得sum == target, 维护满足条件数组的最大长度, 并

让下一个元素进入窗口

 

 

 

 

    代码展示 :

class Solution {
public:
    int minOperations(vector<int>& nums, int x) {
        int n = nums.size(), left = 0, right = 0, ret = -1;
        int sum = 0, tmp = 0;
        for(auto s : nums) sum += s;
        int target = sum - x;
        if(target < 0) return -1;
        while(right < n)
        {
            tmp += nums[right];
            if(tmp > target) 
                while(tmp > target) tmp -= nums[left++];
            if(tmp == target) ret = max(ret, right - left + 1);
            right++;
        }
        if(ret == -1) return ret;
        else return n - ret;
    }
};

 

    结果分析 :

时间复杂度: O(N)

空间复杂度: O(1)

滑动窗口在这道题中是一种效率很高的解决方法.

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值