news 2026/9/13 16:49:56

十大基础算法:从排序到启发式优化的工程实践指南

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
十大基础算法:从排序到启发式优化的工程实践指南

1. 十大基础算法到底该排谁

每当聊起"基础算法",总有人问:网上有人列十大机器学习算法,有人列十大排序算法,还有人把动态规划、贪心、回溯全塞进去,到底哪十个才算真正的"基础"?

我在一线写了十几年代码,面试过几百人,也带过不少新人。我想给的答案很直接:所谓十大基础算法,不是某个权威机构钦定的名单,而是你解决实际问题时几乎绕不开的十类算法范式。它们不是知识树上的果实,而是树根。你学得越深越会发现,后面那些花里胡哨的神经网络、SLAM、渲染引擎、编译器优化,底层到处是这些基础算法的影子。

这份榜单是我个人根据"出现频率 + 迁移价值 + 面试考频"排出来的,不追求绝对正确,但求实用:

排名算法类型解决的核心问题典型代表
1排序算法让数据有序快排、归并、堆排序
2二分查找在有序空间里快速定位标准二分、lower_bound
3动态规划多阶段决策最优化背包、LIS、编辑距离
4贪心算法局部最优解全局最优区间调度、哈夫曼编码
5分治算法大问题拆小问题归并排序、快速幂
6回溯算法穷举所有可能的解空间八皇后、数独、组合
7图论遍历与最短路节点关系的最优路径BFS、DFS、Dijkstra
8字符串匹配文本中的模式查找KMP、BM
9数据结构内建算法数据组织与高效增删查栈、队列、堆、哈希
10启发式与迭代优化无解析解时的逼近求解模拟退火、粒子群、PID

这篇文章适合谁看?准备校招社招的候选人、刚入门想建立算法体系的初学者、以及写了几年 CRUD 想补内功的工程师。我会尽量把每个算法讲透:它解决什么问题、核心思路是什么、代码长什么样、实战里有哪些坑。

2. 排序算法:不只是把数字排整齐

2.1 排序为什么是"算法之首"

有面试官喜欢问:排序算法都学烂了,有什么好问的?但其实排序是理解算法复杂度、递归、分治、堆结构的最佳载体。你写一个冒泡排序和写一个 TimSort,中间差着整个算法思维进阶史。

基础排序里,我建议你至少能手写三种:冒泡排序(理解思想)、快速排序(理解分治与退化)、堆排序(理解堆这种数据结构)。C++ 里直接用std::sort当然快,但如果你不清楚它底层是"快排 + 插入排序 + 堆排序的混合体",一旦遇到极端数据就不会排查性能问题。

冒泡排序核心代码长这样,适合教学但实战里很少用:

