知蛛网
  • 首页
  • 会员
  • 活动
  • 工作台

常见问题
活动
关于我们
联系我们
隐私协议
用户协议
会员协议
© 2026 知蛛网. 保留所有权利。
首页会员工作台我的
flynn@flynn
主页wiki简历作品集
目录
全部书籍
00-路线
  • 学习路线
  • 命名与写作规范
01-计算机基础
01-计算机网络
  • 互联网是如何工作的
  • HTTP协议
  • TCP/IP协议
  • 浏览器直接打开本地html文件,请求时的协议问题
02-浏览器原理
  • 浏览器是如何渲染页面的
  • 浏览器是如何渲染页面的
  • 面试之浏览器
03-操作系统
  • 进程与线程
  • 内存管理与虚拟内存
  • IO 模型与多路复用
  • 文件系统与页缓存
04-数据结构与算法
  • 数组与链表
  • 栈与队列
  • 哈希表
  • 树与堆
  • 基础数据结构
01-基础数据结构
02-高级算法
  • 排序算法
  • 二分查找
  • 递归与回溯
  • 动态规划
  • 双指针与滑动窗口
03-leetcode刷题
02-前端核心
01-web开发
  • Web 和 Web 标准
  • web 入门
  • web 开发
  • URL
  • 域名
  • Web服务器
  • 命令行
  • 工作流程和流程
  • 超链接
  • 浏览器指纹
02-html
  • HTML基础语法
  • HTML 概述
  • HTML 发展史
  • HTML调试
  • HTML标题与段落
  • 文本格式化
  • 强调与重要性
  • 块级元素和内联元素
  • 列表
  • 区块
  • 语义元素
  • 页面结构化和语义化
  • 超链接
  • 相对路径和绝对路径
  • 图片
  • 表格
  • 表单和按钮
  • 多媒体
  • 视频/音频
  • HTML 头部
  • HTML 字符集
  • HTML颜色
  • HTML 语言代码
  • HTML 国家/地区
  • Emoji (表情符号)
  • HTML 标签简写及全称 |
  • SVG
  • Canvas 2D
  • Canvas 3D
  • 框架 `<iframe>`
  • HTML 拖放
  • HTML5 Geolocation(地理定位)
  • Web 存储
  • HTML URL 编码
  • MathML
  • 远程图片资源在自己网站中无法展示问题
03-css
  • CSS简介
  • CSS 语法
  • CSS 创建
  • CSS 发展史
  • CSS 浏览器支持
  • CSS 选择器
  • CSS `!important` 规则
  • css颜色
  • CSS 文本格式
  • CSS 盒子模型
  • CSS 尺寸
  • CSS 内边距
  • CSS 外边距
  • CSS 边框
  • CSS 轮廓
  • CSS 背景(background)
  • CSS 溢出
  • CSS 文档流
  • CSS 显示和可见性
  • CSS 浮动
  • CSS 定位
  • CSS 对齐方式
  • CSS 网页布局
  • CSS 多列布局(CSS Multi-column Layout)
  • CSS 透明度
  • CSS 响应式设计(Responsive Design)
  • CSS 媒体查询
  • CSS 列表
  • CSS 表格
  • CSS 表单
  • CSS 链接
  • CSS 用户界面
  • CSS 计数器
  • CSS 过渡
  • CSS 动画
  • CSS 2D 转换
  • CSS 3D 转换
  • CSS 变量(CSS Custom Properties)
  • CSS 原生嵌套(CSS Nesting)
  • CSS 函数(CSS Functions)
04-javascript
  • 什么是 JavaScript?
  • 编程范式(Programming Paradigm)
  • 什么是范式?
  • ECMAScript 规范
  • 什么是语法糖
  • 开发环境搭建
  • 什么是堆和栈?
  • 什么是执行上下文
  • JavaScript 垃圾回收机制
  • 作业队列与事件循环
  • 深拷贝和浅拷贝
  • 语法与类型
  • 字面量
  • 控制流与错误处理
  • 循环与迭代
  • 函数
  • 表达式与运算符
  • 数字和字符串
  • 表达日期与时间
  • 正则表达式
  • 索引集合类
  • 带键的集合
  • 对象
  • 类
  • Promise
  • async/await
  • 迭代器和生成器
  • 国际化
  • 模块
  • 资源管理
  • 属性的可枚举性和所有权
  • 闭包
  • 继承与原型链
  • 元编程
  • 内存管理
  • 内置对象
  • arguments 对象
  • BOM 对象(浏览器对象模型)
  • DOM 对象(文档对象模型)
  • bind、apply、call的区别
  • new 操作符
  • typeof 和 instanceof 的区别
  • HTML5 Web Workers
