资讯动态

更弱智的算法学习:暴力枚举、剪枝与排序算法复盘

发布时间:2026/10/2 3:27:20 来源:尧图企业网站定制
“更弱智的算法学习”这个名字听起来像是在自嘲但今天是我坚持算法学习的第32天我反而觉得这个“弱智”标签挺真实、也挺有用。朋友圈打卡时候随手起的标题没想到成了我三十多天里最好的心理建设工具不强求一次看懂所有高深理论不指望十几天就刷穿大厂面试题就是把每个算法当成一个“笨办法”来学先想清楚它到底在解决什么问题再动手写、动手调。今天的主题是暴力枚举和剪枝顺便把排序算法整个过了一遍趁着热乎劲儿把这段心得整理出来希望能给同在学算法、尤其是零基础和弱基础的你一点参考。1. 第32天为什么选“暴力枚举”当主角1.1 别小看这个“最笨”的算法很多初学算法的人一上来就冲KMP、冲击力最强的动态规划、刷满脑子“高级算法”但真正到写代码的时候就蒙了套模板都套不对更别提分析为什么对。我前三十天的教训非常直白——地基没打好后面的东西全是空中楼堡。暴力枚举Brute Force就是那个地基。它没有任何花哨的技巧核心就一句把所有可能的答案一个一个试一遍选那个符合条件或者最优的。听起来像废话但实际做起来大部分人连“把所有可能”这五个字都做不到。你可能会问这有什么技术含量我先说一个最真实的场景LeetCode和蓝桥杯的题目很多都不要求你一次写出最优解只要你能在限定时间内跑出正确结果至少能拿到基础分。很多时候暴力解就是把思路理清楚的最短路径先写出来跑通再谈优化。我的习惯是拿到一道题先问自己“如果不用任何高级技巧能不能把答案找出来”用这种思路把状态空间列清楚然后再去考虑优化。这一步训练的是对问题空间的理解力不是代码手速。1.2 暴力枚举的三个实操层次把暴力枚举做好其实有三个层层递进的要求第一枚举什么。也就是要定义清楚状态空间。一个数组里找两个数让它们之和等于target暴力的状态空间就是所有二元组下标(i, j)从一个集合里找满足条件的子集状态空间就是所有子集。很多同学卡在这里不是因为不会写for循环而是没想清楚“把所有可能”到底包含了哪几种可能。这步建议画出来不要直接在脑子里想。第二怎么枚举才不重不漏。这是最容易翻车的地方。两个for循环的时候内层循环是从0开始还是从i1开始直接决定了你会不会重复枚举、会不会漏掉组合。养成一个标准姿势组合类问题里外层循环i从0到n-1内层j从i1到n-1这样枚举的是所有下标对且不重复排列类问题里需要用visited数组记录哪些元素已经用过。这个习惯要形成肌肉记忆。第三能不能早点停下来。这就是剪枝也是暴力枚举进阶到高效算法的起点。1.3 剪枝不是玄学是把“明显不对”的路提前堵死剪枝这个术语听起来很吓人实际它干的事情就是在枚举过程中提前判断某条分支不可能产生答案就跳过它不在它身上浪费时间。我学的时候用了生活化的类比来理解你要在一个小区里找一个人暴力枚举就是每栋楼每层每户都敲门问一遍而剪枝就是——如果物业告诉你这人不住在带电梯的楼里那你就不用去电梯楼挨家挨户问这就是一个剪枝。剪枝常见的有三类我整理成一张表方便对照剪枝类型判断依据典型场景可行性剪枝当前这条路已经不可能满足题目条件已经超过目标值、已经用过非法元素最优性剪枝当前这条路就算走到头也比已有最优解差已经花费超过当前最优答案顺序剪枝先搜更可能成功的分支失败就早点回头分支数量不同时从分支少的开始搜学习的时候关键不是记住这三个名词而是掌握一个思维习惯每写一层循环或递归分支都问一句“有没有什么条件能让我不用继续试了”。比如经典的全排列求和问题如果当前累加和已经超过target后面加什么都超直接return。就这么一行代码可能把运行时间从几百毫秒降到个位数。1.4 暴力枚举阶段最容易踩的坑我把自己踩过和看别人踩过的坑集中写在下面每条都是用时间换来的枚举范围看错。题目给的数范围是0到n你写了for(i0; in; i)漏掉了最后一个位置或者数组长度是n你for到n1直接越界。聪明办法是统一用“左闭右开”的区间写法也就是for循环里写i0; ilen; i不要一会儿一会儿。剪枝剪过头。剪枝的前提是“这一条分支绝不可能产生正确答案”如果你只是“感觉可能不对”就剪掉很容易把正确的答案也剪掉。判断标准是必须能严格证明这条分支不考虑答案也一定错误或一定不优否则别剪。递归枚举忘记恢复状态。比如在全排列里你用过某个元素后要标记visited回溯时必须撤销这个标记否则后续分支会漏掉这个元素。这个操作有个专门的名字叫回溯可以说是递归枚举的灵魂。认为暴力一定超时。实际情况是题目给的数据范围很小的时候暴力就是最优解。判断依据很简单估算枚举次数。如果一次操作规模在10的7次方量级多数语言在1秒到2秒内都能跑完到10的8次方就得考虑优化了。学会估算枚举次数比盲目优化重要得多。2. 排序算法全复习从O(n²)到O(n log n)的分水岭2.1 为什么学习进度里必须插一天专门搞排序排序是所有算法里出场率最高的一类基础操作不管你是做数据分析还是写业务代码不管你是刷题还是搞工程几乎天天和排序打交道。而且排序算法是个非常典型的学习载体同一个问题有十几种解法每种解法的思路、时间空间复杂度、稳定性都不一样。对我来说学习排序算法最大的意义不在于“会用sort函数”而在于理解“同一个目标可以有不同的算法路径”这是算法思维的启蒙课。我在day32安排了一个两小时专项把最常见的一类排序算法挨个手写了一遍包括冒泡排序、插入排序、选择排序、归并排序、快速排序、堆排序。这个过程看着多实际只要理清一条主线就好每个算法都要回答两个问题——它每轮在做什么它每轮能确定什么2.2 O(n²)三兄弟冒泡、插入、选择这三个排序常被放在一起对比因为它们都是两层循环时间复杂度都是O(n²)。但细节差别很大我用一个实战视角来解释冒泡排序每轮从前往后扫描相邻元素比较如果前大后小就交换。这样一轮下来最大的元素会被“冒泡”到最后面所以第i轮结束后倒数第i个位置就确定了。它的优点是代码简单直观缺点是交换次数多。冒泡有个特性对于已经有序的数组一轮扫描如果没有发生任何交换就可以提前结束这是它天然的优化点。插入排序更像是在打扑克牌摸牌把新摸到的牌从右往左和手里已有的牌比较找到位置插进去。实际写的时候是把当前元素记下来逐个往后挪动比它大的元素最后放入正确位置。插入排序在数据局部有序的时候特别猛最优情况复杂度能降到O(n)所以很多高级排序在小规模子问题上会用它收尾。选择排序每轮扫描剩余未排序部分找到最小值和当前位置交换。它的特点是交换次数少但比较次数固定无论如何都是O(n²)。这里要特别提醒一个大家都会犯的错找到最小值下标后如果这个最小值就在当前位置不需要交换直接跳过否则会白白增加一次赋值的运行开销。2.3 归并排序分治思想的第一次正面接触归并排序的核心操作只有两个字合并。把数组切成两半各自排好序再准备一个临时数组把两边按大小依次合并回去。这个“切到不能再切再逐层合并”的过程就是分治思想最标准的范本。它最稳定的地方在于不管数据原本是什么顺序时间复杂度都是O(n log n)不会退化。代价是需要额外O(n)的临时数组空间。我第一次写归并排序的时候被合并那一步各种下标越界问题折磨后来总结出一个不出错的写法合并时用三个游标一个是左半区游标i一个是右半区游标j一个是临时数组游标k。每次比较两个半区的当前元素谁小放谁放完对应游标加一。某个半区放完了就把另一个半区剩余元素全部依次放进去。这里最关键的问题在于终止条件必须是i到左半区末尾和j到右半区末尾都处理完而不是简单的in。2.4 快速排序和堆排序工程级的选手快速排序的平均时间复杂度也是O(n log n)它的核心是选取一个基准值(pivot)把小于它的放左边大于它的放右边然后对左右两边递归排序。它的性能高度依赖基准的选择如果每次选到的都是中位数那么递归树是平衡的如果每次选到的是最大或最小值那快排就退化成了O(n²)。这也是为什么工程实现里不会简单取第一个元素当pivot而是采用三数取中或者随机选法。C标准库的std::sort底层用的是Introsort它结合了快排、堆排序和插入排序的优点在递归深度过深时会切换成堆排序避免退化。堆排序则是利用堆这种数据结构来排序先把整个数组构建成一个大顶堆堆顶就是最大值把堆顶和末尾交换缩小堆范围再向下调整堆。这个过程有意思的地方在于它不需要额外空间属于原地排序。但它的成绩和快排相比在日常测试中通常偏慢一点因为堆的访问模式对CPU缓存不友好。不过作为学习堆结构的重要载体它必须亲手实现一遍。2.5 排序算法对比速查表算法最好情况平均情况最坏情况空间稳定性冒泡排序O(n)O(n²)O(n²)O(1)稳定插入排序O(n)O(n²)O(n²)O(1)稳定选择排序O(n²)O(n²)O(n²)O(1)不稳定归并排序O(n log n)O(n log n)O(n log n)O(n)稳定快速排序O(n log n)O(n log n)O(n²)O(log n)不稳定堆排序O(n log n)O(n log n)O(n log n)O(1)不稳定注意稳定性指相等元素的相对顺序是否保持原样。业务系统里如果有“先按时间排序再按优先级排序”的需求稳定排序是必须的否则第二次排序会打乱第一次的结果。这也是为什么归并排序在很多场景里没法被快排完全替代。3. 30天算法学习路线复盘与调整3.1 阶段一头十天千万不要一上来就刷题很多入门者最大的问题就是过早进入题海。我第一天也想学“大神”直接刷困难题结果被教做人。后来把前十天的任务定成学基础语法和复杂度分析。语言的循环、数组、字典、函数得写到不用查文档的程度算法复杂度的概念得理解到“能估算代码运行次数”的程度不需要会严格的数学证明。这是后面一切的基础。我建议这个阶段可以结合教材像《图解算法》这种有大量图示和简单例子的书比较适合建立直观认知。3.2 阶段二第11到20天把数据结构摸熟学算法绕不开数据结构。我在这个阶段按照“数组、链表、栈、队列、哈希表、树、堆”的顺序逐个击破。这个顺序很有讲究数组和链表是基础存储结构栈和队列是受限制的操作接口哈希表是解决快速查找问题的利器树和堆开始引入层级结构和优先级概念。每个结构都要动手实现一遍基本操作而不是只在题目里用现成的库。比如手写一个链表反转、手写一个栈用来判断括号匹配、手写一个小顶堆用来解决TopK问题这样才能真正体会到每个结构适合解决什么问题。3.3 阶段三第21到31天开始接触搜索和一点动态规划搜索算法DFS深度优先搜索、BFS广度优先搜索是暴力枚举的升级版也是图论的基础。DFS用递归或者栈实现BFS用队列实现。刷题的话可以找几道经典的迷宫问题、全排列问题、岛屿数量问题来练手。动态规划在这个阶段我不建议扎太深但至少要理解最基础的思路把一个大问题拆成重叠的子问题用数组把子问题的答案存起来避免重复计算。一个简单的斐波那契数列从递归到记忆化搜索再到递推数组就能串起动态规划的核心思想。贪心算法在这个阶段也只挑最经典的几个题看比如区间调度、跳跃游戏。3.4 第32天这个节点我做了三件事一是把前三十天学的核心专题过了一遍思维导图跟着脑海里复述每个算法的核心思路不看笔记说不上来就回看二是专门用半天时间把排序算法和暴力枚举剪枝这两个专题打透三是找了三道以前觉得困难的综合题限时完成检验自己的掌握程度。这三道题的检验结果让我明确了下一阶段的方向字符串匹配里的KMP算法、二分图匹配的匈牙利算法、还有图论里的Tarjan算法这些热搜里频繁出现、也是很多面试题库常客的内容要提上日程了。但我不打算一口气全学还是沿用“更弱智”的策略一天只死磕一个点。4. 算法学习里那些踩过的坑和排查技巧4.1 时间复杂度的致命误判我给自己的算法题卡时间的时候经常出现“明明没几行代码为什么超时”的尴尬。后来排查发现大部分超时问题的根源不是某个for循环本身而是循环里调用了高代价操作。最典型的是在循环里使用字符串拼接比如在Java里用String的加号循环拼接大量字符串实际每次拼接都会创建一个新的字符串对象代价从表面上看起来的O(1)变成了O(n)整体复杂度直接升一档。我总结了一条排查方法论先把每行代码写成“时间复杂度乘以执行次数”列成一张清单再找最重的那一项。这一步做完基本上90%的复杂度问题都能发现。4.2 边界条件就是用来出错的算法题最折磨人的不是算法本身而是边界条件。数组长度为0的情况、数组长度为1的情况、目标值出现在数组第一位、目标值出现在数组最后一位、递归到只剩一个元素、左右指针相遇的瞬间……这些场景我每个都出过错。后来养成了一个习惯写完代码先不看示例数据而是自己构造三组边界用例空的、最小的、最大的跑完再手动模拟一遍过程。说句真心话这个方法帮我避免了很多次提交后的一片红。4.3 调试要先打印再谈抽象我这里分享一个对新手特别有用的调试方法当代码结果不对的时候不要急着去猜哪里错了而是在关键节点打印中间状态。比如排序算法每轮结束后打印整个数组暴力枚举里每个分支进入时打印当前选中的元素。打印出来的数据能非常直观地告诉你是哪一轮开始错的是枚举少了还是比较逻辑错了。这一步定位准确之后再回头审视代码逻辑往往一眼就能看到问题。等调试熟练了再学习使用断点和单步执行来排查效率更高。4.4 常见问题速查表现象可能原因排查思路程序运行超时循环里套了高代价操作或枚举状态空间过大估算执行次数列出每行的复杂度输出结果总是少一种情况循环范围少一个或递归忘记回溯状态检查for边界检查visited标记是否恢复结果全都不对比较运算符写反或剪枝条件过于激进打印中间结果单独测试剪枝条件数组下标越界区间写法不统一统一使用左闭右开写法排序结果不稳定用于稳定排序的场景用了不稳定算法确认业务对稳定性要求选择归并5. 这段时间亲测好用的工具和下一步方向5.1 可视化工具和刷题平台我学算法用到的高频工具值得单独列一下。可视化方面强烈推荐在浏览器里打开VisuAlgo这个网站里面用动画演示了各种排序、搜索、图算法直观程度远远超过看代码。第一次看到快速排序的动画时我瞬间理解了基准值是如何把数组层层划分的这是看文字描述完全没有的效果同时力扣平台仍然是刷题首选可以按专题分类刷题并且能看到别人的解题思路和复杂度分析。我自己给每个专题设置一个最低刷题数量比如排序三题、DFS五题、BFS五题、DP三题量不大但每道都要吃透。5.2 算法笔记怎么记才有用踩了一个月的坑之后我发现最有效的笔记方式不是抄代码而是记录三件事这道题的核心思路是什么拿到这题时怎么从题干联想到这个思路的以及我的代码在哪里最容易写错。这种“思路-联想-易错点”方式比“题目-答案”式记录对复习更有用。我还会隔三天翻一次旧笔记专门检验自己不看答案能不能重新做出来。很多当时觉得“看懂了”的题隔三天再做发现完全不会这才是真实的学习状态。重复到第三次能独立完成这个知识点才算真正属于我。5.3 下一步KMP、图论和更广阔的世界第32天之后我给自己排了三个方向还是保持一天一个专题的节奏字符串方向的KMP算法核心是利用部分匹配表避免重复匹配图论方向的匈牙利算法和Tarjan算法一个解决二分图最大匹配问题一个解决强连通分量问题树状数组和线段树这种更高级的数据结构排在更靠后的位置。另外热搜词里还有大量强化学习、深度强化学习、粒子群算法这类机器学习方向的名称虽然短期之内我不会深入学数学原理但至少需要了解这些算法的基本思想框架和适用场景。这种“广泛了解、定向深入”的方式适合用来建立自己的算法知识地图避免被网络上铺天盖地的热搜词带偏。5.4 一个建议给自己的学习留一点“弱智”的空间我越来越觉得“更弱智地学习算法”不是自嘲而是一种刻意的方法。每学一个新算法先不急着看结论、背模板而是问一句如果我不学这个算法用最笨的办法能不能解决这个问题想清楚笨办法的痛点再看高级算法聪明在哪里。这个从“笨”到“聪明”的过程才是算法学习最核心的乐趣。比如不先自己写一遍O(n²)的两数之和暴力解就很难真正理解哈希表把查找从O(n)降到O(1)是多么惊艳的设计。第32天是这段旅程的中间点前面的一个月证明了我这种“地毯式、逐步推进”的方法在我身上有效后面的日子里我依然会保持每天只啃一小块硬骨头的节奏认真暴力认真剪枝认真把每一个“不弱智”的算法都用“弱智”的方式学明白。

读完文章,也想定制专属网站?

尧图设计师 24 小时内与您沟通定制方案

免费获取报价 →
↑