双指针是用两个整数变量协同遍历数组的技巧,分相向移动、同向快慢、滑动窗口三类;关键在移动逻辑与边界控制,而非字面“指针”。

双指针不是一种算法,是种控制访问范围的技巧
Go 里没有叫 TwoPointer 的内置类型或函数,它只是用两个 int 变量(比如 left、right 或 slow、fast)来协同遍历切片或数组的模式。关键不在“指针”字面意思,而在它们怎么动、为什么这么动。
- 相向移动(
left从头增,right从尾减):适合已排序数组找和、盛水、反转等,靠单调性剪枝 - 同向快慢(
slow停留填结果,fast扫描过滤):适合去重、移除元素、找中间节点,本质是覆盖式重写 - 滑动窗口类(
left定界收缩,right主动扩张):需要额外状态记录(如哈希表计数),不是纯双变量就能闭环
环形队列用双指针时,空/满判断必须错开一个位置
用 []T + head/tail 实现循环队列,最常崩在判空和判满都用 head == tail——这会让两者无法区分。Go 没有引用传递陷阱,但取模运算稍一疏忽就溢出或越界。
- 底层数组长度必须设为
n+1(逻辑容量为n),预留一个空位 - 判空:直接
head == tail;判满:必须用(tail + 1) % cap == head - 每次访问数组前都要对索引取模:
data[tail%cap],不能只在更新时取模 - 长度计算别硬套公式,用
(tail - head + cap) % cap更稳,避免负数取模歧义
快排分区用双指针,基准值交换时机决定边界是否稳定
Go 实现快速排序时,如果用类似 partition 函数做原地分区,left/right 移动逻辑稍错,就会漏元素、死循环或分错界。尤其要注意基准值最后落点是否参与下一轮递归。
- 推荐把基准值先换到末尾(
arr[pivotIndex], arr[len(arr)-1] = arr[len(arr)-1], arr[pivotIndex]),避免干扰扫描 - 扫描时
right初始为len(arr)-2,不是len(arr)-1,否则会和基准重复比较 - 退出循环后,
left是第一个 ≥ 基准的位置,此时再把基准换过来:arr[left], arr[len(arr)-1] = arr[len(arr)-1], arr[left] - 递归区间为
arr[:left]和arr[left+1:],left本身是基准,不参与后续排序
LeetCode 移除元素题,快慢指针别在循环里反复赋值
removeElement 这类题用双指针本意是 O(1) 额外空间 + 单次遍历,但有人写成每轮都 nums[slow] = nums[fast] 再 slow++,看似没问题,实际当 fast 指向要删的值时,slow 未动却仍执行了赋值——等于把脏数据又写回去了。
- 正确做法:只在
nums[fast] != val时才赋值并推进slow - 别用
for range,它拷贝的是索引值,无法动态控制fast步长 - 返回值是新长度,不是原切片,调用方看到的
nums后半段仍是旧值(Go 切片截断不擦除内存) - 测试时别只看打印结果,要检查返回长度和前 N 个元素是否真被保留
双指针真正难的不是写法,而是想清楚「谁动、谁不动、动的条件是什么、停在哪」——这三个变量一旦错一个,整个逻辑就偏了。尤其在 Go 里,没有指针算术,全靠整数索引模拟,越简单越要盯紧边界。
golang免费学习笔记(深入):立即使用
在学习笔记中,你将探索golang的核心概念和高级技巧!











