算法作业第十三周(leetcode)——862. Shortest Subarray with Sum at Least K

这道题是一道挺有意思的题,也有一点难度。下面给出题目地址:

https://leetcode.com/problems/shortest-subarray-with-sum-at-least-k/

这道题的大意是给定一个整数序列,求一个最短的连续非空的子序列,使它的和至少为K,返回它的长度。 

这道题是一道对数字序列操作的题,而且是求连续子序列的和的问题。这种题的一个很自然的处理方法就是将序列中第一个数到第i个数的和存成数组sum。这样,任何一个连续子序列就可以用数组中两个数的差求出来。但是,很显然,只有这样处理是不行的。通过暴力循环,我们可以看出算法复杂度为O(n^2),而数组长度最大为50000,并不能满足。

后来看了一下别人的算法。发现它用了一种很特别的处理方法,就是关注第r个数结尾的最短的子序列,然后遍历r。这种倒过来保存的方法似乎比较适合滑动窗口。在遍历r的时候,我们知道这个子序列是以r结尾的,所以我们要找到一个最大的开头j,使j到r的子序列的和大于k。然后我们维护一个双端队列,队列中保存子序列可能的开头序号。在这里,我们先要明确双端队列中的两个数当i<j且sum[i]>sum[j],那么选择j作为子序列开头肯定是更好的解。因为,子序列更短而且差值更大。所以队列中的序号i是递增的,它们对应的sum[i]也是递增的 。

我们每遍历一次r,就应该往队列中插入r-1,就是前一个数。可以比较队列末尾的数值不值得保留。就是对比是否sum[j]<sum[r-1]。否则就从尾端删除,然后再往前遍历。这个可以参见LIS的O(nlog(n))算法。有一道类似的题目,是Uva1471,defense lines。这个处理方法类似。

下面给出实现代码(下面这个是用list实现双端队列的版本):

class Solution {
public:
    list<int> slide;
    int shortestSubarray(vector<int>& A, int K) {
        int i, last, ans = 100000, index;
        vector<int> sum;
        sum.push_back(0);
        for(i=0; i<A.size(); i++)
        {
            sum.push_back(sum[i]+A[i]);
        }
        for(i=1; i<sum.size(); i++)
        {
            last = max(0, i - ans);
            while(!slide.empty())
            {
                index = slide.back();
                if(sum[index]>=sum[i-1])
                    slide.pop_back();
                else
                    break;
            }
            slide.push_back(i-1);
            if(sum[i]<sum[i-1])
                continue;
            for(list<int>::iterator it=slide.begin();it!=slide.end();)
            {
                index = *it;
                if(index<last)
                {
                    it++;
                    slide.pop_front();
                    continue;
                }
                else if(sum[i]-sum[index]>=K)
                    ans = i - index;
                else
                    break;
                it++;
            }

        }
        if(ans == 100000)
            ans = -1;
        return ans;
    }
};

结果:

 

滑动数组实现双端队列法:

int slide[50010];
class Solution {
public:
    int shortestSubarray(vector<int>& A, int K) {
        int i, last, ans = 100000, index, fro = 0, bac = -1;
        vector<int> sum;
        sum.push_back(0);
        for(i=0; i<A.size(); i++)
        {
            sum.push_back(sum[i]+A[i]);
        }
        for(i=1; i<sum.size(); i++)
        {

            while(fro<=bac)
            {
                index = slide[bac];
                if(sum[index]>=sum[i-1])
                    bac--;
                else
                    break;
            }
            last = max(0, i - ans + 1);
            bac++;
            slide[bac] = i-1;
            if(sum[i]>sum[i-1])
            {
                int cnt = fro;
                while(cnt<=bac)
                {
                    index = slide[cnt];
                    if(index<last)
                        fro++;
                    else if(sum[i]-sum[index]>=K)
                        ans = i - index;
                    else
                        break;
                    cnt++;
                }
            }


        }
        if(ans == 100000)
            ans = -1;
        return ans;
    }
};

 

结果:

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值