05-typescript
  • TypeScript 类型系统基础
  • 类型体操
03-框架与跨端
01-react
  • 开发环境搭建
  • JSX
  • 组件
  • 生命周期
  • Hooks
  • 组件通信
  • 状态管理
  • React 19 新特性
  • useCallback 闭包陷阱
  • 常用的性能优化操作
  • Firbe 架构
  • React 18 新特性
  • useRef
  • 基于umijs 4.0 搭建官网
  • react+vite本地开发,如何手机联调
  • React 实现谷歌登录
  • React 项目调起 WhatsApp
02-vue
  • vue 解决跨域请求
  • 在vue2使用TinyMCE富文本编辑器
  • 使用jszip 导出新闻
  • Vue 本地开发启用 https
  • 基于vue3+vite+ts+elementplus+pinia+axios搭建后台管理系统
03-react-native
  • iOS 开发环境(RN 0.72)
  • RN 原生模块(Native Module)
  • 查看Maven包的Java包名
  • 优化 APP 启动速度
  • 安全键盘
  • 启动图与 Icon 尺寸规范
  • RN如何生成/配置iOS的开发者证书和生产证书
  • 邓白氏编码
  • 微信开放平台配置Universal Link
  • 上架至Apple App Store
04-android
  • 开发环境搭建
  • Android项目目录结构
  • 认识Gradle
04-工程化
01-git
  • 如何解决已被git跟踪文件不在跟踪
  • git标签
  • Git报错: Failed to connect to github.com port 443 解决方案
  • Git 分支操作速查
02-构建工具
  • 构建工具原理与选型
02-webpack
  • webpack 核心概念与构建流程
  • webpack 性能优化
03-vite
  • vite 为什么快
  • vite 插件与生产构建
03-测试
  • 前端测试体系
04-部署
  • Nginx 反向代理解决跨域
  • SPA 刷新非首页报 404
05-docker
  • Docker 常用命令
  • docker-compose nginx配置ssl证书
  • Docker Desktop 升级后 docker-compose 命令找不到
06-性能优化
  • 前端性能优化体系
  • 背景 GIF 优化方案
07-微前端
  • 微前端实战(qiankun)
  • 微前端 · qiankun
01-qiankun
05-后端与数据
01-nodejs
  • Node 版本与依赖的 engines 冲突
  • Express 实现 Gmail 发信(邮箱验证码)
02-php
  • 浏览器保存新闻,发布到自有网站
  • [分布式] 浏览器保存新闻,发布到自有网站
  • 邮件发送,多账号轮询
  • nginx和php-fpm通信问题导致网站页面空白
03-python
  • macOS M1 芯片 如何安装 py 和 tkinter
  • tkinter 编写的程序如何打包成桌面软件
04-架构设计
  • 后台管理系统中角色管理、管理员管理、权限设置他们三者之间的关系
05-运维
  • redis启动失败
  • 实现自动备份宝塔项目及数据库到本地
  • 宝塔面板
01-宝塔
06-实战项目
  • 实战准备工作
02-项目记录
  • AI-Chat 模块技术文档
  • CRMChat 部署
07-面试专项
01-面试
  • 面试之自我介绍
  • CSS 面试
  • JS面试
  • React面试
  • Vue面试
  • RN 面试 · 题目清单
  • RN 面试 · 答案详解
  • RN 物流公司 · 题目清单
  • RN 物流公司 · 答案详解
  • Uniapp 面试
  • 带团队经验
12-react专项
  • 一个表格,某一行的某一列数据变动了,如何优化性能?
13-reactnative专项
  • RN 开发过程中,有遇到什么棘手的问题?你是如何解决的?
14-过往面试总结
  • 冷哥传媒 · 面试题
  • 沐腾科技 · 面试题
  • 字符频率排序
02-面试八股
  • 网络八股
  • 浏览器八股
  • 工程化八股
03-算法面试题
  • 前端高频手写题
  • 场景设计题
04-真实面试经历
  • 20251027 盾蓝科技 面试
  • 20251028 芝麻甜心 面试
