【剑指 Offer II】和大于等于 target 的最短子数组

三种办法,这里都说一下。

暴力

直接每一个字串全部加起来,然后算第一个大于 target 的下标减去刚开始加的下标,得到长度之后开始比较找最小。

class Solution {
public:
    int minSubArrayLen(int target, vector<int>& nums) {
        int ans = INT_MAX;
​
        for(int i = 0;i<nums.size();i++)
        {
            int sum = 0;
            for(int j = i;j<nums.size();j++)
            {
                sum+=nums[j];
                if(sum >= target)
                {
                    ans = min(ans,j-i+1);
                }
            }
        }
        return ans == INT_MAX?0:ans;
    }
};

其实你要是跟我说这个不超时的话我是不信的。

每次 LeetCode 针对这种暴力都是直接上很大一串代码的。


前缀和+二分

这里就可以用上前缀和了。

细说前缀和。

看起来好像很厉害的样子,实际上还算简单。

假设有一个数组 a ,这个数组里面是:2,3,1,2,4,3

那它的前缀和就是:0,2,5,6,8,12,15

第一个位置是 0 ,因为我们是从 1 开始加的

V[I] = V[I-1]+a[i-1];

有了这个前缀和,我们就可以继续往后写了。

-找位置-

C++里面有一个函数,叫做:lower_bound

JAVA里面叫做:Arrays.binarySearch

C#称之为:Array.BinarySearch

Python:bisect.bisect_left

其实它们都是一样的,就是找这一串数字里面第一个大于等于要找数字的位置。

返回的是一这个位置的迭代器,这里如果用上,如果熟悉的话会很简单,但是如果不熟悉,建议自己写一个二分。

其实也简单,找一个 >= tar 的元素,那就把判断条件改为 >= tar,其他的不变即可。

重新说回前缀和。

我们要找第一个元素它大于等于 tar 的区间长度,那我们就在前缀和里面找 >= tar 的元素即可。

假设:tar = 7

第一个元素是 2 ,它的前缀和是 0 ,那它要找的 tar就是 0+7 = 7,tar 就是它的前缀和+它本身, 区间应该是: 2,3,1,2,加起来结果是8,最后 2 的下标是 4 ,所以这段区间的长度就是 4 - 1 + 1。

这里的 -1 是因为是从第一个元素开始找的, +1 是因为别忘了这个元素也是要算在长度里面的。

第二个元素就跟第一个元素不一样了。

它是 3 ,它的前缀和是 2 ,所以它要找的 tar 是 2 + 7 = 9,为什么不一样?因为从这里开始算的话他是已经 +2 的结果(加了前面一个值),那它找的结果也要加前面的值,所以它找的是区间是:3,1,2,4,算下来也是 4 .

这里直接跳到 4 的位置。

4 的前缀和是 8 ,也就是说它要找的 tar 值是 8 + 7 = 15,那往后找找到结尾的 15,此时长度是 5 - 4 + 1 = 2,再将它与前面的比较即可。

思路说完了,来看一下代码怎么写。

第一种,调用 lower_bound:

class Solution {
public:
    int minSubArrayLen(int target, vector<int>& nums) {
        vector<int> v(nums.size()+1,0);
        int ans = INT_MAX;
​
        for(int i = 1;i<=nums.size();i++)
        {
            v[i] = v[i-1] + nums[i-1];
        }
​
        for(int i = 1;i<=nums.size();i++)
        {
            int tar = v[i-1]+target;
            auto pos = lower_bound(v.begin(),v.end(),tar);
            if(pos != v.end())
            ans = min(ans, static_cast<int>((pos - v.begin()) - (i - 1)));
        }
​
        return ans == INT_MAX?0:ans;
    }
};

static_cast<int> 就是强制类型转换,因为它是两个迭代器相减。

然后我们再自己写一个 lower_bound:

class Solution {
public:
    int minSubArrayLen(int target, vector<int>& nums) {
        vector<int> v(nums.size()+1,0);
        int ans = INT_MAX;
​
        for(int i = 1;i<=nums.size();i++)
        {
            v[i] = v[i-1] + nums[i-1];
        }
​
        for(int i = 1;i<=v.size();i++)
        {
            int tar = v[i-1]+target;
            int pos = my_lower_bound(v,tar);
            if(pos != -1)
            ans = min(ans,pos-i+1);
        }
        return ans == INT_MAX?0:ans;
    }
​
    int my_lower_bound(vector<int>& v,int tar)
    {
        int left = 0,right = v.size()-1;
​
        if(v[right] < tar)
        return -1;
        while(left < right)
        {
            int mid = (left+right)>>1;
            if(v[mid] >= tar)
            {
                right = mid;
            }
            else
                left = mid+1;
        }
        return right;
    }
};

滑动窗口

直接上代码吧...这个就是一直 + ,+ 到大于 tar 的时候就可以算区间长度,然后 区间右移,继续 + ,其实思路跟前缀和有那么点像。

class Solution {
public:
    int minSubArrayLen(int target, vector<int>& nums) {
        int start = 0,end = 0;
        int ans = INT_MAX;
        int sum = 0;
​
        while(end < nums.size())
        {
            sum+=nums[end];
            while(sum >= target){
                ans = min(ans,end-start+1);
                sum -= nums[start];
                start++;
            }
            end++;
        }
        return ans == INT_MAX ? 0 : ans;
    }
};

拿这个举例:2,3,1,2,4,3

start 和 end 都从 0 开始,一直加到 大于 tar(7),区间是:2,3,1,2,end 在 2 的位置,然后end - start +1 注意 start 还在 0 没动,所以它的长度是 4 ,然后我们要做的就是将这个区间重新变为小于 tar 的状态,start - end 区间里面,end 可能很大,就让 start 一直往后移,每一次移动都减去start,直到它小于 tar,然后再继续加...

如果还看不懂也必要紧,这题 LeetCode 考点是 用 N*logN,也就是用第二种办法...

滑动窗口多看几遍,实在看不懂可以走读一下,加强自己的代码阅读能力。


评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值