【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)

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

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值