1900 字
10 分钟
力扣算法部分的一些笔记

双指针#

双指针主要分为三大门派

1. 对撞指针(左右指针)#

适用场景:主要用于有序数组字符串。常见于查找两数之和、反转字符串、判断回文串、以及像“盛最多水的容器”这种寻找边界极值的问题。

func collisionPointers(nums []int) {
left := 0
right := len(nums) - 1
for left < right { // 根据题意,有时可能是 left <= right
// 1. 计算当前左右指针组合的状态/结果
// ...
// 2. 根据状态决定移动哪个指针
if /* 需要更大的值 */ {
left++
} else if /* 需要更小的值 */ {
right--
} else {
// 找到目标,执行后续逻辑(如记录结果、去重或退出)
// left++
// right--
}
}
}

2. 快慢指针#

应用场景:处理链表问题(找中点、判断环)、数组原地去重、移除/移动特定元素(如“移动零”)。

func fastSlowPointers(nums []int) int {
slow := 0
for fast := 0; fast < len(nums); fast++ {
if /* 满足特定条件,需要保留当前 fast 指向的元素 */ {
// 将 fast 的值赋给 slow(或进行交换)
nums[slow] = nums[fast]
slow++ // slow 推进,准备接收下一个有效元素
}
}
// 返回有效元素的长度或最终 slow 的位置
return slow
}

3. 同向指针(在下面滑动窗口说明)#


滑动窗口#

适用场景: “连续子数组/子串” + 最值/满足条件#

像「最大/最长」、「最小/最短」、「刚好满足某个条件(如和为 K,包含特定字符等)」

注意:

  1. 如果是“子序列”(Subsequence),通常不用滑动窗口,因为子序列不要求连续。

  2. 注意是否有负数,如果数组包含负数,滑动窗口通常会失效(因为窗口扩大时和不一定增加)

滑动窗口主要分为两类:可变窗口(求最长/最短)和固定窗口

1. 可变窗口(最通用)#

核心思想:right 主动扩张探索,left 被动收缩调整。

func slidingWindowVariable(s string) int {
// 1. 定义数据结构维护窗口内的状态 (通常是 map, slice, 计数器等)
window := make(map[byte]int)
left, right := 0, 0
res := 0 // 或者 math.MaxInt32,取决于求最长还是最短
for right < len(s) {
// 2. 将 s[right] 加入窗口,更新状态
c := s[right]
window[c]++
// 3. 判断当前窗口状态是否触发了“收缩”条件
for /* 满足收缩条件 (例如窗口内出现了重复字符,或者和超出了限制) */ {
// (可选) 在此处更新求【最短】子串的答案
// 4. 准备将 s[left] 移出窗口,更新状态
d := s[left]
window[d]--
left++ // left 右移,窗口收缩
}
// 5. 在此处更新求【最长】子串的答案 (因为此时窗口一定满足条件)
// res = max(res, right - left + 1)
right++ // right 右移,窗口继续扩张
}
return res
}

2. 固定窗口#

核心思想:窗口大小恒定为 k,每次 right 进一个,left 必须出一个。

func slidingWindowFixed(nums []int, k int) int {
left, right := 0, 0
// 定义状态变量...
for right < len(nums) {
// 1. 将 nums[right] 加入窗口,更新状态
// 2. 如果窗口长度达到 k,说明窗口已经成型
if right - left + 1 == k {
// 3. 记录或更新答案
// 4. 将 nums[left] 移出窗口,为下一个新元素腾位置
left++
}
right++
}
return res
}

前缀和#

适用场景:#

  • “连续子数组” + 求和/积

  • 区间查询

    [i, j] 内的元素之和,就是两个连续子数组相减

  • 连续子数组的“特定数学关系”

    像子数组和等于 K…

注意

为了避免处理边界条件

➕:在最前面补一个 0,即前缀和数组的长度为原数组长度 + 1

✖️:令第一个为1


回溯#


二分查找#

