长度最小的子数组

方法 1:暴力
想法

按照题目要求直接求。把所有可能的子数组求和并更新 \text{ans}ans ,直到我们找到最优子数组且和满足 \text{sum} \geq \text{s}sum≥s 。

算法

初始化 \text{ans}=\text{INT\_MAX}ans=INT_MAX
用变量 ii 从左到右遍历数组:
用变量 jj 从当前元素到数组尾部遍历:
将 ii 到 jj 这些元素求和得到 \text{sum}sum
如果和 \text{sum}sum 比 ss 大:
更新 \text{ans} = \min(\text{ans}, (j - i + 1))ans=min(ans,(j−i+1))
继续迭代
C++
int minSubArrayLen(int s, vector<int>& nums)
{
    int n = nums.size();
    int ans = INT_MAX;
    for (int i = 0; i < n; i++) {
        for (int j = i; j < n; j++) {
            int sum = 0;
            for (int k = i; k <= j; k++) {
                sum += nums[k];
            }
            if (sum >= s) {
                ans = min(ans, (j - i + 1));
                break; //Found the smallest subarray with sum>=s starting with index i, hence move to next index
            }
        }
    }
    return (ans != INT_MAX) ? ans : 0;
}
复杂度分析

时间复杂度:O(n^3)O(n 
3
 ) 。

对数组里的每一个元素,我们从它开始枚举所有的子数组,需要的时间为 O(n^2)O(n 
2
 )。
将每一个子数组求和的时间复杂度为:O(n)O(n) 。
所以总时间复杂度为:O(n^2 * n) = O(n^3)O(n 
2
 ∗n)=O(n 
3
 ) 。
空间复杂度:O(1)O(1)。只是用了常数个额外变量。

方法 2:优化的暴力
想法

在方法 1 中,我们注意到求子数组的和需要的时间为 O(n)O(n) 。我们其实可以很容易地实现 O(1)O(1) 时间的求和,只需要从开始元素用一个累加器保存和。我们将累积和保存在 \text{sums}sums 中,通过这种方法,我们可以轻松求出任意子区间的和。

算法

算法流程与方法 1 类似。
唯一的不同是求子数组的和:
建立一个大小为 \text{nums}nums 的向量 \text{sums}sums
初始化 \text{sums}[0]=\text{nums}[0]sums[0]=nums[0]
遍历 向量 \text{sums}sums :
更新 \text{sums}[i] = \text{sums}[i-1] + \text{nums}[i]sums[i]=sums[i−1]+nums[i]
从 ii 到 jj 的和计算方法:
\text{sum}=\text{sums}[j] - \text{sums}[i] +\text{nums}[i]sum=sums[j]−sums[i]+nums[i] ,其中 \text{sums}[j] - \text{sums}[i]sums[j]−sums[i] 是从第 i+1i+1 个元素到第 jj 个元素的和。
C++
int minSubArrayLen(int s, vector<int>& nums)
{
    int n = nums.size();
    if (n == 0)
        return 0;
    int ans = INT_MAX;
    vector<int> sums(n);
    sums[0] = nums[0];
    for (int i = 1; i < n; i++)
        sums[i] = sums[i - 1] + nums[i];
    for (int i = 0; i < n; i++) {
        for (int j = i; j < n; j++) {
            int sum = sums[j] - sums[i] + nums[i];
            if (sum >= s) {
                ans = min(ans, (j - i + 1));
                break; //Found the smallest subarray with sum>=s starting with index i, hence move to next index
            }
        }
    }
    return (ans != INT_MAX) ? ans : 0;
}
复杂度分析

时间复杂度:O(n^2)O(n 
2
 ) 。

找到所有子数组的时间复杂度为 O(n^2)O(n 
2
 ) 。
计算子数组的和为 O(1)O(1) 的时间。
因此,总时间复杂度为:O(n^2 * 1) = O(n^2)O(n 
2
 ∗1)=O(n 
2
 ) 。
空间复杂度:O(n)O(n) 。

额外的 \text{sums}sums 数组空间大小为 O(n)O(n) 。
方法 3:使用二分查找
想法

我们可以用二分查找的方法优化方法 2 。我们找到从下标 ii 开始满足 \text{sum} \geq \text{s}sum≥s 的子数组需要 O(n)O(n) 的时间。但是我们可以用二分查找的方法把这个时间优化到 O(\log(n))O(log(n)) 。在方法 2 中,我们从 ii 开始找 jj ,直到找到 \text{sum}=\text{sums}[j] - \text{sums}[i] + \text{nums}[i]sum=sums[j]−sums[i]+nums[i] 大于等于\text{s}s 的。与其线性地查找这个和,我们可以使用二分搜索的方法找到 \text{sums}sums 中不小于 \text{s}+\text{sums[i]}-\text{nums[i]}s+sums[i]−nums[i] 的第一个 \text{sums[j]}sums[j] ,可以用 C++ STL 中的 \text{lower_bound} 函数做到。

