算法日记:长度最小子数组——数组双指针滑动窗口

318 阅读2分钟

题目

力扣链接 长度最小子数组

给定一个含有 n 个正整数的数组和一个正整数 s ,找出该数组中满足其和 ≥ s 的长度最小的 连续 子数组,并返回其长度。如果不存在符合条件的子数组,返回 0。

示例:

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

提示:

  • 1 <= target <= 10^9
  • 1 <= nums.length <= 10^5
  • 1 <= nums[i] <= 10^5

解法

暴力解法

首先这是一个数组的遍历问题,我们一开始就能想到暴力解法就是直接通过双重for循环

  • 先遍历每一个数字
  • 再遍历后面到结尾的数字进行相加
  • 假如相加的结果大于s就直接通过break退出循环
  • 记录已经遍历的长度保存
  • 对比多个遍历的长度,记录最短的长度返回
/**
 * @param {number} target
 * @param {number[]} nums
 * @return {number}
 */
var minSubArrayLen = function(target, nums) {
  let x = Infinity; //对比最小的话初始化需要最大
  for (let i = 0; i < nums.length; i++) { //外层遍历,遍历每一个数字
    let sum = nums[i]; //记录数字的第一个元素
    if (sum >= target) {  //假如第一个数字就已经满足目标值,直接赋值后退出循环
      x = 1;
      break;
    }
    for (let j = i + 1; j <= nums.length - 1; j++) { //内部循环,从i的下一个开始,到数组末尾结束
      sum += nums[j]; //累加
      if (sum >= target) { //假如sum大于等于目标值
        let res = j - i + 1; //记录当前数组长度
        x = res < x ? res : x //假如新长度更小,进行赋值,否则不改动
        break; //找到后跳出内部循环
      }
    }
  }
  return x===Infinity?0:x //假如x未被赋值过,则返回0
};

因为用到了双重for循环,所以时间复杂度是O(n^2)

双指针滑动窗口

使用暴力解法的话需要遍历很多次,假如不用break及时退出循环,在力扣是会超时的。虽然能解决问题,但是明显效率是不高的。

那有没有一种方法可以只遍历一次就计算出最短长度呢?

所谓滑动窗口,就是不断的调节子序列的起始位置和终止位置,从而得出我们要想的结果

双指针的思路具体如下:

  • 首先需要一个for循环遍历数据
  • 将循环索引j当成窗口的末尾,定义一个i当成窗口的起始位置
  • 计算j累加的和,当大于目标值时,记录长度
  • 同时累加值减去i所对应的值,i向后移一位,假如仍然大于目标值,则继续记录长度和向后移
  • 最后记录最短长度

参考动图

来自代码随想录

动图来自代码随想录

var minSubArrayLen = function(target, nums) {
    let res = Infinity //定义结果,大小为无限大
    let sum = 0,i=0,slength =0; //定义计算结果sum,起始位置i,数组长度slength
    for(let j=0;j<nums.length;j++){ //外层遍历,j为末尾位置
      sum+=nums[j] //计算j累加的值
      while(sum>=target){  //这里while是因为假如i向后移一位仍然大于目标值,则需要不断后移知道小于目标值
        slength= j-i+1 //记录长度
        sum-=nums[i] //计算结果减去i对应的值
        i++; //i向后移
        res = res<slength?res:slength //对比哪个更小
      }
    }
    return res===Infinity?0:res //假如未被赋值过,则返回0
};

总结

数组问题,感觉大部分都可以用双指针来达到更好的效果,但是也有可能我刚入门学的不深。

如果有更好的解法欢迎交流。