常见的算法技巧分类
1、指针 / 索引技巧
-
双指针(Two Pointers)
- 用途:处理数组、链表中的遍历、搜索、排序相关问题,通过两个指针协同工作减少时间复杂度。
- 常见场景:
- 有序数组的两数之和(左右指针向中间靠拢);
- 移除元素(快慢指针,快指针遍历、慢指针记录有效元素);
- 反转数组 / 字符串(首尾指针交换)。
-
滑动窗口(Sliding Window)
- 用途:处理字符串或数组中 “连续子序列” 问题(如最长 / 最短子串、子数组和)。
- 核心:用左右指针维护一个 “窗口”,根据条件动态调整窗口大小,避免重复计算。
- 示例:无重复字符的最长子串、最小覆盖子串。
-
快慢指针(Floyd's Tortoise and Hare)
- 用途:检测链表环、寻找环的入口、找到链表中点等。
- 原理:快指针每次走 2 步,慢指针每次走 1 步,利用速度差判断环或定位特定节点。
2、哈希表 / 集合技巧
- 哈希表(Hash Table)
- 用途:快速查找、存储键值对关系,将时间复杂度从 O (n) 降至 O (1)(平均情况)。
- 常见场景:
- 两数之和(用哈希表存储已遍历元素的索引);
- 统计元素出现频率(如多数元素问题);
- 判断元素是否存在(如存在重复元素)。
- 哈希集合(Hash Set)
- 用途:去重、快速判断元素是否存在(如环形链表中判断节点是否访问过)。
3、递归与回溯
-
递归(Recursion)
- 用途:将复杂问题分解为子问题,适合解决具有递归结构的问题(如树、图的遍历)。
- 示例:二叉树的前 / 中 / 后序遍历、斐波那契数列(需优化重复计算)。
-
回溯(Backtracking)
- 用途:解决 “排列、组合、子集、切割、棋盘” 等需要穷举所有可能的问题,通过 “尝试 - 撤销 - 再尝试” 剪枝无效路径。
- 示例:全排列、组合总和、N 皇后问题。
4、分治算法(Divide and Conquer)
- 核心:将问题拆分为多个子问题,分别解决后合并结果。
- 示例:
- 归并排序、快速排序(拆分数组后排序合并);
- 求最大子数组和(分治 + 递归);
- 二叉树的最近公共祖先(拆分左右子树查找)。
5、贪心算法(Greedy)
- 核心:每次选择局部最优解,最终得到全局最优解(需证明问题满足 “贪心选择性质”)。
- 示例:
- 区间调度问题(选择结束最早的区间);
- 零钱兑换(优先用最大面额硬币,限于特定币种);
- 跳跃游戏(每次跳最远的位置)。
6、动态规划(Dynamic Programming, DP)
- 核心:通过存储子问题的解(DP 表),避免重复计算,解决具有 “重叠子问题” 和 “最优子结构” 的问题。
- 常见场景:
- 斐波那契数列(用数组存储中间结果);
- 最长递增子序列(LIS)、最长公共子序列(LCS);
- 背包问题(0-1 背包、完全背包);
- 打家劫舍(状态转移方程:
dp[i] = max(dp[i-1], dp[i-2] + nums[i]))。
7、位运算技巧
- 用途:高效处理整数的二进制表示,解决与 “状态压缩、奇偶性、数值计算” 相关的问题。
- 常见操作:
- 判断奇偶(
n & 1); - 清零最低位的 1(
n & (n-1),用于统计 1 的个数); - 交换两个数(
a = a ^ b; b = a ^ b; a = a ^ b); - 子集枚举(用二进制位表示元素是否选中)。
- 判断奇偶(
8、其他实用技巧
-
前缀和与后缀和
- 用途:快速计算数组中任意子数组的和(前缀和
prefix[i] = prefix[i-1] + nums[i])。 - 示例:和为 K 的子数组、二维矩阵的子矩阵和。
- 用途:快速计算数组中任意子数组的和(前缀和
-
单调栈(Monotonic Stack)
- 用途:解决 “下一个更大 / 更小元素”“柱状图中最大的矩形” 等问题,栈内元素保持单调递增 / 递减。
-
并查集(Union-Find/Disjoint Set)
- 用途:处理 “动态连通性” 问题(如判断图中两点是否连通、合并集合),优化后时间复杂度接近 O (1)。
- 示例:岛屿数量(合并相邻陆地)、冗余连接。
这些技巧的核心是 “针对特定问题场景,选择合适的工具降低复杂度”。实际应用中,很多问题需要多种技巧结合(如滑动窗口 + 哈希表、动态规划 + 贪心),需通过练习熟悉其适用场景。
常见的算法技巧示例
1. 双指针(Two Pointers)
核心思想:使用两个指针(通常在数组或链表中)从不同位置(如头部、尾部、相邻位置)出发,根据条件移动指针,高效解决问题。
适用场景:有序数组去重、两数之和、反转数组等。
示例:有序数组去重(原地删除重复元素)
public int removeDuplicates(int[] nums) {
if (nums.length == 0) return 0;
int slow = 0; // 慢指针:指向不重复元素的最后一个位置
for (int fast = 1; fast < nums.length; fast++) {
if (nums[fast] != nums[slow]) {
slow++; // 只有当元素不同时,慢指针才移动
nums[slow] = nums[fast]; // 覆盖重复元素
}
}
return slow + 1; // 有效长度
}
2. 哈希表(Hash Table)
核心思想:利用哈希函数将键映射到存储位置,实现 O (1) 平均时间复杂度的插入、查询和删除,常用于快速查找、去重或计数。
适用场景:两数之和、判断重复元素、字符频次统计等。
示例:两数之和(返回数组中两数之和为目标值的索引)
public int[] twoSum(int[] nums, int target) {
Map<Integer, Integer> map = new HashMap<>();
for (int i = 0; i < nums.length; i++) {
int complement = target - nums[i];
if (map.containsKey(complement)) {
return new int[]{map.get(complement), i}; // 找到互补元素
}
map.put(nums[i], i); // 存储元素与索引
}
throw new IllegalArgumentException("No solution");
}
3. 二分查找(Binary Search)
核心思想:在有序数组中,通过不断将目标值与中间元素比较,缩小查找范围(每次排除一半元素)。
适用场景:查找目标值、寻找插入位置、求峰值等。
示例:二分查找目标值
public int search(int[] nums, int target) {
int left = 0, right = nums.length - 1;
while (left <= right) {
int mid = left + (right - left) / 2; // 避免溢出
if (nums[mid] == target) {
return mid;
} else if (nums[mid] < target) {
left = mid + 1; // 目标在右半部分
} else {
right = mid - 1; // 目标在左半部分
}
}
return -1; // 未找到
}
4. 前缀和(Prefix Sum)
核心思想:预处理数组,计算前i个元素的和(前缀和数组),快速求解任意子数组的和(时间复杂度从O(n)降为O(1))。
适用场景:子数组和问题、区间和查询等。
示例:求子数组和等于 k 的次数
public int subarraySum(int[] nums, int k) {
int count = 0;
int prefixSum = 0;
Map<Integer, Integer> map = new HashMap<>();
map.put(0, 1); // 前缀和为0的情况(子数组从0开始)
for (int num : nums) {
prefixSum += num;
// 若存在前缀和 = prefixSum - k,则说明中间子数组和为k
count += map.getOrDefault(prefixSum - k, 0);
map.put(prefixSum, map.getOrDefault(prefixSum, 0) + 1);
}
return count;
}
5. 滑动窗口(Sliding Window)
核心思想:维护一个 “窗口”(子数组或子字符串),通过移动窗口的左右边界,动态调整窗口范围,解决连续子序列问题。
适用场景:最长无重复子串、最小覆盖子串、子数组和等。
示例:长度最小的子数组(找到和≥target 的最短连续子数组)
public int minSubArrayLen(int target, int[] nums) {
int left = 0; // 窗口左边界
int sum = 0;
int minLen = Integer.MAX_VALUE;
for (int right = 0; right < nums.length; right++) {
sum += nums[right]; // 右边界右移,扩大窗口
// 当窗口和≥target时,尝试缩小左边界
while (sum >= target) {
minLen = Math.min(minLen, right - left + 1);
sum -= nums[left];
left++;
}
}
return minLen == Integer.MAX_VALUE ? 0 : minLen;
}
6. 树的遍历技巧(DFS/BFS)
树的遍历是处理树结构的基础,常见的有深度优先搜索(DFS)和广度优先搜索(BFS),衍生出前序、中序、后序遍历(DFS)和层序遍历(BFS)。
示例 1:二叉树的层序遍历(BFS)
public List<List<Integer>> levelOrder(TreeNode root) {
List<List<Integer>> result = new ArrayList<>();
if (root == null) return result;
Queue<TreeNode> queue = new LinkedList<>();
queue.add(root);
while (!queue.isEmpty()) {
int levelSize = queue.size(); // 当前层的节点数
List<Integer> level = new ArrayList<>();
for (int i = 0; i < levelSize; i++) {
TreeNode node = queue.poll();
level.add(node.val);
if (node.left != null) queue.add(node.left);
if (node.right != null) queue.add(node.right);
}
result.add(level); // 加入当前层的结果
}
return result;
}
示例 2:二叉树的后序遍历(DFS,递归)
public List<Integer> postorderTraversal(TreeNode root) {
List<Integer> result = new ArrayList<>();
dfs(root, result);
return result;
}
private void dfs(TreeNode node, List<Integer> result) {
if (node == null) return;
dfs(node.left, result); // 左
dfs(node.right, result); // 右
result.add(node.val); // 根
}
7. 位运算(Bit Manipulation)
核心思想:直接操作二进制位,利用位运算的特性(如与、或、异或、左移、右移)解决问题,效率极高(时间 / 空间复杂度低)。
适用场景:二进制计算、去重(如数组中只出现一次的数字)、状态压缩等。
示例:数组中只出现一次的数字(其他数字均出现两次)
public int singleNumber(int[] nums) {
int result = 0;
for (int num : nums) {
result ^= num; // 异或:a^a=0,0^a=a,且满足交换律
}
return result; // 最终结果为只出现一次的数字
}
8. 贪心算法(Greedy Algorithm)
核心思想:每次选择当前最优解(局部最优),最终期望得到全局最优解(需证明问题具有 “贪心选择性质”)。
适用场景:区间调度、霍夫曼编码、零钱兑换(特定条件下)等。
示例:无重叠区间(选择最多不重叠的区间)
public int eraseOverlapIntervals(int[][] intervals) {
if (intervals.length == 0) return 0;
// 按区间结束时间排序
Arrays.sort(intervals, (a, b) -> a[1] - b[1]);
int count = 1; // 至少选择一个区间
int end = intervals[0][1];
for (int[] interval : intervals) {
int start = interval[0];
if (start >= end) { // 不重叠,选择当前区间
count++;
end = interval[1];
}
}
return intervals.length - count; // 需删除的区间数
}
9. 递归与记忆化(Recursion + Memoization)
核心思想:通过递归将问题分解为子问题,同时用缓存(如哈希表)存储已解决的子问题结果,避免重复计算(本质是动态规划的 “自顶向下” 实现)。适用场景:斐波那契数列、爬楼梯、零钱兑换等具有重叠子问题的场景。
示例:斐波那契数列(记忆化优化)
public int fib(int n) {
Map<Integer, Integer> memo = new HashMap<>();
return helper(n, memo);
}
private int helper(int n, Map<Integer, Integer> memo) {
if (n <= 1) return n;
if (memo.containsKey(n)) return memo.get(n); // 直接返回缓存结果
int res = helper(n - 1, memo) + helper(n - 2, memo);
memo.put(n, res); // 缓存子问题结果
return res;
}
10. 堆(Heap)/ 优先队列(Priority Queue)
核心思想:利用堆的特性(如大顶堆 / 小顶堆)快速获取极值(最大值 / 最小值),常用于动态维护 Top K 元素或处理带权重的调度问题。
适用场景:前 K 个高频元素、数据流中的中位数、合并 K 个有序链表等。
示例:前 K 个高频元素
public int[] topKFrequent(int[] nums, int k) {
// 1. 统计元素频次
Map<Integer, Integer> freqMap = new HashMap<>();
for (int num : nums) {
freqMap.put(num, freqMap.getOrDefault(num, 0) + 1);
}
// 2. 小顶堆:只保留前k个高频元素(堆顶为当前最小的高频元素)
PriorityQueue<Map.Entry<Integer, Integer>> heap = new PriorityQueue<>(
(a, b) -> a.getValue() - b.getValue()
);
for (Map.Entry<Integer, Integer> entry : freqMap.entrySet()) {
heap.add(entry);
if (heap.size() > k) {
heap.poll(); // 超过k个则移除最小的
}
}
// 3. 提取结果
int[] result = new int[k];
for (int i = k - 1; i >= 0; i--) {
result[i] = heap.poll().getKey();
}
return result;
}
11. 动态规划(Dynamic Programming, DP)
核心思想:将复杂问题分解为重叠子问题,通过存储子问题的解(DP 表)避免重复计算,从底向上推导最终结果。
适用场景:斐波那契数列、最长递增子序列、背包问题、编辑距离等。
示例:爬楼梯(每次可爬 1 或 2 阶,求到第 n 阶的方法数)
public int climbStairs(int n) {
if (n <= 2) return n;
int[] dp = new int[n + 1];
dp[1] = 1;
dp[2] = 2;
for (int i = 3; i <= n; i++) {
dp[i] = dp[i - 1] + dp[i - 2]; // 递推公式:到i阶 = 到i-1阶+1步 或 到i-2阶+2步
}
return dp[n];
}
12. 单调栈(Monotonic Stack)
核心思想:维护一个栈内元素单调递增或递减的栈,用于高效解决 “下一个更大 / 更小元素” 等问题,避免暴力遍历。
适用场景:每日温度、下一个更大元素、柱状图中最大的矩形等。
示例:下一个更大元素 I(nums1 是 nums2 的子集,求 nums1 中每个元素在 nums2 中的下一个更大元素)
public int[] nextGreaterElement(int[] nums1, int[] nums2) {
Map<Integer, Integer> nextGreater = new HashMap<>();
Stack<Integer> stack = new Stack<>(); // 单调递减栈
for (int num : nums2) {
// 当栈顶元素小于当前元素时,当前元素是栈顶元素的下一个更大元素
while (!stack.isEmpty() && stack.peek() < num) {
nextGreater.put(stack.pop(), num);
}
stack.push(num);
}
// 处理nums1
int[] result = new int[nums1.length];
for (int i = 0; i < nums1.length; i++) {
result[i] = nextGreater.getOrDefault(nums1[i], -1);
}
return result;
}
13. 回溯法(Backtracking)
核心思想:通过递归尝试所有可能的解,当发现当前路径无效时,回溯到上一步继续尝试其他路径(“试错 + 回退”)。
适用场景:排列组合、子集、全排列、数独求解等。
示例:生成所有子集
public List<List<Integer>> subsets(int[] nums) {
List<List<Integer>> result = new ArrayList<>();
backtrack(result, new ArrayList<>(), nums, 0);
return result;
}
private void backtrack(List<List<Integer>> result, List<Integer> temp, int[] nums, int start) {
result.add(new ArrayList<>(temp)); // 加入当前子集
for (int i = start; i < nums.length; i++) {
temp.add(nums[i]); // 选择当前元素
backtrack(result, temp, nums, i + 1); // 递归(下一个元素从i+1开始,避免重复)
temp.remove(temp.size() - 1); // 回溯(移除最后一个元素)
}
}
14. 分治法(Divide and Conquer)
核心思想:将问题分解为规模更小的子问题,递归解决子问题后合并结果,适合处理具有 “分治特性” 的问题(如问题可拆分、子问题独立、合并简单)。
适用场景:归并排序、快速排序、求最大子数组和、二叉树问题等。
示例:求最大子数组和(LeetCode 53)
public int maxSubArray(int[] nums) {
return divide(nums, 0, nums.length - 1);
}
private int divide(int[] nums, int left, int right) {
if (left == right) return nums[left]; // 基线条件:单个元素
int mid = left + (right - left) / 2;
// 分:左半部分最大子数组和、右半部分最大子数组和
int leftMax = divide(nums, left, mid);
int rightMax = divide(nums, mid + 1, right);
// 合:跨越中点的最大子数组和
int crossMax = cross(nums, left, mid, right);
return Math.max(Math.max(leftMax, rightMax), crossMax);
}
private int cross(int[] nums, int left, int mid, int right) {
int leftSum = Integer.MIN_VALUE;
int sum = 0;
for (int i = mid; i >= left; i--) { // 从中间向左扩展
sum += nums[i];
leftSum = Math.max(leftSum, sum);
}
int rightSum = Integer.MIN_VALUE;
sum = 0;
for (int i = mid + 1; i <= right; i++) { // 从中间向右扩展
sum += nums[i];
rightSum = Math.max(rightSum, sum);
}
return leftSum + rightSum;
}
15. 并查集(Union-Find / Disjoint Set)
核心思想:高效管理和合并 “集合”,支持快速查询两个元素是否在同一集合、合并两个集合(路径压缩和按秩合并优化后,时间复杂度接近O(1))。适用场景:连通分量问题(如岛屿数量)、朋友圈、判断图中是否有环等。
示例:岛屿数量(统计连通的陆地数量)
public int numIslands(char[][] grid) {
if (grid == null || grid.length == 0) return 0;
int rows = grid.length;
int cols = grid[0].length;
UnionFind uf = new UnionFind(grid);
// 方向数组:右、下
int[][] dirs = {{0, 1}, {1, 0}};
for (int i = 0; i < rows; i++) {
for (int j = 0; j < cols; j++) {
if (grid[i][j] == '1') {
for (int[] dir : dirs) {
int x = i + dir[0];
int y = j + dir[1];
if (x < rows && y < cols && grid[x][y] == '1') {
uf.union(i * cols + j, x * cols + y); // 合并相邻陆地
}
}
}
}
}
return uf.count;
}
// 并查集实现
class UnionFind {
int[] parent;
int count; // 连通分量数量
public UnionFind(char[][] grid) {
int rows = grid.length;
int cols = grid[0].length;
parent = new int[rows * cols];
for (int i = 0; i < rows; i++) {
for (int j = 0; j < cols; j++) {
if (grid[i][j] == '1') {
parent[i * cols + j] = i * cols + j; // 自身为父节点
count++; // 初始每个陆地是独立分量
}
}
}
}
// 查找根节点(路径压缩)
public int find(int x) {
if (parent[x] != x) {
parent[x] = find(parent[x]);
}
return parent[x];
}
// 合并两个集合(按秩合并,可选)
public void union(int x, int y) {
int rootX = find(x);
int rootY = find(y);
if (rootX != rootY) {
parent[rootY] = rootX;
count--; // 合并后分量数减1
}
}
}
16. 拓扑排序(Topological Sort)
核心思想:针对有向无环图(DAG),按照依赖关系(如 “先修课” 必须在 “后修课” 之前)将节点排序,确保所有前驱节点都排在后继节点之前。
适用场景:课程安排、任务调度、依赖关系处理等。
示例:课程表(判断是否能完成所有课程,即图中无环)
public boolean canFinish(int numCourses, int[][] prerequisites) {
// 1. 构建邻接表和入度数组
List<List<Integer>> adj = new ArrayList<>();
int[] inDegree = new int[numCourses];
for (int i = 0; i < numCourses; i++) {
adj.add(new ArrayList<>());
}
for (int[] p : prerequisites) {
int course = p[0];
int pre = p[1];
adj.get(pre).add(course); // 前驱 -> 后继
inDegree[course]++; // 后继入度+1
}
// 2. 入度为0的节点入队(无依赖的课程)
Queue<Integer> queue = new LinkedList<>();
for (int i = 0; i < numCourses; i++) {
if (inDegree[i] == 0) {
queue.add(i);
}
}
// 3. 拓扑排序
int count = 0;
while (!queue.isEmpty()) {
int curr = queue.poll();
count++; // 完成一门课程
for (int next : adj.get(curr)) {
inDegree[next]--; // 依赖减少
if (inDegree[next] == 0) { // 无依赖时入队
queue.add(next);
}
}
}
return count == numCourses; // 所有课程都能完成则无环
}
17. 前缀树(Trie)
核心思想:一种多叉树结构,用于存储字符串集合,支持高效的前缀查找、插入和删除操作(时间复杂度与字符串长度相关)。
适用场景:自动补全、拼写检查、前缀匹配等。
示例:实现前缀树
class TrieNode {
boolean isEnd; // 是否为单词结尾
TrieNode[] children; // 26个小写字母
public TrieNode() {
isEnd = false;
children = new TrieNode[26];
}
}
class Trie {
private TrieNode root;
public Trie() {
root = new TrieNode();
}
// 插入单词
public void insert(String word) {
TrieNode node = root;
for (char c : word.toCharArray()) {
int index = c - 'a';
if (node.children[index] == null) {
node.children[index] = new TrieNode();
}
node = node.children[index];
}
node.isEnd = true;
}
// 查找单词是否存在
public boolean search(String word) {
TrieNode node = root;
for (char c : word.toCharArray()) {
int index = c - 'a';
if (node.children[index] == null) {
return false;
}
node = node.children[index];
}
return node.isEnd;
}
// 查找是否有以prefix为前缀的单词
public boolean startsWith(String prefix) {
TrieNode node = root;
for (char c : prefix.toCharArray()) {
int index = c - 'a';
if (node.children[index] == null) {
return false;
}
node = node.children[index];
}
return true;
}
}
18. 滑动窗口 + 单调队列(处理滑动窗口极值)
核心思想:结合滑动窗口和单调队列(维护窗口内元素的单调性),高效求解滑动窗口中的最大值 / 最小值(时间复杂度O(n))。
适用场景:滑动窗口最大值、滑动窗口最小值。
示例:滑动窗口最大值
public int[] maxSlidingWindow(int[] nums, int k) {
if (nums == null || nums.length == 0) return new int[0];
int[] result = new int[nums.length - k + 1];
Deque<Integer> deque = new LinkedList<>(); // 单调递减队列(存储索引)
for (int i = 0; i < nums.length; i++) {
// 移除窗口外的元素(索引 <= i - k)
while (!deque.isEmpty() && deque.peekFirst() <= i - k) {
deque.pollFirst();
}
// 移除队列中比当前元素小的元素(它们不可能是最大值)
while (!deque.isEmpty() && nums[deque.peekLast()] < nums[i]) {
deque.pollLast();
}
deque.addLast(i);
// 窗口形成后,队首即为当前窗口最大值
if (i >= k - 1) {
result[i - k + 1] = nums[deque.peekFirst()];
}
}
return result;
}
19. Morris 遍历(树的常数空间遍历)
核心思想:通过修改树的指针(临时创建前驱节点的右指针指向当前节点),实现 O (1) 空间复杂度的树遍历(无需栈或递归栈)。适用场景:在空间受限的情况下遍历树。
示例:二叉树的中序 Morris 遍历
public List<Integer> inorderTraversal(TreeNode root) {
List<Integer> result = new ArrayList<>();
TreeNode curr = root;
TreeNode prev = null;
while (curr != null) {
if (curr.left == null) {
// 左子树为空,直接访问当前节点
result.add(curr.val);
curr = curr.right;
} else {
// 找左子树的最右节点(前驱节点)
prev = curr.left;
while (prev.right != null && prev.right != curr) {
prev = prev.right;
}
if (prev.right == null) {
// 首次访问,建立前驱节点到当前节点的指针
prev.right = curr;
curr = curr.left;
} else {
// 已访问过,恢复指针并访问当前节点
prev.right = null;
result.add(curr.val);
curr = curr.right;
}
}
}
return result;
}
20. 字符串匹配(KMP 算法)
核心思想:通过预处理模式串得到 “部分匹配表(next 数组)”,在字符串匹配时跳过不必要的比较,将时间复杂度从O(n*m)优化到O(n+m)(n为主串长度,m为模式串长度)。
适用场景:字符串查找(如在主串中找模式串的位置)。
示例:实现 strStr ()(在主串中找模式串首次出现的位置)
public int strStr(String haystack, String needle) {
if (needle.isEmpty()) return 0;
int n = haystack.length();
int m = needle.length();
if (m > n) return -1;
// 构建next数组(部分匹配表)
int[] next = new int[m];
for (int i = 1, j = 0; i < m; i++) {
while (j > 0 && needle.charAt(i) != needle.charAt(j)) {
j = next[j - 1]; // 回退j
}
if (needle.charAt(i) == needle.charAt(j)) {
j++;
}
next[i] = j;
}
// KMP匹配
for (int i = 0, j = 0; i < n; i++) {
while (j > 0 && haystack.charAt(i) != needle.charAt(j)) {
j = next[j - 1]; // 利用next数组回退j
}
if (haystack.charAt(i) == needle.charAt(j)) {
j++;
}
if (j == m) { // 匹配成功
return i - m + 1;
}
}
return -1;
}
难易程度
- 入门级:双指针、哈希表、二分查找;
- 进阶级:前缀和、滑动窗口、树的遍历、位运算、贪心、堆;
- 高手级:动态规划、单调栈、回溯法、分治法、并查集、拓扑排序;
- 专家级:前缀树、滑动窗口 + 单调队列、Morris 遍历、KMP 算法。
常用频率
- 双指针(Two Pointers)
- 哈希表(Hash Table)
- 滑动窗口(Sliding Window)
- 二分查找(Binary Search)
- 动态规划(DP)
- 树的遍历(DFS/BFS)
- 递归与记忆化
- 贪心算法
- 堆(Priority Queue)
- 前缀和(Prefix Sum)
1506

被折叠的 条评论
为什么被折叠?