算法

创建大小为 n+1n+1 的数组 sumssums :
\text{sums}[0]=0\text{, }\text{sums}[i]=\text{sums}[i-1]+\text{nums}[i-1]
sums[0]=0, sums[i]=sums[i−1]+nums[i−1]

从 i=1i=1 到 nn 枚举:
在 \text{sums}sums 中找到值 \text{to\_find}to_find ,满足从 ii 开始到这个位置的和大于等于 ss 且是最小子数组:
\text{to\_find}=\text{s}+\text{sums}[i-1]
to_find=s+sums[i−1]

+在 \text{sums}sums 中找到值满足大于等于 \text{to_find} 的下标,记作 \text{bound}bound
+如果我们在 \text{sums}sums 中找到了值 \text{to_find}, 那么:
- 当前子数组的大小为:
\text{bound} - (\text{sums.begin}()+i-1)bound−(sums.begin()+i−1)
- 将 ansans 与当前数组的大小做比较,并把较小值保存到 ansans 中
C++
int minSubArrayLen(int s, vector<int>& nums)
{
    int n = nums.size();
    if (n == 0)
        return 0;
    int ans = INT_MAX;
    vector<int> sums(n + 1, 0); //size = n+1 for easier calculations
    //sums[0]=0 : Meaning that it is the sum of first 0 elements
    //sums[1]=A[0] : Sum of first 1 elements
    //ans so on...
    for (int i = 1; i <= n; i++)
        sums[i] = sums[i - 1] + nums[i - 1];
    for (int i = 1; i <= n; i++) {
        int to_find = s + sums[i - 1];
        auto bound = lower_bound(sums.begin(), sums.end(), to_find);
        if (bound != sums.end()) {
            ans = min(ans, static_cast<int>(bound - (sums.begin() + i - 1)));
        }
    }
    return (ans != INT_MAX) ? ans : 0;
}
复杂度分析

时间复杂度:O(n\log(n))O(nlog(n)) 。
对向量中的每一个元素,从它开始用二分查找找到子数组,满足和大于 ss 。因此,遍历的时间复杂度是 O(n)O(n) ,二分查找的时间复杂度是 O(\log(n))O(log(n)) 。
因此,总时间复杂度是 O(n*\log(n))O(n∗log(n))
空间复杂度:O(n)O(n)。\text{sums}sums 需要额外的 O(n)O(n) 空间。
方法 4:使用两个指针
想法

到现在为止,我们都保持子数组的左端点不动去找右端点。其实一旦知道这个位置开始的子数组不会是最优答案了,我们就可以移动左端点。我们用 2 个指针,一个指向数组开始的位置,一个指向数组最后的位置,并维护区间内的和 \text{sum}sum 大于等于 ss 同时数组长度最小。

算法

初始化 \text{left}left 指向 0 且初始化 \text{sum}sum 为 0
遍历 \text{nums}nums 数组:
将 \text{nums}[i]nums[i] 添加到 \text{sum}sum
当 \text{sum}sum 大于等于 ss 时:
更新 \text{ans}=\min(\text{ans},i+1-\text{left})ans=min(ans,i+1−left) ,其中 i+1-\text{left}i+1−left是当前子数组的长度
然后我们可以移动左端点,因为以它为开头的满足 \text{sum} \geq ssum≥s 条件的最短子数组已经求出来了
将 \text{sum}sum 减去 \text{nums[left]}nums[left] 然后增加 \text{left}left
C++
int minSubArrayLen(int s, vector<int>& nums)
{
    int n = nums.size();
    int ans = INT_MAX;
    int left = 0;
    int sum = 0;
    for (int i = 0; i < n; i++) {
        sum += nums[i];
        while (sum >= s) {
            ans = min(ans, i + 1 - left);
            sum -= nums[left++];
        }
    }
    return (ans != INT_MAX) ? ans : 0;
}
复杂度分析

时间复杂度:O(n)O(n) 。每个指针移动都需要 O(n)O(n) 的时间。
每个元素至多被访问两次,一次被右端点访问,一次被左端点访问。
空间复杂度: O(1)O(1) 。\text{left}left,\text{sum}sum,\text{ans}ans 以及 ii 这些变量只需要常数个空间。

作者:LeetCode
链接:https://leetcode-cn.com/problems/minimum-size-subarray-sum/solution/chang-du-zui-xiao-de-zi-shu-zu-by-leetcode/
来源:力扣(LeetCode)
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值