🎶二分 · 双指针 · 滑动窗口 · 螺旋矩阵:数组算法四题拆解

数组算法四题精讲

二分查找 · 双指针 · 滑动窗口 · 螺旋矩阵 LeetCode 704 · 27 · 977 · 209 · 59


01 二分查找(LeetCode 704)

给定一个升序排列的有序数组和目标值 target,返回目标值的下标,找不到返回 -1。

暴力解法从头扫到尾,时间复杂度 O(n)。二分查找利用数组有序的特性,每次把搜索范围缩小一半,效率远高于暴力。

核心思路

用左右两个指针圈定搜索范围,每次取中间值和 target 比较,根据大小决定往左半区还是右半区继续找。

ini 复制代码
nums = [-1, 0, 3, 5, 9, 12]   target = 9

left=0                 right=5
  ↓                      ↓
[-1,  0,  3,  5,  9,  12]
           ↑
         mid=2   nums[2]=3 < 9  → 往右找

              left=3    right=5
                ↓         ↓
[              5,   9,   12]
                    ↑
                  mid=4  nums[4]=9 == 9  → 找到!返回4

边界条件:left < right 还是 left <= right?

这是二分查找最容易搞混的地方,取决于你定义的区间是左闭右开 还是左闭右闭

左闭右开 [left, right) 的规则:

  • right = nums.length(不是 length-1),因为右边是开区间取不到
  • while(left < right),left == right 时区间为空,退出
  • nums[mid] > target 时,right = mid(不是 mid-1,因为右边取不到)
java 复制代码
int left = 0;
int right = nums.length;          // 左闭右开,right不包含

while (left < right) {            // 相等时区间为空,退出
    int mid = (right + left) / 2;
    if (nums[mid] == target) {
        return mid;
    } else if (nums[mid] < target) {
        left = mid + 1;           // mid已判断过,左边从mid+1开始
    } else {
        right = mid;              // 右开区间,right=mid即排除mid
    }
}
return -1;

时间复杂度:O(log n)  空间复杂度:O(1)


02 移除元素(LeetCode 27)

给一个数组和值 val,原地移除所有等于 val 的元素,返回剩余元素个数。不能开新数组,要在原数组上操作。

核心思路:快慢双指针

用两个指针 fast 和 slow,fast 负责遍历每个元素,slow 负责指向下一个有效位置。

  • 遇到不等于 val 的元素就写入 slow 位置,slow 前进
  • 遇到等于 val 的直接跳过,slow 不动
ini 复制代码
nums = [3, 2, 2, 3]   val = 3

fast=0  slow=0
  3 == val → 跳过,slow不动,fast++

fast=1  slow=0
  2 != val → nums[slow]=2,slow++,fast++

fast=2  slow=1
  2 != val → nums[slow]=2,slow++,fast++

fast=3  slow=2
  3 == val → 跳过

结果:nums = [2, 2, ...]  返回 slow=2
javascript 复制代码
let slow = 0
for (let fast = 0; fast < nums.length; fast++) {
    if (nums[fast] != val) {       // fast找到有效元素
        nums[slow] = nums[fast]    // 写入slow位置
        slow++                     // slow往前走
    }
    // fast不管怎样都会++(for循环自动)
}
return slow                        // slow就是有效元素个数

关键点:

  • fast 每次都走,slow 只有遇到有效元素才走
  • slow 同时也是有效元素的计数器,最终返回 slow 即可
  • 原地操作,没有开新数组,空间复杂度 O(1)

时间复杂度:O(n)  空间复杂度:O(1)


03 有序数组的平方(LeetCode 977)

给一个按非递减顺序排列的整数数组(包含负数),返回每个数字平方后按非递减顺序排列的新数组。

关键点:数组有负数,负数平方后可能很大。最大的平方一定在数组两端(最左或最右),因此用双指针从两端往中间收。

核心思路:对撞双指针 + 从后往前填

用 i 指向左端,j 指向右端,k 指向结果数组的末尾。每次比较两端的平方,把较大的放入 resultk,然后 k--,对应指针也收缩。

ini 复制代码
nums = [-4, -1, 0, 3, 10]

i=0  j=4  k=4
(-4)²=16   (10)²=100  → 100更大,result[4]=100,j--,k--

i=0  j=3  k=3
(-4)²=16   (3)²=9     → 16更大,result[3]=16,i++,k--

i=1  j=3  k=2
(-1)²=1    (3)²=9     → 9更大,result[2]=9,j--,k--

i=1  j=2  k=1
(-1)²=1    (0)²=0     → 1更大,result[1]=1,i++,k--

i=2  j=2  k=0
(0)²=0                → result[0]=0

结果:[0, 1, 9, 16, 100] ✅
javascript 复制代码
const result = []
let k = nums.length - 1           // 结果数组从末尾开始填

for (let i = 0, j = nums.length - 1; i <= j;) {
    if (nums[i] * nums[i] < nums[j] * nums[j]) {
        result[k] = nums[j] * nums[j]   // 右边更大
        j--
        k--
    } else {
        result[k] = nums[i] * nums[i]   // 左边更大或相等
        i++
        k--
    }
}
return result

时间复杂度:O(n)  空间复杂度:O(n)


04 长度最小的子数组(LeetCode 209)

给定数组和目标值 target,找出总和 ≥ target 的最小连续子数组,返回其长度,不存在则返回 0。

核心思路:滑动窗口

用左右两个指针维护一个窗口:

  • right 不断往右扩张,把新元素加进窗口
  • 当窗口内的和 ≥ target 时,记录当前长度,然后 left 往右收缩,尝试找更小的满足条件的窗口
