zoukankan      html  css  js  c++  java
  • LeetCode209. 长度最小的子数组

    方法一

    暴力枚举所有可能的子数组,也就是枚举子数组的所有开始下标和结束下标,计算子数组的和,如果子数组的和小于等于s,就更新最小长度。

    class Solution {
    public:
        int minSubArrayLen(int s, vector<int>& nums) {
            if(nums.size() == 0) {                  //数组为空的情况需要特判
                return 0;
            }
            int res = 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 >= s) {
                        res = min(res, j -i + 1);          //如果子数组的和小于等于s,就更新res
                        break;                      //不要忘了加 break !!!
                    }
                }
            }
            return res == INT_MAX ? 0 : res;
        }
    };
    

    方法二

    同样是枚举子数组的起始下标,我们考虑优化子数组结束下标的搜索空间,如果能用二分找到(满足子数组的和大于等于s)子数组结束下标的位置,相比于方法一就会更快,
    二分查找要求数组是单调的,由于题目保证每一个元素都是正的,所以我们考虑用一个sums数组记录前缀和,也就是说,sums[i]表示nums[0] ~ nums[i - 1]的元素总和,
    这样,问题就转化为对于每一个起始下标i,通过二分查找寻找一个大于等于i的最小下标bound,使得sums[bound] - sums[i - 1] >= s,sums[bound] - sums[i - 1]就是子数组的和。找到了bound之后,我们就更新子数组的最小长度(此时子数组的长度是bound - (i - 1))。

    class Solution {
    public:
        int minSubArrayLen(int s, vector<int>& nums) {
            if(nums.size() == 0) {
                return 0;
            }
            int size = nums.size();
            vector<int> sums(size + 1, 0);            //这里sums的大小为size + 1,是因为我们要让sums[0]为0,方便下一句的for循环对suns做初始化
            for(int i = 1; i <= size; ++i) {
                sums[i] = sums[i - 1] + nums[i - 1];
            }
            int res = INT_MAX;
            for(int i = 1; i <= size; ++i) {
                int target = s + sums[i - 1];         //target 为 s + sums[i - 1],也就是说如果某个数bound的前缀和sums >= target,那么以i为起始下标,到bound的子数组的和就大于等于sum
                auto bound = lower_bound(sums.begin(), sums.end(), target);
                if(bound != sums.end()) {            //如果bound不越界,就更新子数组的最小长度
                    res = min(res, static_cast<int>((bound - sums.begin()) - (i - 1)));   
                }
            }
            return res == INT_MAX ? 0 : res;
        }
    };
    

    方法三

    考虑用双指针,两个指针start和end分别表示子数组的开始和结束下标,变量sum表示以start开头,以end结尾的子数组的和。start, end, sum的初始值都为0.
    先将nums[end]加入sum中,如果sum < s,就继续增加end(当然同时也把nums[end]加入sum)直到sum >= s,此时不增加end,而是更新子数组的最小长度(此时长度为end - start + 1),然后不断增加start(并更新子数组的最小长度)直到sum < s,然后再继续增加end,直到end超过了数组最大下标。

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

    复杂度分析

    1. 暴力方法中枚举起始下标需要O(n),内层循环枚举结束下标又需要O(n),因此时间复杂度是O(n);由于无需额外的辅助数组,因此空间复杂度是O(1);
    2. 前缀和 + 二分查找同样需要O(n)来枚举起始下标,但这里对于子数组的结束下标使用了二分查找进行优化,查找的复杂度是O(logn),因此总的时间复杂度是O(nlogn);由于需要一个额外数组sums存放前缀和,因此空间复杂度是O(n);
    3. 双指针方法中,左右指针start和end都只会向右移动,并且对于每个起始下标,无需从头开始枚举结束下标,因此时间复杂度是O(n);双指针方法也无需额外的辅助数组,空间复杂度为O(1).
  • 相关阅读:
    BZOJ 1854 [Scoi2010]游戏
    【模板】二分图匹配-匈牙利算法
    BZOJ 1432 [ZJOI2009]Function
    BZOJ 1192 [HNOI2006]鬼谷子的钱袋
    BZOJ 1088 [SCOI2005]扫雷Mine
    BZOJ 1047 [HAOI2007]理想的正方形
    BZOJ 1034 [ZJOI2008]泡泡堂BNB
    BZOJ 1022 [SHOI2008]小约翰的游戏John
    LOJ 6278 数列分块入门2
    【BZOJ 1003】[ZJOI2006]物流运输(Dijkstra+DP)
  • 原文地址:https://www.cnblogs.com/linrj/p/13204659.html
Copyright © 2011-2022 走看看