08-工具
01-n8n
  • 初始n8n
  • 快速入门
  • 在宝塔中搭建n8n环境
  • n8n中常用节点
  • n8n和代码的区别
02-Trae
  • Figma MCP 配置指南
03-UI_UX_pro_max
  • UI_UX_pro_max的使用
04-开发环境
  • VMWare虚拟机安装
  • PowerShell 执行策略与编码设置
98-杂项
  • A股股票交易相关费用
99-手册
  • iOS 开发者账号申请流程及费用
  • 邓白氏编码申请及费用
  • App 推送通道申请及费用
  • App 开发收费项目清单
  • 注册AWS账号
  • 使用Amazon Polly实现文本转语音
  • Google 翻译 Key 申请与前端调用
  • 使用SmsForwarder转发短信

双指针与滑动窗口

02-高级算法2026/11/14

双指针把两两枚举压成单向扫描,滑动窗口是它在连续区间上的特化,指针总移动次数仍是 O(n)。

前端做字符串 diff 时要同步扫描两份文本,排查线上问题时要在日志里找一段连续时间满足错误率阈值。这两类需求都不需要回头看,指针只朝一个方向走,天然适合双指针与滑动窗口。

三种形态

形态指针方向典型题关键点
同向快慢都向右,速度不同判环、删除重复、移动零slow 写、fast 读
相向对撞一个从左、一个从右两数之和 II、三数之和、盛水、反转移动哪侧由单调性决定
分离双指针各自在自己的序列上前进合并两个有序数组、链表相交两条链互不干扰

快慢指针不要求有序,对撞指针才依赖单调性:数组有序时,左指针右移会让和变大,右指针左移会让和变小,才能确定该动哪一边。

快慢指针判环

Floyd 判圈算法让 slow 每次走一步、fast 每次走两步。slow 进环后,fast 已经在环内,两者距离每轮缩小 1,所以一定会相遇,不会因为相对速度大于 1 而跳过。

function hasCycle(head) {
  let slow = head, fast = head;
  while (fast && fast.next) {
    slow = slow.next;
    fast = fast.next.next;
    if (slow === fast) return true;
  }
  return false;
}

找环入口时,相遇后把其中一个指针放回 head,两者同速前进,再次相遇的位置就是入口。设 head 到入口距离 a、入口到相遇点 b、环长 b+c,则 2(a+b) = a+b+n(b+c),化简得 a = c + (n-1)(b+c),所以从 head 走 a 步与从相遇点走 c 步会同时到达入口。

function detectCycle(head) {
  let slow = head, fast = head;
  while (fast && fast.next) {
    slow = slow.next;
    fast = fast.next.next;
    if (slow === fast) {
      let p = head;
      while (p !== slow) { p = p.next; slow = slow.next; }
      return p;
    }
  }
  return null;
}

滑动窗口的条件与模板

适用条件是"连续子数组或子串"加"单调性":窗口右扩时指标单调变化,左缩时能恢复。求最长用 while (不满足) 收缩,求最短用 while (满足) 收缩,两个循环都只在指针移动时执行,总复杂度 O(n)。

function slidingWindow(s) {
  let left = 0, ans = 0;
  const window = new Map();
  for (let right = 0; right < s.length; right++) {
    扩张(window, s[right]);
    while (不满足条件(window)) {
      收缩(window, s[left]);
      left++;
    }
    更新答案(ans, right - left + 1);
  }
  return ans;
}

定长窗口与不定长窗口的区别在收缩时机:定长窗口大小固定为 k,右指针每走一步,左指针固定跟到 right - k + 1,用 if 减掉离开的元素即可;不定长窗口要靠 while 反复收缩,直到条件重新满足。

// 定长窗口:长度为 k 的子数组最大和
function maxSumWindow(nums, k) {
  let sum = 0, ans = 0;
  for (let right = 0; right < nums.length; right++) {
    sum += nums[right];
    if (right >= k) sum -= nums[right - k];
    if (right >= k - 1) ans = Math.max(ans, sum);
  }
  return ans;
}

为什么用 Map 而不是每次重算

如果每移动一次右指针都把窗口内元素重新统计一遍,单次是 O(k),整体退化成 O(nk)。用 Map 或计数数组增量维护,进入窗口加一、离开窗口减一,每次移动 O(1)。ASCII 字符可以用长度 128 的数组,Unicode 字符必须用 Map。

