【LeetCode209.长度最小的子数组】——双指针法、二分查找法

209.长度最小的子数组

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

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

示例 1:

输入:target = 7, nums = [2,3,1,2,4,3]
输出:2
解释:子数组 [4,3] 是该条件下的长度最小的子数组。

示例 2:

输入:target = 4, nums = [1,4,4]
输出:1

示例 3:

输入:target = 11, nums = [1,1,1,1,1,1,1,1]
输出:0

提示:

  • 1 <= target <= 109
  • 1 <= nums.length <= 105
  • 1 <= nums[i] <= 105

进阶:

  • 如果你已经实现 O(n) 时间复杂度的解法, 请尝试设计一个 O(n log(n)) 时间复杂度的解法。

209. 长度最小的子数组 - 力扣(LeetCode)

思路:

要寻找符合条件的最小子数组,自然而然就可以想到通过把所有的子数组遍历一遍,找到符合要求的,再比较其中的长度最小的子数组。这样的话,要使用两层for循环,属于暴力解法,时间复杂度较高。

做这道题我们可以使用滑动窗口的方法,其实和双指针法是比较类似的。只不过移动的是整个一个区间,所以称为滑动窗口。

暴力解法:

class Solution {
public:
    int minSubArrayLen(int s, vector<int>& nums) {
        int result = INT32_MAX; // 让result等于一个极大的值,INT32_MAX为宏定义
        int sum = 0; // 存储子序列的数值之和
        int subLength = 0; // 存储子序列的长度
        for (int i = 0; i < nums.size(); i++) { // 设置子序列起点为i
            sum = 0;
            for (int j = i; j < nums.size(); j++) { // 设置子序列终止位置为j
                sum += nums[j];
                if (sum >= s) { // 一旦发现子序列和超过了s,更新result
                    subLength = j - i + 1; // 取子序列的长度
                    result = result < subLength ? result : subLength; //比较更新result
                    break; //找到符合条件最短的子序列,退出循环
                }
            }
            // 如果result没有被赋值的话,就返回0,说明没有符合条件的子序列
            return result == INT32_MAX ? 0 : result;
        }
    }
};

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

滑动窗口法:

我们可以使用滑动窗口法对时间复杂度进行优化,不断调节子序列的起始和终止位置。

设定两个指针i和j,分别指向滑动窗口的起始位置以及终止位置。

这里我们就需要考虑滑动窗口外层for循环中遍历的应该是窗口的起始位置还是终止位置。

如果起始位置一直在移动的话,不可避免的,我们在起始位置每次变化前,终止位置都需要对整条数组进行一遍遍历,若是这么做,那就与之前的暴力解法无异了。

所以我们采用循环的索引表示滑动窗口终止位置的方法,让j先去不断移动,用sum记录此时窗口中的和,一旦sum的值大于等于目标值,我们就可以对起始位置进行更新,缩小起始位置,从而判断是否存在更小长度,符合条件的子数组。

class Solution {
public:
   //滑动窗口
    int minSubArrayLen(int s, vector<int>& nums) {
        int result = INT32_MAX; //让result取一个极大值
        int sum = 0; // 滑动窗口数值之和
        int i = 0; // 滑动窗口起始位置
        int subLength = 0; // 滑动窗口的长度
        for (int j = 0; j < nums.size(); j++) { //j指向滑动窗口的末尾位置,用来遍历
            sum += nums[j];
            // 注意这里使用while,每次更新 i(起始位置),并不断比较子序列是否符合条件
            while (sum >= s) {
                subLength = (j - i + 1); // 取子序列的长度
                result = result < subLength ? result : subLength;
                sum -= nums[i++]; // 不断变更i(子序列的起始位置)
            }
        }
        // 如果result没有被赋值的话,就返回0,说明没有符合条件的子序列
        return result == INT32_MAX ? 0 : result;
    }
};

时间复杂度:O(2n) 可以视作 O(n)

二分查找法:

这种方法利用了之前所学到的二分查找法,先创建一个数组sums用于存储数组nums的前缀和。sums[i]表示nums[0]到nums[i-1]的元素和。

得到前缀和后,对于每个开始下标i,通过二分查找得到大于等于i的最小下标bound,使得sums[bound]-sums[i-1]大于等于s。并实时更新子数组的最小长度bound-(i-1)

这里使用二分查找的前提是前缀和数组sums是有序的,由于原数组中都是正整数,所以所得到的的前缀和数组也必定是递增的,所以我们可以使用二分查找的方法。

class Solution {
public:
   //二分查找
    int minSubArrayLen3(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 
        // sums[0] = 0 意味着前 0 个元素的前缀和为 0
        // sums[1] = A[0] 前 1 个元素的前缀和为 A[0]
        // 以此类推
        for (int i = 1; i <= n; i++) {
            sums[i] = sums[i - 1] + nums[i - 1];
        }
        for (int i = 1; i <= n; i++) {
            int target = s + sums[i - 1];
            auto bound = lower_bound(sums.begin(), sums.end(), target); //使用现成的库函数来实现这里二分查找大于等于某个数的第一个位置的功能
            if (bound != sums.end()) {
                ans = min(ans, static_cast<int>((bound - sums.begin()) - (i - 1)));
            }
        }
        return ans == INT_MAX ? 0 : ans;
    }
};

时间复杂度:O(nlogn)

后续也会坚持更新我的LeetCode刷题笔记,欢迎大家关注我,一起学习。
如果这篇文章对你有帮助,或者你喜欢这篇题解,可以给我点个赞哦。
CSDN同步更新,欢迎关注我的博客:一粒蛋TT的博客_CSDN博客-LeetCode学习笔记,HTML+CSS+JS,数据结构领域博主

往期回顾:
LeetCode977.有序数组的平方
LeetCode844.比较含退格的字符串
LeetCode283.移动零
LeetCode27.移除元素
LeetCode26.删除有序数组中的重复项

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 216,001评论 6 498
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 92,210评论 3 392
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 161,874评论 0 351
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 58,001评论 1 291
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 67,022评论 6 388
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 51,005评论 1 295
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,929评论 3 416
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,742评论 0 271
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 45,193评论 1 309
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,427评论 2 331
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,583评论 1 346
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,305评论 5 342
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,911评论 3 325
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,564评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,731评论 1 268
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 47,581评论 2 368
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,478评论 2 352

推荐阅读更多精彩内容