【滑动窗口】leetcode1658:将x减到0的最小操作数

目录

一.题目描述

二.思路分析

三.代码编写


一.题目描述

将x减到0的最小操作数

【滑动窗口】leetcode1658:将x减到0的最小操作数_第1张图片

 题目要求我们在数组的两端不断地取值,使得取出的数之和等于x,问我们最少需要取几次。

也就是说,在两边取两个区间,使得这两个区间的之和等于x,求这两个区间长度之和最小是多少。

两个区间研究起来比较麻烦,正难则反,我们可以转化为研究两侧区间所围的区间,那么只需找到满足条件的最长区间,这个条件就是区间之和为sum-target(sum是整个数组的和),结果就是数组的长度减去求得的len。

二.思路分析

两层for循环暴力枚举所有的区间,找到符合条件的区间,通过比较得到最长的区间长度,从而得到结果。代码就不具体实现了。

使用滑动窗口优化,首先要证明right不需要回退

【滑动窗口】leetcode1658:将x减到0的最小操作数_第2张图片

 right不断向后移动,当移动到图中位置时,区间之和total>= sum-x, right停了下来,(潜台词是[left, right - 1]区间的和是小于total的)。此时应该判断total是否等于sum-x, 如果是就更新结果。隐含条件:。

【滑动窗口】leetcode1658:将x减到0的最小操作数_第3张图片

按照暴力枚举策略 ,left向后移动一步,right回退。但是最终right还是会回到原处。因为图中大括号对应的区间之和是不满足要求的,right会一直向右移动。故right没有必要往回退,留在原地即可。

【滑动窗口】leetcode1658:将x减到0的最小操作数_第4张图片

 那么此时的区间之和是否小于sum-x呢?不确定,因为可能跳过的是一个很小的数,区间之和仍然>=sum-x,此时right没有必要向后枚举了,因为区间再往后扩展,和肯定大于sum-x了。所以让left继续向后移动,直到total 

所以判断是一个循环的逻辑,不能用if简单地只判断一次。

三.代码编写

【滑动窗口】leetcode1658:将x减到0的最小操作数_第5张图片

 其中更新结果时total应该等于target,故可以再判断条件成立,出窗口之前判断total是否等于target,如果等于就更新结果。

class Solution {
public:
    int minOperations(vector& nums, int x) 
    {
        int sum = 0;
        for (auto e : nums)
        {
            sum += e;
        }

        int target = sum - x;

        int n = nums.size();
        int len = -1;
        int left = 0, right = 0;
        int total = 0;//统计窗口内所有数的和
        while (right < n)
        {
            //进窗口
            total += nums[right];

            //判断
            while (total >= target)
            {
                //更新结果
                if (total == target)
                {
                    len = max(len, right - left + 1);
                }
                //出窗口
                total -= nums[left++];
            }

            right++;
        }

        return len == -1 ? -1 : n - len;
    }
};

当你怀着激动的心情提交代码时,发现到第6个用例就挂了

 【滑动窗口】leetcode1658:将x减到0的最小操作数_第6张图片

 这种错误提示,八成是越界访问了。模拟代码的执行逻辑,最后发现确实是left越界了。原因在于target是个负数,当right移动到n-1位置,left移动到n位置时,total恰好等于0,但还是大于等于target,故还要出窗口,此时left就越界访问报错了。所以targe小于等于0时需要特殊处理。

当target小于0时,直接返回-1,因为所有数都是正整数,不可能有结果。当target=0时,说明sum=x,直接返回数组长度即可。

class Solution {
public:
    int minOperations(vector& nums, int x) 
    {
        int sum = 0;
        for (auto e : nums)
        {
            sum += e;
        }

        //越界情况单独处理
        int target = sum - x;
        if (target < 0)
        {
            return -1;
        }

        if (target == 0)
        {
            return nums.size();
        }
        
        int n = nums.size();
        int len = -1;
        int left = 0, right = 0;
        int total = 0;//统计窗口内所有数的和
        while (right < n)
        {
            //进窗口
            total += nums[right];

            //判断
            while (total >= target)
            {
                //更新结果
                if (total == target)
                {
                    len = max(len, right - left + 1);
                }
                //出窗口
                total -= nums[left++];
            }

            right++;
        }

        return len == -1 ? -1 : n - len;
    }
};

事实上,还可以将更新结果的操作放在while循环外面,这时只需把循环的条件改为total>target。当程序通过while循环的逻辑后,total要么小于target,要么等于target。此时再进行判断,如果等于就更新结果,这样的逻辑会更加简单,而且此时target=0的情况也不会越界访问了。

class Solution {
public:
    int minOperations(vector& nums, int x) 
    {
        int sum = 0;
        for (auto e : nums)
        {
            sum += e;
        }

        //越界情况单独处理
        int target = sum - x;
        if (target < 0)
        {
            return -1;
        }

        int n = nums.size();
        int len = -1;
        int left = 0, right = 0;
        int total = 0;//统计窗口内所有数的和
        while (right < n)
        {
            //进窗口
            total += nums[right];

            //判断
            while (total > target)
            {
                //出窗口
                total -= nums[left++];
            }
            //更新结果
            if (total == target)
            {
                len = max(len, right - left + 1);
            }

            right++;
        }

        return len == -1 ? -1 : n - len;
    }
};

时间复杂度O(n)

你可能感兴趣的:(算法,数据结构)