本质: 二分查找的本质不是“全局有序”,而是“两段性”。只要能找到一个判断条件,将当前的搜索空间一分为二(一半满足条件,另一半必定不满足),就可以使用二分。

时间复杂度: O(log n)。

根据题型特点,二分查找主要分为以下四种场景:

1. 标准精确查找#

  • 适用场景: 数组全局有序,且元素不重复。要求寻找某个确切的 target

2. 寻找左右边界#

  • 适用场景: 数组全局有序,但包含重复元素。要求寻找 target 第一次出现(左边界)或最后一次出现(右边界)的位置。(如 LeetCode 34)
  • 特征: 找到 target不停手,而是记录当前位置,并强行收缩另一边的区间,继续向目标边界逼近。
func binarySearchTemplate1(nums []int, target int) int {
left, right := 0, len(nums)-1
// 可选:ans := -1 // 如果是找边界/找极值,用外部变量记录最近一次符合条件的答案
for left <= right {
mid := left + (right - left)/2
if nums[mid] == target {
return mid
// 如果是找左边界:ans = mid; right = mid - 1
// 如果是找右边界:ans = mid; left = mid + 1
} else if nums[mid] < target {
left = mid + 1 // target 在右侧,砍掉左半边
} else {
right = mid - 1 // target 在左侧,砍掉右半边
}
}
return -1 // 或 return ans
}

3. 寻找极值(非全局有序)#

  • 适用场景: 数组“局部有序”(如旋转数组)或者存在“高低起伏”(如寻找峰值)。(如 LeetCode 153、33)
  • 特征: 通常没有明确的 target 来匹配,而是通过比较 nums[mid]nums[left]nums[right] 的相对大小,判断出哪里发生了“断层”或“转折”,从而决定收缩方向。
func binarySearchTemplate2(nums []int) int {
left, right := 0, len(nums)-1
// 注意:这里绝对不能写等号,否则在某些收缩逻辑下必死循环
for left < right {
mid := left + (right - left)/2
if nums[mid] < nums[right] {
// 举例:判断条件满足,说明右半边是平稳上坡,极值不可能在右边。
// 但 mid 本身可能是极值,绝不能踢掉!
right = mid
} else {
// 举例:发生了断层跌落,极值必定在 mid 右侧的悬崖下。
// mid 本身处于悬崖上方,已被洗脱嫌疑,果断跨过!
left = mid + 1
}
}
// 循环结束时,left 和 right 撞在一起,就是那个唯一的极值
return nums[left]
}

贪心算法#

1. 基础匹配#

  • 适用题目:《分发饼干》(455)
  • 技术动作:双数组排序 + 双指针单向匹配。

2. 覆盖范围#

  • 适用题目:《跳跃游戏 I/II》(55, 45)、《划分字母区间》(763)

  • 特征:别管每一步怎么跳,死死盯住“最远能覆盖到哪”。

  • 技术动作

    • 维护一个动态的 maxReach(未来最远潜力)和一个 currentEnd(当前这一跳的边界)。
    • 遍历时不断扩张 maxReach
    • 核心触发器:当 i == currentEnd 时,被迫结算(跳跃次数+1,或切分出一段区间),并将边界更新为新的 maxReach

3. 区间调度#

  • 适用题目:《合并区间》(56)、《无重叠区间》(435)、《用最少数量的箭引爆气球》(452)
  • 通关口诀:求合并看左边界,求消减看右边界
  • 技术动作
    • 合并类(如 56 题):按左边界排序。贪心策略是“能吞就吞”,右边界不断取最大值 max(last[1], curr[1])
    • 消减/射箭类(如 435, 452 题):强烈建议按右边界排序!这是降维打击。
    • 右边界排序的极简逻辑:谁结束得早,就先安排谁。只要发生重叠(left <= end),后来的那个必定是个累赘,直接干掉(count++);不重叠才更新安全边界 end

力扣算法部分的一些笔记
https://sheep44044.github.io/posts/算法/力扣算法部分的一些笔记/
作者
sheep44044
发布于
2026-04-11
许可协议
CC BY-NC-SA 4.0