ini 复制代码
nums = [2, 3, 1, 2, 4, 3]   target = 7

right扩张:2→5→6→8 ≥ 7
窗口 [2,3,1,2]  长度4  记录minLen=4

left收缩:sum-=2=6 < 7,继续扩张

right扩张:6+4=10 ≥ 7
left收缩:sum-=3=7 ≥ 7,窗口[1,2,4]  长度3  更新minLen=3
left收缩:sum-=1=6 < 7,停止收缩

right扩张:6+3=9 ≥ 7
left收缩:sum-=2=7 ≥ 7,窗口[4,3]  长度2  更新minLen=2
left收缩:sum-=4=3 < 7,停止

最终 minLen = 2
javascript 复制代码
let left = 0
let sum = 0
let minLen = 10000000              // 初始设一个极大值

for (let right = 0; right < nums.length; right++) {
    sum += nums[right]             // 扩张右边界

    while (sum >= target) {        // 满足条件就收缩左边界
        minLen = Math.min(minLen, right - left + 1)
        sum -= nums[left]          // 移出左边元素
        left++
    }
}

return minLen === 10000000 ? 0 : minLen   // 没找到返回0

关键点:

  • right 用 for 循环控制,left 用 while 控制,两者不对称
  • 用 while 而不是 if:满足条件要持续收缩,直到不满足为止
  • 每次收缩都记录一次长度,保证不遗漏更小的窗口

时间复杂度:O(n)  空间复杂度:O(1)


05 螺旋矩阵 II(LeetCode 59)

给定 n,生成一个 n×n 的矩阵,按螺旋顺序填入 1 到 n²。

这道题没有特别的算法,考的是边界控制。关键是坚持"左闭右开"原则,每条边不走最后一个格,留给下一条边的起点,保持一致性。

核心思路

每一圈分四条边:上、右、下、左。用 startx、starty 记录当前圈的起点,end 控制每条边走多远,每走完一圈,起点内移、范围缩小、圈数减少。

ini 复制代码
n=4 的螺旋顺序(每条边左闭右开):

 →  →  →  ↓
 1   2   3   4
↑           ↓
12  13  14   5
↑           ↓
11  16  15   6
↑   ←   ←   ↓
10   9   8   7

startx=starty=0,end=1,每圈后 start++,end++,test--
javascript 复制代码
let startx = 0, starty = 0       // 每圈起点
let end = 1                      // 每条边结束位置偏移
let count = 1                    // 填入的数字
let test = Math.floor(n / 2)     // 需要转几圈

while (test) {
    // 上边:从左往右(列变,行固定为startx)
    for (j = starty; j < n - end; j++) nums[startx][j] = count++
    // 右边:从上往下(行变,j停在n-end)
    for (i = startx; i < n - end; i++) nums[i][j] = count++
    // 下边:从右往左(列变,i停在n-end)
    for (;           j > starty; j--) nums[i][j] = count++
    // 左边:从下往上(行变,j停在starty)
    for (;           i > startx; i--) nums[i][j] = count++

    startx++; starty++            // 下一圈起点内移
    end++                         // 每条边走的范围缩小
    test--                        // 圈数减一
}

// n为奇数时,中心格单独处理
if (n % 2 != 0) nums[Math.floor(n/2)][Math.floor(n/2)] = n * n

关键点:

  • 坚持左闭右开:每条边不走最后一个格,留给下一条边
  • 四条边共用 i 和 j,上一条边结束时 i/j 的位置,正好是下一条边的起点
  • n 为奇数时中心格永远走不到,要单独填入 n²
  • 转几圈 = Math.floor(n/2),n=4 转2圈,n=5 转2圈+中心

时间复杂度:O(n²)  空间复杂度:O(n²)


总结:解题方法对照

题目 方法 核心思路
二分查找 二分查找 有序数组每次砍掉一半范围
移除元素 快慢双指针 fast找有效元素,slow负责写入
有序数组的平方 对撞双指针 两端往中间收,从后往前填
长度最小的子数组 滑动窗口 right扩张,满足条件left收缩
螺旋矩阵 II 模拟 坚持左闭右开,边界统一不出错
相关推荐
YH55269845 小时前
GPT‑5.6 Sol 原本支持 1M 上下文,Codex 现已放开此前限制,如何看待这次调整?
java·jvm·人工智能·gpt·算法·chatgpt
遥感知识服务7 小时前
从局部阈值、双极化到暗地表剔除:NASA OPERA DSWx-S1全球动态水体算法拆解
大数据·人工智能·深度学习·神经网络·算法·机器学习
晚风醉蝶7 小时前
1-13-TimSort
算法
ZJU_统一阿萨姆7 小时前
【算子开发】算子融合与Softmax_LayerNorm实现
人工智能·算法·语言模型·硬件架构
研☆香9 小时前
聊一聊前端的常见字 关键字
开发语言·前端·javascript
叩码以求索9 小时前
浅谈:前序遍历反转法求解N叉树的后序遍历
算法
北风toto11 小时前
中缀、前缀、后缀表达式
算法·软件设计师
听取WA声一片(无恶意)11 小时前
CSP-J/CSP-S 深度优先搜索(DFS)完全讲义
c++·算法·深度优先
码流怪侠11 小时前
2026年8月GitHub热榜深度拆解:Agent Skills席卷开源圈,一个“技能包“收割5万星
算法·程序员·github
hold?fish:palm11 小时前
30 两两交换链表中的节点
数据结构·算法·链表