Leetcode——209 长度最小的子数组

Leetcode——209 长度最小的子数组

题目描述

  • 给定一个含有 n 个正整数的数组和一个正整数 target

    找出该数组中满足其总和大于等于 target 的长度最小的 子数组 [numsl, numsl+1, ..., numsr-1, numsr] ,并返回其长度**。**如果不存在符合条件的子数组,返回 0

题解1:前缀和+二分

暴力解法就是枚举所有可能的i和j,在此情况下思考,是否可以把j的求法优化一下呢?

如果让每个i作为终点,能快速找到j(j≤ij \leq iji)使得j - i + 1(即长度)最小且nums[j…i]的和满足题目条件。

换句话说,j是有单调性的,如果满足条件,那么让j往i走,否则往远离i走。

现在的问题就是如何编写二分中的check函数,使得能够快速检查这段区间和是否满足条件呢?

很自然的想到前缀和,如果预处理sumisum_isumi表示nums1nums_{1}nums1numsinums_inumsi的和,那么求nums[l…r]的和的计算公式为:
suml,r=sum[r]−sum[l−1] sum_{l,r} = sum[r] - sum[l - 1] suml,r=sum[r]sum[l1]
这样只需要花费一定的空间就可以节省check时求和所需要的时间。

PS:这种方式最好数组从1开始存储,若从0的话比较麻烦。

代码如下:

class Solution {
public:
    int minSubArrayLen(int target, vector<int>& nums) {
        if(nums.size() == 0) return 0;
        vector<int>v;
        v.push_back(0);
        int n = nums.size();
        for(int i = 0;i < n;i ++) {
            v.push_back(nums[i]);
        }
        vector <int> sum;
        sum.push_back(0);
        for(int i = 1;i <= n;i ++) {
            sum.push_back(sum[i - 1] + v[i]);
        }
        int ans = INT_MAX;
        for(int i = 1;i <= n;i ++) {
            int l = 1;
            int r = i;
            while(l < r) {
                int mid = l + r + 1 >> 1;
                if(sum[i] - sum[mid - 1] >= target) l = mid;
                else r = mid - 1;
            }
            if(sum[i] - sum[l - 1] >= target) ans = min(ans,i - l + 1);
        }
        if(ans > INT_MAX / 2) return 0;
        else return ans;
    }
};

时间复杂度O(nlogn)O(nlogn)O(nlogn)

空间复杂度O(n)O(n)O(n)

题解2 利用数组性质——滑动窗口

其实从解法1来看,二分求解一个j的方法还是太麻烦了,为什么呢?

举个例子,比如我目前求出了区间[j,i]是满足题意的,那么对于下一个i(i = i + 1),区间内的和肯定会变大,但是这个变大到底影响多少呢?如果我尝试收紧左端,这个变大是否能抵消掉收紧所带来的影响呢?

比如:[1,2,3]再来个4,target=6的情况下,只需要2就可以;当区间来到[1,3]时,加入4,尝试删除1和2发现还是满足区间和大于等于target

那么我只需要从数组开头记录一个j,每次枚举一个i,尝试收束左边界即可,区间和只需要加加减减即可,代码如下:

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

时间复杂度O(n)

空间复杂度O(1)

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值