「前端每日一问(63)」长度最小的子数组

287 阅读4分钟

一起养成写作习惯!这是我参与「掘金日新计划 · 4 月更文挑战」的第28天,点击查看活动详情

本题难度:⭐ ⭐ ⭐ ⭐

本题类型:算法、手写

跟着阿林一起刷算法题,阿林选的题全是面试高频题,今天介绍数组类型题目常用的套路——滑动窗口,同时也介绍一种求最小值或者最大值的小技巧。

知识点

  • 算法思想之滑动窗口
  • 求最小值或最大值的小技巧

题目描述

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

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

原题地址:209. 长度最小的子数组

题目要求:

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

解题思路 & 编码实现

解这道题我们首先会想到暴力法,循环两次,每次做对比,记录最小值。

这里有个小技巧,求最小值时,可以先设置初始值为 Infinity,之后如果有更小的,这个 Infinity 一定会被替换,这样写不用考虑一些边界问题,简单粗暴。

代码实现如下:

function minSubArrayLen (target, nums) {
  let len = nums.length
  let res = Infinity
  for (let i = 0; i < len; i++) {
      let sum = 0
      for (let j = i; j < len; j++) {
          sum += nums[j]
          if (sum >= target) {
              res = Math.min(res, j - i + 1);
              break
          }
      }
  }
  return res == Infinity ? 0 : res;
}

当然,用暴力法解决了问题之后,接下来该考虑的就是如何去优化了,毕竟无脑遍历去解题在面试官那里可不会通过的。

这道题我们还是可以用快慢指针来做,思路是:

  • 定义慢指针 i,快指针 j
  • 定义 sum 记录当前累加值,定义 res 记录要返回的长度。
  • 慢指针先不动,快指针一直往后走,同时把累加值记录到 sum 上。
  • 当累加值 sum >= target 时,把当前快慢指针索引之差记录到 res 上,再动慢指针,具体逻辑见下面的图解。

老规矩,先自己用文字“画图”,理清思路,不要光靠脑袋想。

  慢指针 i,快指针 j
  target = 7  
  
  初始状态:
  sum = 2  res = Infinity
  
   i          
  [2,3,1,4,3]
   j
  
  
  下一步:
  sum = 5  res = Infinity
  
   i          
  [2,3,1,4,3]
     j
  
  
  下一步:
  sum = 6  res = Infinity
  
   i          
  [2,3,1,4,3]
       j
  
  
  下一步:
  sum = 10  res = 4
  
   i          
  [2,3,1,4,3]
         j
  
  下一步:
  sum = 8  res = 3
  
     i          
  [2,3,1,4,3]
         j
  
  
  下一步:
  sum = 5  res = 3
  
       i          
  [2,3,1,4,3]
         j
  
  
  下一步:
  sum = 8  res = 3
  
       i          
  [2,3,1,4,3]
           j
  
  
  下一步:
  sum = 7  res = 2
  
         i          
  [2,3,1,4,3]
           j
  
  
  下一步:
  sum = 3  res = 2
  
           i          
  [2,3,1,4,3]
           j
 
  
  下一步:
  快指针走完,循环结束
  
           i          
  [2,3,1,4,3]
             j

编码实现:

写代码时用更语意化的 fastslow 来编码。

function minSubArrayLen (target, nums) {
  let slow = 0, fast = 0
  let len = nums.length
  let res = Infinity
  let sum = 0
  while(fast < len) {
    sum += nums[fast]
    while (sum >= target) {
      res = Math.min(fast - slow + 1, res)
      sum -= nums[slow]
      slow++
    }
    fast++
  }
  return res === Infinity ? 0 : res
}

看到这里,其实你已经不知不觉地理解了滑动窗口的思想。

在本题里,其实快慢指针之间的这个小区间,就可以理解成一个滑动的窗口。

arr33.png

arr34.png

随着快慢指针的移动,这个窗口在不停地扩大或者收缩,所以把它叫做滑动窗口。

滑动窗口是一种算法思想,或是一种解题方式,在数组上通过双指针同向移动(快慢指针)而解决的一类问题。

使用滑动窗口解决的问题通常是暴力解法的优化,掌握这一类问题最好的办法就是多练习,比如一道题用暴力法解决了,就去思考一下可不可以通过滑动窗口去优化一下,减少一些遍历的次数。

运行结果

vscode leetcode 插件 yyds! 上班“认真工作”的神器 🐶

image.png

结尾

阿林水平有限,文中如果有错误或表达不当的地方,非常欢迎在评论区指出,感谢~

如果我的文章对你有帮助,你的👍就是对我的最大支持^_^

我是阿林,输出洞见技术,再会!

上一篇:

「前端每日一问(62)」如何只截取一个数值的整数部分

下一篇“

「前端每日一问(64)」Vue3 项目打包时如何关闭 options API ?