void bubbleSort(vector<int>& arr) { int n = arr.size(); for (int i = 0; i < n - 1; i++) { bool swapped = false; for (int j = 0; j < n - i - 1; j++) { if (arr[j] > arr[j + 1]) { swap(arr[j], arr[j + 1]); swapped = true; } } if (!swapped) break; // 已有序,提前退出 } }

那个swapped标记很多人会漏掉,但它恰恰是工程化的关键——面对接近有序的数据,加了这行能把最好复杂度降到 O(n)。

2.2 快排的退化陷阱

快速排序平均 O(n log n),但如果你每次选的 pivot 都是最大值或最小值,它就会退化成 O(n²)。很多初学者不知道这一点,直到在有序数组上跑出了超时。

规避方法有两种:一是随机选 pivot,二是"三数取中"(取首、中、尾三个数的中位数做 pivot)。工程上经常两者结合。我见过一个线上服务线上问题,就是有人手写快排没做随机化,在用户ID接近有序的数据上直接卡死,后来换成std::sort就好了。

2.3 堆排序的隐蔽优势

堆排序最容易被忽视的价值是"不稳定但省内存"。它在 O(1) 额外空间里完成排序,更适合内存受限的嵌入式场景。热搜词里有"9个值排序算法RTL实现",说的就是硬件描述语言里做排序——那种资源紧张的环境下,堆排和插入排序反而比快排更常用,因为快排是递归的,硬件里实现递归非常痛苦。

实战心得:工程中绝大多数排序直接调库,但你要能看懂std::sort的行为——数据少用插入排序、递归深用堆排序兜底。这正是三种基础排序思想的合体。

3. 二分查找:比你想的难得多

3.1 二分不只是"数组里找数"

热搜词里"二分算法"单独霸榜,不是没道理。二分思想远不止在有序数组里找一个数,它本质是在一个单调的决策空间里快速收敛到边界。比如求平方根、查找旋转数组的最小值、在有序矩阵中搜索、Linux 磁盘寻道算法的电梯调度变种,都是二分思路的延展。

基础模板我建议背这个(C++ 版本),它找的是第一个满足条件的位置:

int lowerBound(vector<int>& nums, int target) { int left = 0, right = nums.size(); // 左闭右开 while (left < right) { int mid = left + (right - left) / 2; if (nums[mid] >= target) { right = mid; } else { left = mid + 1; } } return left; }

注意left + (right - left) / 2而不是(left + right) / 2,后者在 left + right 溢出时直接崩。这个溢出坑在 C++ 和 Java 里真实存在,LeetCode 早期版本的题目都踩过。

3.2 二分的三个致命细节

细节一:边界定义。左闭右开[left, right)还是左闭右闭[left, right]?两种写法都可以,但你必须写之前就定死。我见过太多人写着写着混了,结果while (left <= right)right = mid组合在一起直接死循环。

细节二:mid 的取整方向。查找左边界时 mid 向下取整没问题,但查找右边界时最好向上取整,否则当left + 1 == right时会死循环。

细节三:二分的应用前提是"单调性",不一定是有序数组。比如"每个版本是否崩溃"是一个 false 序列到 true 序列的切换,虽然数组没有"大小关系",但答案具备单调性,就可以二分。很多人学了二分只会做排序数组,遇到"最大值最小化"问题就懵了,其实那是二分的经典战场。

3.3 二分之外的查找

热搜词里有"除了二分法还有什么算法",这个问题很实在。有序数组查找可以插值查找(类似查字典翻页)和斐波那契查找,但实际收益有限。真正值得关注的是哈希查找——O(1) 平均复杂度,但它不要求有序,也无法做范围查询。所以工程实践里的结论是:要范围查询选 B+ 树索引,要等值查询选哈希索引,二分在两者之间承上启下。你把这个逻辑理清了,数据库索引的原理也顺带懂了。

4. 动态规划:状态转移是灵魂

4.1 动态规划到底在干什么

动态规划(DP)是新手最头疼的算法之一,因为没有固定模板,每一道题都不一样。但如果你从"暴力递归 + 备忘录"切入,会好理解很多。动态规划本质上是对暴力搜索的优化——把重复计算的子问题结果存下来,用空间换时间。

经典的斐波那契数列就是最简单的 DP,但它太简单,体现不出"状态转移"的威力。我用爬楼梯问题来演示从递归到 DP 的演进:

// 暴力递归:O(2^n) int climbStairs(int n) { if (n <= 2) return n; return climbStairs(n - 1) + climbStairs(n - 2); } // 备忘录递归:O(n) int climbStairsMemo(int n, vector<int>& memo) { if (n <= 2) return n; if (memo[n] != 0) return memo[n]; return memo[n] = climbStairsMemo(n - 1, memo) + climbStairsMemo(n - 2, memo); } // 自底向上 DP:O(n) int climbStairsDP(int n) { if (n <= 2) return n; vector<int> dp(n + 1, 0); dp[1] = 1; dp[2] = 2; for (int i = 3; i <= n; i++) { dp[i] = dp[i - 1] + dp[i - 2]; } return dp[n]; } // 滚动数组优化:O(1) 空间 int climbStairsOpt(int n) { if (n <= 2) return n; int prev = 1, curr = 2; for (int i = 3; i <= n; i++) { int next = prev + curr; prev = curr; curr = next; } return curr; }

4.2 状态定义是最难的环节

DP 的难点从来不是写代码,而是定义状态。同样是背包问题,"前 i 个物品中选若干件,容量为 j 时的最大价值"这个状态定义,新手想不出来很正常——这是经验问题。我分享一个实操经验:拿到 DP 题,先暴力递归,递归函数的参数就是状态,然后看递归里哪些重复计算,再用 memo 数组或者 DP 表去重。这个"暴力递归 -> 备忘录 -> 自底向上"的三段法是通用的。

我做面试官时,最怕听到"我会背包九讲",因为背诵和真正理解是两回事。如果你能把状态定义、转移方程、初始化条件、遍历顺序四个要素讲清楚,比背下十道题有用得多。

4.3 遍历顺序里的小坑

背包问题的遍历顺序直接决定算法正确性。01背包要求逆序遍历容量,完全背包要求正序遍历容量。很多文章只是说"记住就行",我解释一下原因:

01背包的状态转移依赖dp[i][j]dp[i-1][j-w]转移,如果正序遍历容量,dp[j]可能已经被本轮更新过,相当于同一个物品被重复选入,这就变成了完全背包。所以逆序遍历,用的还是上一轮(上一个物品)的旧值。

这道题我当年第一次写就栽了,debug 到怀疑人生,后来才搞明白。这类细节,教科书里往往一笔带过,但实战就是会被卡住。

5. 贪心算法:简单背后是证明

5.1 贪心的直觉与陷阱

贪心算法的代码往往比 DP 短得多,但难在证明"局部最优能推出全局最优"。比如区间调度问题:给你一堆区间,选出尽量多的互不重叠区间。策略很简单——每次选最早结束的区间。

int intervalSchedule(vector<vector<int>>& intervals) { sort(intervals.begin(), intervals.end(), [](auto& a, auto& b) { return a[1] < b[1]; }); int count = 1; int end = intervals[0][1]; for (int i = 1; i < intervals.size(); i++) { if (intervals[i][0] >= end) { count++; end = intervals[i][1]; } } return count; }

代码写了不到十行,但能证明这个策略正确的人并不多。实际工作中,那些"看起来对但证明不了"的贪心方案,往往会在边界条件上翻车。

5.2 贪心与 DP 的边界

贪心和 DP 很容易被搞混。我习惯用一个简单标准来判断:当前决策会不会影响后续决策?如果不会,大概率是贪心;如果会,就要考虑 DP。比如找零钱问题,在人民币面额体系(100、50、20、10、5、1)下贪心是对的,但在自定义面额(如 1、5、11)下贪心可能失败——凑 15 块,贪心先拿 11,结果要 4 张(11+1+1+1),而正确解是 3 张(5+5+5)。

这个例子特别适合跟新手讲清楚:贪心的前提是"局部最优就是你当前能做的唯一正确选择",而这个前提必须靠数学证明或题目条件保障。

5.3 实战里的贪心应用

贪心算法在工程里极其常见。哈夫曼编码是贪心,最小生成树的 Kruskal 和 Prim 是贪心,Dijkstra 最短路本质也是贪心(每次选距离最近的点扩展)。热词里有个"高级算法设计与分析",如果你参加过这类课程就知道,贪心章节作业的难度从来不在写代码,而在"证明你的贪心策略是安全的"。

我的经验是:工作中遇到一个优化问题,先试试贪心能不能解,解不了再上 DP 或者搜索。因为贪心的代码量最小、bug 最少、可维护性最高,哪怕它只能达到理论最优的 90%,在工程里也比一个完美但复杂的 DP 要好。

6. 分治与回溯:递归的两副面孔

6.1 分治:分而治之再合并

分治算法本质是"把原问题拆成几个规模更小的子问题,分别解决后合并结果"。最经典的例子是归并排序和快速幂。快速幂这个例子特别漂亮,把计算x^n从 O(n) 降到 O(log n):

long long fastPow(long long x, long long n, long long mod) { long long res = 1; while (n > 0) { if (n & 1) res = (res * x) % mod; x = (x * x) % mod; n >>= 1; } return res; }

这段代码的核心思想是:x^10 = x^8 * x^2,用二进制的位运算把指数拆开。你有没有发现,这和二分查找其实是同一个思想的不同面孔——都是利用"信息复用"来减少计算量

6.2 回溯:暴力搜索的空间管理

回溯算法是"决策树的深度优先遍历",典型场景是八皇后、数独、全排列、组合总和。回溯的核心是"做选择 -> 递归 -> 撤销选择"三步曲。以全排列为例:

vector<vector<int>> permute(vector<int>& nums) { vector<vector<int>> res; vector<int> path; vector<bool> used(nums.size(), false); backtrack(nums, used, path, res); return res; } void backtrack(vector<int>& nums, vector<bool>& used, vector<int>& path, vector<vector<int>>& res) { if (path.size() == nums.size()) { res.push_back(path); return; } for (int i = 0; i < nums.size(); i++) { if (used[i]) continue; used[i] = true; path.push_back(nums[i]); backtrack(nums, used, path, res); path.pop_back(); used[i] = false; } }

6.3 剪枝才是回溯的性能钥匙

没有剪枝的回溯和暴力枚举没区别。我拿组合总和举例:如果已知当前和已经超过 target,后面更大的数显然也不可能了,这时直接 return。再比如 N 皇后,如果当前列、主对角线、副对角线都做了哈希标记,判断冲突就是 O(1),而不是每次扫描整个棋盘。

剪枝优化后的回溯经常能跑出远超预期的性能。LeetCode Hot100 里那些回溯题,很多时候不是回溯本身慢,而是你剪枝剪得不够狠。

7. 图论:BFS、DFS 与 Dijkstra 的取舍

7.1 图的遍历,先从 BFS/DFS 说起

图的遍历是图论的地基。DFS 走递归或栈,BFS 走队列。BFS 天然具备"最短路径"的特性——第一次扫描到目标节点时的步数一定是最短步数,这在无权图中尤其好用。

我做一个简单的模板对比:

// BFS 模板 void bfs(vector<vector<int>>& graph, int start) { queue<int> q; vector<bool> visited(graph.size(), false); q.push(start); visited[start] = true; while (!q.empty()) { int node = q.front(); q.pop(); for (int neighbor : graph[node]) { if (!visited[neighbor]) { visited[neighbor] = true; q.push(neighbor); } } } }

BFS 的层次遍历有一种很常见的变体,就是记录"当前层大小":

int step = 0; while (!q.empty()) { int size = q.size(); for (int i = 0; i < size; i++) { // 处理当前层节点 } step++; }

这个变体在"走迷宫最少多少步""单词接龙最短路径"里都是核心结构。

7.2 Dijkstra 与 A* 的取舍

热词里专门有"A*算法与BFS算法的优缺点"和"Dijkstra算法",我把三者放在一起说:

  • BFS:适合无权图找最短路径,无法处理边权重
  • Dijkstra:适合带权图但所有边权非负。核心是贪心——每次从未访问节点中挑距离最小的,用优先队列优化后复杂度为 O(E log V)
  • A*:在 Dijkstra 的基础上加了启发式函数(估算当前节点到目标的距离),搜索方向性更强,适合地图导航这类场景。A* 的缺点是启发式函数设计不好会退化——过度估计的话,找到的路径可能不是最短路径

我有个实际经验:工程里做路径规划,如果地图规模不大,直接上 Dijkstra 最稳;如果地图很大且对实时性有要求,再考虑 A并仔细设计启发式函数。* 别为了炫技一上来就上 A*,它的正确性边界比 Dijkstra 苛刻得多。

7.3 图论在 AI 算法里的身影

你可能觉得图论离 AI 很远,其实不然。热词里的"3DGS算法最经典的论文"(3D Gaussian Splatting)涉及空间关系组织,底层有大量图论和空间数据结构的思想。强化学习里的 PPO 算法训练智能体在环境中探索,环境本身的关系网络也需要图算法做表示。更别提"数据结构与算法"本来就是 AI 工程师的必修基础。

8. 字符串匹配:KMP 到底优化了什么

8.1 暴力匹配的问题

字符串匹配看起来简单:从文本串第一个字符开始,逐个和模式串比较,不匹配就右移一位重新比较。最坏复杂度 O(m * n),在文本很长、模式串也有很多重复前缀时会非常慢。

KMP 算法优化的核心是:当匹配失败时,利用已经匹配的部分信息,把模式串尽量多地右移,而不是只移动一位。这个"尽量多"由 next 数组(也叫部分匹配表)决定。

求 next 数组是 KMP 的难点:

vector<int> buildNext(const string& pattern) { int m = pattern.size(); vector<int> next(m, 0); int j = 0; for (int i = 1; i < m; i++) { while (j > 0 && pattern[i] != pattern[j]) { j = next[j - 1]; } if (pattern[i] == pattern[j]) { j++; } next[i] = j; } return next; } int kmpSearch(const string& text, const string& pattern) { vector<int> next = buildNext(pattern); int j = 0; for (int i = 0; i < text.size(); i++) { while (j > 0 && text[i] != pattern[j]) { j = next[j - 1]; } if (text[i] == pattern[j]) { j++; } if (j == pattern.size()) { return i - pattern.size() + 1; // 匹配起点 } } return -1; }

8.2 next 数组的直觉理解

很多教程讲 next 数组讲得很晦涩。我用大白话说:next[i] 表示"模式串前 i 个字符中,最长相等前后缀的长度"。当第 i 个位置匹配失败时,我们已经知道前 i-1 个字符是匹配的,所以可以直接把模式串跳到"前缀等于后缀"的位置,从而避免重复比较。

举个例子:模式串 ABCABX,在 X 处匹配失败,前面的 ABCAB 中,最长相等前后缀是 AB(长度 2),所以直接跳到下标 2 继续比,而不是回到 0。

我当年学 KMP 的时候,死记硬背看了一天看不懂,后来自己把 next 数组的构建过程用纸笔一步步模拟了一遍,然后才真正开窍。这类算法必须手推,光看代码不行。

8.3 KMP 与工程现实的差距

说实话,如果你用的是 Python、Java、C++ 这种高级语言,直接调findstrstr就够了,底层实现可能比手写 KMP 更高效。但 KMP 的思想仍然值得掌握,因为在正则表达式引擎、编辑器高亮、基因序列比对这些场景里,KMP 的思想会以各种变体出现。

热词里的"CDCL算法"(SAT求解器核心算法)其实也借鉴了类似的思想——通过记录冲突和学习子句来跳过大量无效搜索。可见**"利用已获取的信息减少重复计算"**是贯穿所有算法的一条主线。

9. 数据结构中的内建算法:真正的高频武器

9.1 为什么数据结构也算算法

你可能会问:栈、队列、哈希表不是数据结构吗,怎么跑进算法榜单了?我说个实在的:数据结构本身就是"操作序列的规则",它们的访问方式、增删查策略、扩容策略,每一个细节都是算法。

比如栈,它的算法思想在于"后进先出"这个访问策略——函数调用栈、括号匹配、表达式求值、撤销操作,全是靠这个策略工作的。队列则是"先进先出",适用于 BFS、消息队列、任务调度。

堆(优先队列)更典型:插入 O(log n)、取最值 O(1)、删除 O(log n),C++ 里priority_queue底层就是堆。热词里"堆排序算法"和"pid算法"蹭在一起不是偶然,很多实时控制场景里,堆就是用来维护最值的。

9.2 哈希表的扩容与冲突

哈希表是工程中出现频率最高的数据结构,但很多人只是"会用",没想过它的实现细节。哈希表的两个核心问题是哈希函数设计和冲突处理。C++unordered_map用的是链地址法(拉链法),当某个桶链过长时会触发 rehash。

我踩过一个真实的坑:用unordered_map存大量自定义对象,但没重写哈希函数,导致所有对象都映射到同一个桶上,查询复杂度退化到 O(n),一个离线任务直接跑了几小时。后来换成了合适的哈希函数,几十秒就完了。哈希函数选得好不好,直接决定哈希表是 O(1) 还是 O(n)。

9.3 手写数据结构在面试中的地位

面试时经常考"手写 LRU Cache"或"手写最小栈"。LRU 的标准解法是"哈希表 + 双向链表",哈希表提供 O(1) 查找,双向链表提供 O(1) 的插入和删除。这个组合看起来简单,但把两个结构间的指针维护理顺,比想象中容易出错。

热词里有"数据结构排序算法"这种组合词,说明大家也意识到:单独背排序或单独背数据结构都不够,关键要理解数据结构如何支撑算法的运行效率。比如图算法依赖邻接表(链表/数组),Dijkstra 依赖优先队列(堆),BFS 依赖队列,这些依赖关系才是真实项目中做技术选型的依据。

10. 启发式与迭代优化:传统算法与现代AI的桥梁

10.1 粒子群与模拟退火

热词里有"粒子群算法原理",也有"模拟退火算法",还有"基于混合SPSS-PSO-SVM模型烟气软测量算法软件"。这些词看着高大上,本质上都干同一件事:在解空间巨大且没有解析解时,用一种启发式策略逼近最优解。

模拟退火的直觉很讨喜:把求解过程类比成金属退火,温度高时接受差解的概率大(允许跳出局部最优),温度逐渐降低,接受差解的概率变小,最终收敛。

粒子群算法的直觉则是:一群粒子在解空间里飞行,每个粒子记住自己的历史最优位置,同时群体共享全局最优位置,通过"个体认知 + 社会认知"两个分量更新速度。

import random import math def particle_swarm_optimization(objective, bounds, n_particles=30, iterations=100): dim = len(bounds) particles = [] velocities = [] pbest = [] for _ in range(n_particles): pos = [random.uniform(b[0], b[1]) for b in bounds] particles.append(pos) velocities.append([0.0] * dim) pbest.append(pos[:]) gbest = min(pbest, key=objective) w, c1, c2 = 0.7, 1.5, 1.5 for _ in range(iterations): for i in range(n_particles): for d in range(dim): r1, r2 = random.random(), random.random() v = w * velocities[i][d] \ + c1 * r1 * (pbest[i][d] - particles[i][d]) \ + c2 * r2 * (gbest[d] - particles[i][d]) velocities[i][d] = v particles[i][d] += v if objective(particles[i]) < objective(pbest[i]): pbest[i] = particles[i][:] if objective(pbest[i]) < objective(gbest): gbest = pbest[i][:] return gbest

这类算法的调参是一门玄学,w、c1、c2 的数值会影响收敛速度和探索能力。我的建议是:先用默认参数跑通,再观察收敛曲线决定调参方向,不要一上来就死磕参数。

10.2 PID 与卡尔曼滤波:工业界的常青树

热词里PID算法、增量式PID、卡尔曼滤波单独成词,说明它们在实际工程中的需求非常大。

PID(比例-积分-微分)控制是工业控制领域最基础的闭环算法。你别看它公式只有三行,整定参数(Kp、Ki、Kd)却能让工程师秃头。增量式PID在电机控制里很常见,它输出的不是绝对量,而是"增量",避免大幅跳变。

卡尔曼滤波则是传感器融合领域的王者算法。NTC温度传感器、IMU姿态解算、GPS+惯导融合,底层都是卡尔曼滤波或它的变体(扩展卡尔曼、无迹卡尔曼)。它的核心是"预测 + 更新"两个步骤,用协方差矩阵描述不确定性,把多个带噪声的观测融合成一个更准确的估计。

10.3 机器学习算法也是"算法"

热词里有"机器学习算法""回归算法""Kmeans聚类""强化学习算法PPO""YOLO算法",这些超出了传统"基础算法"的范畴,但它们的根基仍然是基础算法。Kmeans 的 EM 思想与贪心/迭代优化有千丝万缕的联系,YOLO 的 anchor 设计背后是枚举与剪枝,PPO 的策略梯度更新也离不开梯度下降这个优化算法。

所以我不是把十大基础算法当成一个封闭名单,而是说:你如果把十大基础算法吃透了,往后学任何新算法都有抓手。它们解决的是"如何设计计算过程"的元问题,而新算法只是把不同的元问题组合方式套在了具体场景上。

11. 算法怎么学才算真学会

11.1 学习的路径建议

结合我带新人的经验,我建议的学习路径是:

  • 第一步:把排序和二分练到"闭眼能写"的程度
  • 第二步:用递归专题打通分治和回溯,递归是这两类算法的共同根基
  • 第三步:把 DP 的"暴力递归 -> 备忘录 -> 自底向上"三步法练熟
  • 第四步:刷图论模板题,BFS/DFS/Dijkstra 各手写一遍
  • 第五步:学数据结构底层的实现细节,尤其是堆、哈希、双向链表
  • 第六步:有余力再看启发式算法和 AI 算法的入门教程

热词里的"信奥算法"和"算法设计与分析"其实对应的就是这个路径。信奥(信息学奥赛)非常强调基础算法的手写能力和数学功底,而大学里的"算法设计与分析"则更侧重复杂度分析和正确性证明,两者都要重视。

11.2 刷题的正确姿势

很多新手刷题有个误区:一道题只看题解,看懂了就觉得会了,结果三天后再做仍然写不出来。我推荐一个笨办法:

  • 拿到题先想 15 分钟,想不出来看题解,但要关掉题解自己写
  • 写完提交,AC 了也别急着下一题,花 5 分钟总结:这题属于哪类范式?状态转移方程/贪心策略/剪枝条件是什么?
  • 一周后再重新做一遍,如果还能独立写出来,才算真正掌握

LeetCode Hot100 这个刷题清单是公认的高质量题库,里面覆盖了本篇文章提到的绝大多数算法类型。但说实话,刷题数量不是关键,你能不能用一句话讲清楚每道题的"核心套路"才是关键。

11.3 避免"会做但不会用"

我见过不少候选人,算法题刷了三百道,但到了实际项目里,遇到一个"计算两个地理位置之间的距离并找最近的点"的问题,还是直接用双重循环。明明用分治思想(类似最近点对)能把复杂度从 O(n²) 降到 O(n log n),他却想不到。

问题出在哪?刷题时题目已经帮你贴好了标签("这题是动态规划"),但真实世界里没人会给你贴标签。所以平时练习时,我建议你多做"标签模糊"的练习题,或者干脆从业务问题反推:这个问题的数据规模是多少?操作类型是什么?对实时性要求有多高?这些问题的答案会自然指向某个算法范式。

最后的提醒

写到这里,我想再啰嗦一点。十大基础算法看起来是"基础知识",但正因为它们太基础,反而容易被低估。热词里有"算法研发过程代码管理"这个词,说明大家的关注点已经在从"会不会写算法"转向"怎么在团队里规范地做算法研发"——这是好现象,说明你开始从"会用"走向"工程化"。

我个人在实际项目里最大的体会是:算法能力不是靠背模板获得的,而是在反复调试中把每个细节揉碎了理解之后才能真正内化的。比如你真正调试过一次二分查找的死循环,就再也不会在边界条件上翻车;真正在线上环境排查过一次哈希碰撞导致的性能劣化,就不会再轻视哈希函数的设计。

所以别怕慢,别怕 debug,别怕把一道题反复做三遍。基础算法的价值不在于让你在面试里默写代码,而在于当你面对一个全新的、复杂的问题时,能下意识地用排序、二分、DP、贪心、分治、回溯、图论这些"标准件"去拆解它、组合它,最终拼出一个可靠方案。

这份十大名单虽然是旧的,但算法思想永远是新的入场券。

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/9/13 16:48:04

工业边缘计算:让AI真正嵌入控制环的硬核实践

1. 这不是“加个AI模块”那么简单&#xff1a;工业自动化系统里的边缘计算到底在干啥&#xff1f;“智造工业自动化系统&#xff1a;边缘计算赋能&#xff0c;让工业控制更智能”——这个标题里藏着三个容易被误解的关键词&#xff1a;“智造”、“边缘计算”、“更智能”。很多…

作者头像 李华
网站建设 2026/9/13 16:47:01

抖音视频公开与私密状态合规切换指南

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

作者头像 李华
网站建设 2026/9/13 16:46:46

MATLAB实现蓝色车牌识别系统:从图像处理到智能识别

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

作者头像 李华
网站建设 2026/9/13 16:45:43

Metabase H2 应用数据库故障排查与迁移生产数据库实战指南

Metabase H2 应用数据库故障排查与迁移生产数据库实战指南 【免费下载链接】metabase The easy-to-use open source Business Intelligence and Embedded Analytics tool that lets everyone work with data :bar_chart: 项目地址: https://gitcode.com/GitHub_Trending/me/m…

作者头像 李华