【异或消除成对元素】
核心用途:状态消除 / 状态压缩
适用场景: 数组中除一个(或少数)元素外,其余元素出现次数满足某种“可抵消性”(如偶数次), 要求 O(n) 时间 + O(1) 额外空间。
核心性质:
a ^ a = 0(相同状态可相互抵消)a ^ 0 = a(零为幺元)- 交换律、结合律成立(顺序无关)
- 所有成对出现的元素最终都会被消掉
典型题:
- 136 只出现一次的数字
- 只出现一次的数字 II / III(次数变化、本质仍是状态统计)
滑动窗口解决“窗口性质单调”的问题; 前缀思想解决“当前状态与历史状态做差”的问题。
核心作用: 快速计算任意区间结果,或将问题转化为「当前状态 vs 历史状态」。
基本形式:
- 任意区间结果 = 两个前缀结果之差 / 之积 / 之比较
- 当前答案往往依赖某个“历史最优 / 历史累计状态”
常见变形:
- 前缀和
- 前缀积
- 前缀最值
- 滚动前缀(状态压缩)
典型题:
- 最大子数组和
- 子数组和为 k
- 区间和 / 区间统计
- 除自身以外数组的乘积
核心前提: 窗口内的“约束条件”具有单调性。
常见单调性表现:
- 窗口变大 → 某个指标只会变大或变小
- 一旦违反条件 → 通过缩小窗口一定可以缓解
- 右扩 + 左缩 能逐步逼近最优解
典型场景:
- 非负数组的区间和
- 去重 / 计数类窗口
- 满足 / 不满足某种限制的最短或最长子数组
非适用情况:
- 窗口内状态不单调(如包含负数)
- 扩大窗口可能让结果变好也可能变差
- 无法判断“缩窗口是否有意义”
当题目限制常数级额外空间时,本质是在问: 状态应该存在哪里?
常见可借用的“存储载体”:
- 输入数组本身(原地修改 / 原地哈希)
- 输出数组(若题目不将其计入额外空间)
- 常数个变量(滚动状态 / 状态压缩)
典型思路:
- 值域有限 → 用索引表达存在性(Index as Hash)
- 前缀 / 后缀状态 → 用变量滚动维护
- 输出数组 → 作为中间结果缓存
典型题:
- 41 缺失的第一个正数(原地哈希)
- 238 除自身以外数组的乘积(输出数组复用)
- 数组中重复 / 缺失元素相关问题
核心思想:用“边界”描述进度,而不是用 visited 描述状态。
适用场景:
- 按固定顺序遍历矩阵
- 每一轮都会消耗一整行或一整列
- 不需要回退访问
核心状态: top / bottom / left / right 表示当前尚未遍历的矩形区域
不变量: 只遍历边界 每遍历一次,边界必然收缩 只要 top <= bottom && left <= right,就仍有合法区域
典型题:
- 螺旋矩阵 I / II
- 矩阵旋转 在这里由于矩阵时方阵导致自己疏忽了,在循环中还是采用的固定的0和n,i,这里由于考虑到边,遍历时应该考虑 层 + 偏移量 ,i不应该时绝对的,而是相对当前边的起点
- 矩阵置零(第一行第一列作标记)
核心思想:通过移动策略让多个指针处于“等价状态”,而不是显式记录状态。
当两个指针的起点、路径或长度不一致时,可以通过: 调整起点 、切换路径 、控制移动节奏 使它们在某一时刻满足可比较性(对齐), 从而用 O(1) 额外空间 解决问题。
在链表中,这种思想通常体现为: 显式对齐(先消除长度差,再同步前进) 隐式对齐(走到末尾后切换到另一条链表)
是否相交,本质等价于:是否存在一个时刻两个指针指向同一状态; 不相交也可视为在 null 状态相交。
典型场景:
- 链表相交
- 快慢指针(是否成环 / 找入口)
- 删除倒数第 N 个节点
- 有序数组的双指针问题
双指针可用于找到链表等的中点, 典型例题:
- 回文链表,找到中点后反转后面的链表
递归 = 把“大问题的答案”建立在“更小规模同类问题的答案”之上
写递归时,只需要做 3 件事:
- 递归函数的“语义”必须先确定,就是递归函数的返回值代表什么信息;
- 假设“子问题已经正确”,然后如何利用这两个返回值,构造当前层的结果
- 当前层只做“合并结果”
递归返回值 = 你这道题“需要向上汇报什么信息”
举例子:
94 二叉树的中序遍历:递归语义:负责把以 root 为根的子树的中序遍历结果,追加到结果容器中
104 二叉树的最大深度:递归语义:返回「以 root 为根的子树的最大深度」
543 二叉树的直径:要求得到直径,就需要得到当前节点的左子树的最大深度和右子树的最大深度,返回以 root 为根的子树的最大左子树深度加上最大右子树深度
437

递归可以用于处理这种情况:传入两个数组,现在要对短的数组进行处理,可以参考 第4题 执行一次递归来实现处理短的数组,不必要进行判断分支
public double findMedianSortedArrays(int[] nums1, int[] nums2) {
// 保证 nums1 是较短的数组
if (nums1.length > nums2.length) {
return findMedianSortedArrays(nums2, nums1);
}
}在动态数据中,维护前 k 大 / 前 k 小 → 堆是最自然的数据结构
贪心算法 = 在每一步,只做「当前看起来最优」的选择, 并且确信: 这个选择不会破坏全局最优解
真正的贪心算法三要素(非常重要) 一个问题能用贪心,必须满足 3 个条件:
-
局部最优选择性质(最核心) 存在一种选择规则: 当前最优选择,一定存在于某个全局最优解中.现在做的选择,不会让最优解变“更差”
-
最优子结构 做出一个选择后,剩下的问题仍然是一个“同类型的最优问题” ,这点和 DP 很像,但区别在于: DP:要算所有子问题;贪心:只算一个方向
-
不需要回退(单调性) 一旦做了选择,就永远不后悔.这通常体现在:指针只向前,区间只扩展,状态只变“更好”.