上一篇动态规划
下一篇已经是最后一篇
本页目录
三种形态快慢指针判环滑动窗口的条件与模板为什么用 Map 而不是每次重算常见误区常考题型无重复字符的最长子串最小覆盖子串长度最小的子数组三数之和盛最多水的容器关联阅读

只维护计数还不够,判断"窗口是否覆盖目标"时要避免每次遍历整个 Map。常见做法是额外维护 valid,当某个字符的计数刚好达到需求时加一,超过时不变,这样判断条件是 O(1)。

常见误区

很多人以为双指针必须有序,实际上快慢指针和分离双指针都不需要,只有对撞型依赖单调性。

很多人以为窗口不满足条件时用 if 收缩一次就够,实际上可能需要连续收缩多次,必须用 while。

很多人以为滑动窗口能解子序列问题,实际上它只能处理连续区间,子序列要换 DP 或贪心。

常考题型

无重复字符的最长子串

记录每个字符最后出现的位置,遇到重复就把 left 跳到上次位置加一。时间 O(n),空间 O(字符集)。

function lengthOfLongestSubstring(s) {
  const last = new Map();
  let left = 0, ans = 0;
  for (let right = 0; right < s.length; right++) {
    const c = s[right];
    if (last.has(c) && last.get(c) >= left) left = last.get(c) + 1;
    last.set(c, right);
    ans = Math.max(ans, right - left + 1);
  }
  return ans;
}

最小覆盖子串

先用 need 记录 t 的字符需求,右扩时更新窗口计数与 valid,覆盖后收缩并记录最短区间。时间 O(n),空间 O(字符集)。

function minWindow(s, t) {
  const need = new Map(), win = new Map();
  for (const c of t) need.set(c, (need.get(c) || 0) + 1);
  let left = 0, valid = 0, start = 0, len = Infinity;
  for (let right = 0; right < s.length; right++) {
    const c = s[right];
    win.set(c, (win.get(c) || 0) + 1);
    if (need.has(c) && win.get(c) === need.get(c)) valid++;
    while (valid === need.size) {
      if (right - left + 1 < len) { start = left; len = right - left + 1; }
      const d = s[left++];
      if (need.has(d) && win.get(d) === need.get(d)) valid--;
      win.set(d, win.get(d) - 1);
    }
  }
  return len === Infinity ? '' : s.slice(start, start + len);
}

长度最小的子数组

数组元素全为正,窗口和随右扩单调增加,一旦达到 target 就收缩并更新答案。时间 O(n),空间 O(1)。

function minSubArrayLen(target, nums) {
  let left = 0, sum = 0, ans = Infinity;
  for (let right = 0; right < nums.length; right++) {
    sum += nums[right];
    while (sum >= target) {
      ans = Math.min(ans, right - left + 1);
      sum -= nums[left++];
    }
  }
  return ans === Infinity ? 0 : ans;
}

三数之和

排序后固定第一个数,剩下两个用对撞指针找相反数,找到后跳过重复元素。时间 O(n²),空间 O(1)(不计排序栈)。

function threeSum(nums) {
  nums.sort((a, b) => a - b);
  const res = [];
  for (let i = 0; i < nums.length - 2; i++) {
    if (i > 0 && nums[i] === nums[i - 1]) continue;
    let l = i + 1, r = nums.length - 1;
    while (l < r) {
      const sum = nums[i] + nums[l] + nums[r];
      if (sum === 0) {
        res.push([nums[i], nums[l], nums[r]]);
        while (l < r && nums[l] === nums[l + 1]) l++;
        while (l < r && nums[r] === nums[r - 1]) r--;
        l++; r--;
      } else if (sum < 0) l++;
      else r--;
    }
  }
  return res;
}

盛最多水的容器

面积由较矮的一侧决定,移动较高的一侧只会让宽度变小、高度不变或更小,所以每次移动矮的一侧。时间 O(n),空间 O(1)。

function maxArea(height) {
  let l = 0, r = height.length - 1, ans = 0;
  while (l < r) {
    ans = Math.max(ans, Math.min(height[l], height[r]) * (r - l));
    if (height[l] < height[r]) l++;
    else r--;
  }
  return ans;
}

关联阅读

  • 排序算法:三数之和、合并区间都先把数组排好序
  • 二分查找:同样利用单调性,一个缩区间,一个缩窗口
  • 本目录说明:高级算法的整体学习路径