ARTICLE DETAIL

建站实战干货

来自一线的建站与推广经验沉淀,每一条都经过真实交付验证。

62天算法学习日记:用“更弱智”的方法啃下排序、二分与动态规划

2026/9/11 14:33:19 拓冰建站 浏览量
62天算法学习日记:用“更弱智”的方法啃下排序、二分与动态规划 在写这篇记录之前先把这个系列的“更弱智”三个字解释清楚。这三个字不是谦虚也不是标题党而是我给自己定的一个死规矩任何复杂的算法都要用最简单、最笨、最不装逼的方式讲明白讲给一个月前的我自己听。到第62天我依然在坚持这个规矩也正因为这个规矩很多曾经一看就头大的概念如今回头看竟然变得特别顺眼。这篇就是一个阶段性的学习输出把这段时间踩过的坑、总结的套路、想明白的点全部拎出来晒一晒。这篇内容适合所有被算法折磨过的人不管你是刚上大一的计算机专业学生还是准备跳槽刷题的职场人又或者是转行自学、看到“动态规划”四个字就呼吸困难的非科班选手。我会按照我实际的复习路径把排序、二分、KMP、图论、搜索策略这类高频考点全部过一遍每一步都附上“为什么这么做”和“我当初错在哪里”尽量让你少走弯路。1. 62天算法到底该怎么坚持学1.1 我为什么开启“更弱智”的算法学习先交代一下背景。我本身不是科班出身工作之后才意识到算法基础对职业天花板的影响有多大于是决定系统重过一遍。但刚开始的体验非常糟糕一上来就看什么“红黑树”“AVL树”“线段树”的讲解视频觉得自己就是个废物。后来我调整了策略不再追求一步到位而是刻意用最笨的方式去拆解每个知识点然后记录下来这就是“更弱智”系列诞生的原因。所谓“更弱智”本质上是一种学习态度不耻于从零开始不羞于反复推导。就拿最简单的冒泡排序来说很多教程三分钟就讲完了但真要问我“为什么内层循环要减i”我刚开始竟然答不上来。这种能答上来细节、能把每一步手算推演出来的能力才是算法学习的核心。第62天回头看我最大的收获不是记住了多少个算法模板而是建立了一种“拆解复杂问题”的肌肉记忆。1.2 从零开始的四阶段路线图如果让我把这62天浓缩成一条学习路线大概可以分成四个阶段每个阶段大概花两周左右。第一阶段是“暴力期”只学那些最直接的算法各种排序、线性扫描、哈希表用法重点是代码能跑通能AC简单题。第二阶段是“套路期”开始学习二分、双指针、滑动窗口、递归回溯这些有明确模板的算法这阶段最考验耐心因为每个套路的细节都特别多失之毫厘谬以千里。第三阶段是“图论期”从DFS、BFS开始逐步接触到拓扑排序、Dijkstra、并查集、最小生成树这一阶段我开始感觉到算法不仅仅是一个个孤立的知识点而是可以互相组合的“积木”。第四阶段是“优化期”开始接触动态规划、贪心证明、剪枝策略以及一些更高级的优化思路比如用单调栈、用状态压缩、用记忆化搜索。到现在第62天我正处在第四阶段的尾巴上每天都在跟“边界条件”较劲但也是成长最快的时候。1.3 每天学多少、怎么复盘最有效很多朋友问我每天到底应该投入多长时间我的回答是至少两小时但这两小时不全是刷题。我的固定模式是前一小时学新知识点跟着手写一遍核心逻辑后一小时做一道对应知识点的题目做完之后不看题解先自己走一遍测试用例看看哪里崩了这个过程我认为比刷十道题都管用。复盘是特别重要的一环。我每周末会把这周所有做错的题重新看一遍不看正确答案而是凭记忆在草稿纸上写出思路卡住的地方就用红笔标记下个周末再复盘一遍。这个方法很笨但是对我这种基础薄弱的人特别有效因为很多错误第一次改完以为自己会了过三天又忘得精光只有反复触发回忆才能真正沉淀下来。2. 几个必懂算法的原理解剖2.1 排序从冒泡到堆排序的一根筋排序算法是算法的“打底功夫”热搜词里冒泡排序算法c、堆排序算法、9个值排序算法rtl实现全都指向这个点。我先说冒泡排序它的思路特别直观每一轮都从头到尾两两比较把最大的元素像气泡一样“冒”到最后面。代码写起来非常简单但是真正让我明白算法魅力的是去算它的时间复杂度。冒泡排序的时间复杂度是O(n²)这个复杂度怎么理解假设我们有n个数第一轮要做n-1次比较第二轮要做n-2次直到最后一轮做1次加起来就是(n-1)(n-2)...1 n(n-1)/2这个结果在n很大的时候约等于n²/2这就是所谓O(n²)的由来。我刚开始还犯过一个经典错误内层循环写成从头到n-1没减去已经排好的后缀结果每一轮都把最大值来回搬白白浪费了比较次数。这个细节是理解排序优化思路的绝佳例子。堆排序又是另一套逻辑我一开始觉得它很高级后来发现它其实就是“用数组模拟一棵树”。把数组看成一棵完全二叉树然后维护大顶堆父节点大于等于两个子节点每次把堆顶的最大值换到最后再重新调整堆。这个过程的时间复杂度是O(n log n)比冒泡快了一个量级。但是老实说堆排序在面试中手撕的频率并不高真正重要的是理解“堆”这个数据结构本身它在后面解决topK问题、优先队列问题的时候特别管用。2.2 二分查找边界条件才是真考点二分算法在热搜词里出现了好几次而且我能负责任地说这是面试中出现频率极高、但错误率也极高的算法。二分的基本思想很简单在一个有序数组里每次把搜索区间砍半判断目标值和中间值的大小关系然后缩小区间。我一开始写二分的时候经常出现死循环后来才明白问题全出在“边界条件”上。最经典的几个坑循环条件是left right还是left right更新边界时是left mid 1还是left mid如果数组是 [1, 2, 2, 2, 3]你要找第一个2还是最后一个2代码完全不一样。我常用的一个模板如下这个模板查找第一个等于target的下标// C 代码寻找第一个不小于 target 的位置即 lower_bound int binarySearchLeft(vectorint nums, int target) { int left 0, right nums.size(); // 注意 right 开区间 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是为了防止两个大整数相加时溢出。这类细节是平时看书不太注意、但真正写代码时非常实用的点。2.3 KMP给字符串匹配装上“后悔药”字符串匹配问题在热搜词里也有明确存在也就是KMP算法。先说不带KMP的做法如果要在主串里找模式串普通写法是挨个位置对齐比较一旦失败就右移一格重来最坏情况时间复杂度是O(n*m)主串一长就非常痛苦。KMP的思路就是不回溯主串的指针而是利用已经比对过的信息把模式串一次性跳到合适的位置。这个“合适的位置”依赖一张next数组也有叫部分匹配表的它记录的是模式串每个前缀中“最长的相同前后缀长度”。我刚开始看这个定义觉得特别绕后来用“后悔药”来类比才理解我已经比较到一半失败了前面的信息不能浪费我要知道模式串的哪些前缀已经在主串里验证过了直接把它对齐过去省掉无效重试。构建next数组的代码和匹配过程有点像都是自己匹配自己。这不是一个能靠死记硬背理解的算法建议一定要自己手动填一遍next数组拿笔在纸上画几个例子比如模式串是ABABCABAB的时候每个位置的最长公共前后缀是怎么变化的。等到你想明白“失配时跳到前一个next值”这个操作时KMP就算真正入门了。2.4 Dijkstra最短路与水波的类比图论算法里Dijkstra算法是我认为最值得彻底弄明白的因为它特别贴近直觉。你可以想象往一张地图上倒水水从源点向外扩散最先被水覆盖到的路径就是最短路径。Dijkstra算法做的就是这件事只不过每次不是整片扩散而是贪心地从当前已经确定最短路的顶点集合出发选一个离源点最近的未确定顶点加入集合然后更新它邻居的最短距离。关键点在于这个算法要求边的权重非负因为一旦有负权边我刚刚“确定”最短路的顶点之后可能又被后面发现的负权边刷新掉算法就失效了。所以“为什么Dijkstra不能处理负权”是我面试中被问过好几次的问题建议每个人都想清楚。代码层面可以用优先队列来优化每次都从队列里弹出当前距离最小的节点这就是O((VE)logV)的复杂度。我踩过的一个坑是忘了标记“已经确定最短路的顶点”导致一个顶点被反复弹出更新有时候还会因为更新顺序问题得出错误结果。后来我在每个节点出队前加了if (dist[cur] curDist) continue;这个判断把“过期”的队列元素直接跳过问题就解决了。2.5 贪心、剪枝与动态规划三类核心策略这三种策略经常出现在同一道题里很多初学者分不清区别。贪心是“每一步都选当前看起来最优的”剪枝是“搜索过程中提前砍掉明显不可能的分支”动态规划是“把大问题拆成相互重叠的子问题并保存结果”。我举个简单的零钱兑换场景假设有1元、3元、4元的硬币要凑出6元。贪心法会选4元再选1元再选1元得到3枚硬币但最优解其实是3元3元只要2枚。这就说明贪心不是万能的只有在满足“贪心选择性质”和“最优子结构”时才能用。动态规划就能解决这个反例定义dp[i]为凑出i元所需的最少硬币数转移方程是dp[i] min(dp[i - coin] 1)然后把 dp[1] 到 dp[6] 逐个算出来。剪枝的应用更常见于DFS、回溯这类暴力搜索中。比如组合总和问题我给你一个数组和一个目标值让你找出所有和为目标值的组合。如果当前累积和已经超过目标就可以直接剪掉不必继续递归。这就是最基础的“可行性剪枝”更高级的有“上下界剪枝”“奇偶剪枝”“记忆化剪枝”。这些名词听着吓人其实核心都是“少做没用的计算”。3. 刷题实战从看懂到写对3.1 一道排序题的完整推演如果你完全没刷过题建议先用一道排序题作为起点比如把冒泡排序和归并排序都手写一遍然后用随机数组做压力测试。我拿归并排序举例它采用的是“分治思想”把数组从中间劈成两半左边排序、右边排序然后合并两个有序数组。归并排序的代码不难但经常栽在合并那一步临时数组的长度、指针的起始位置、最后拷贝回原数组的范围都要特别小心。我建议合并步骤单独抽成一个函数先传入[left, mid]和[mid1, right]两个区间再在临时数组里比较两个区间的头部元素最后覆盖回原数组。这个过程每一步不要跳步就和我写这篇文字的节奏一样。实战时我习惯先写一个测试用例比如nums {5,1,4,2,8}手动走一遍整个排序流程把每次合并后的数组状态记下来然后对照代码看输出是否一致。这个方法让我在早期就建立起了“用例子验证代码”的习惯比看多少遍理论都管用。3.2 边界条件与时间复杂度的错误排查刷题最让人崩溃的莫过于“思路完全正确但就是有测试用例过不了”。我总结了三个高频错误来源数组越界、死循环、复杂度超限。数组越界在二分和指针操作中最常见一定要反复检查left 1、right - 1、i n - 1这类表达式在边界情况下的行为。死循环通常出现在二分边界更新不当的时候比如某次循环中 left 一直不变那程序就到天荒地老了。时间复杂度方面我通常用数量级估算来判断n 是 10^5 时O(n²) 的算法在多数在线评测系统里都可能超时这时候就需要优化成 O(n log n) 或者 O(n)。热搜词里的“算法设计与分析”其实就是在教这套估算方法。我给个特别简单的统计技巧如果你的代码里有一个嵌套 for 循环内外层都大概跑 n 次那基本上就是 O(n²)如果只有一层循环但内部每次都会翻倍减半那就可能藏着一个 O(log n)。3.3 把算法用到真实场景从PID到粒子群刷题刷到一定阶段你会想知道这些算法在工业界到底怎么用不然总觉得纸上谈兵。热搜词里出现了一堆工程向的算法PID算法、卡尔曼滤波算法、粒子群算法原理、NSGA-II算法这些我特意挑出来说一说。PID算法是控制领域的常青树它本质上是一个反馈控制律根据当前误差的比例P、历史误差的积分I、误差变化趋势的微分D来调整输出让系统稳定在目标值。你在网上看到有人讨论“增量式PID算法”其实就是只计算输出量的增量避免积分饱和问题在电机控制里用得多。这个算法的思路和Dijkstra有点像都是不断观察当前状态、做出下一步动作只是应用场景从静态图变成了动态系统。粒子群算法PSO则是模拟鸟群觅食的优化算法每个“粒子”都在搜索空间里飞行不断根据个体最优和全局最优调整自己的速度和位置。我第一次看它不太理解为什么这种方式能找到最优解后来对比模拟退火算法才明白这类“元启发式算法”追求的不是绝对最优而是在复杂大空间里快速逼近一个可接受的解这在工程调参、路径规划、组合优化里特别实用。4. 常见问题与避坑清单4.1 看不懂代码就放弃先手算一遍学算法的时候最打击人的就是“看了半天题解依然不知道代码在干嘛”。我的经验是百分之八十的情况你不需要先看代码而应该先手算一遍思路。比如动态规划的题你先把dp数组在草稿纸上画出来手动填几个格子看看数值是怎么一步步转移的。这个过程只需要几步但对理解题目本质的帮助超过任何讲解。我经常用的一个“笨办法”是找一道题目的标准答案然后逐行翻译成自然语言。例如dp[i] max(dp[i-1], dp[i-2] nums[i])我会写一行注释“当前位置的最大值要么是跳过这个房子要么是抢这个房子加上更早之前的结果”。翻译完再比对照经典例题比如打家劫舍、最长递增子序列记忆会牢固很多。4.2 运行超时怎么办复杂度的自我审问如果你发现自己的代码在小数据上能通过换大数据就超时那就得做一次复杂度的自我审问。先看最内层循环执行了多少次再看有没有重复计算最后考虑是否可以用缓存记忆化或者改用更高效的结构。举个例子求斐波那契数列第n项用普通递归看起来极其简洁但时间复杂度是O(2^n)n稍大就爆炸。如果你改用数组存已经算过的中间结果就变成了O(n)。这就是动态规划的核心思想之一。所以当你提交代码超时别急着优化什么位运算、常数因子第一件事永远是看复杂度级别是否可接受这是“降维打击”式优化。4.3 编辑器、调试、测试三板斧工具这块真的决定了你刷题的幸福指数。我自己用的是VS Code加C和Python插件本地写代码、调试、跑测试用例非常方便。很多人习惯直接在网页编辑器里裸写其实特别容易因为一个括号错误浪费大量时间。本地写的话配合调试器看变量变化尤其适合排查二分和指针类问题。测试用例方面我建议每个算法题至少准备三组用例普通情况、边界情况空数组、单元素、全是相同元素、超大数据压力测试。手动构造用例的过程本身就是在复习算法细节。你还可以写一个随机数据生成器把自己写的解法跟暴力算法对拍如果两个结果不一致就缩小数据量逐步定位。这套“对拍”方法是我学算法以来收获最大的技巧之一没有之一。5. 写在day62之后一些笨办法反而最有效到了第62天我已经渐渐习惯每天打开编辑器写点东西算法学习对我来说从一项任务变成了一种日常修炼。回顾这段经历我觉得最有用的是那些听起来特别笨的办法手写推导、对拍测试、定期复盘、详细注释。而这些方法的共同点就是它们都强迫你“慢下来”认真对待每个细节而不是靠“看过就会了”的错觉自我感动。如果你也想走这条路我有两点建议。第一不要贪多一天真正吃透一个概念比一天划二十个概念要强得多第二遇到不会的题目先不看题解哪怕卡三小时也要自己先思考哪怕最终没想出来再看题解时的印象也会深刻得多。这种“先苦后甜”的学习节奏比直接看答案然后恍然大悟更能帮助你形成长期记忆。可能有人会说都第62天了还在聊冒泡排序和二分查找是不是进度太慢了我不这么看。算法的世界里真正难的不是记住某个冷门模板而是理解那些基础算法背后的思维模型。冒泡排序教会我循环不变式二分教会我边界意识KMP教会我如何利用已有信息避免重复计算。这些思维方式一旦建立学任何新算法都会快很多。最后再分享一个小技巧我现在每天学完新算法都会随手写一道与它相关的简单题哪怕这个知识点我已经完全掌握。这个习惯让我保持对代码的手感也防止自己在连续学习新东西的时候把旧知识遗忘得太快。下一个day100我打算把动态规划和图论的进阶专题好好过一遍到时候如果有什么新的体悟再来跟大家分享。