算法这个词,这几年快被说烂了。大厂面试要考算法,竞赛要刷算法,连做数据分析、写点自动化脚本都得懂点算法。但你要是真去问一个刚入门的同学:算法到底是什么?十有八九会得到这样的回答:算法就是LeetCode上那些题,或者就是快速排序、动态规划那堆代码。我在带新人的这几年里发现,能把这个问题讲清楚的人真不多。我自己当年也踩过这个坑——刷了一百多道题,背了不少模板,等到面试官问了一句“你这个解法为什么比那个快”,一下就愣住了。其实算法不是一串代码,也不只是一堆公式。地图导航规划路线、短视频推荐你感兴趣的内容、搜索引擎把网页排序,背后全是算法在工作。算法是解决问题的一套具体步骤,是“怎么把事办成”的思考方式。这篇是算法入门系列的第一篇,用大白话讲清楚算法的定义、五个特征、两个入门必会的经典算法例子,再给一段能直接上手的实操路径。适合零基础想系统学算法的人,也适合刷题刷得满头问号、准备回头打地基的同学。
1. 为什么入门第一课要纠结“什么是算法”
因为这个问题不掰扯清楚,后面所有算法学习都是空中楼阁。你看着是学会了快速排序、学会了KMP,但稍微变个问法就不会做,根子就在这儿:你把算法理解成了“代码段”,而不是“思考方式”。
1.1 代码只是算法的翻译,不是算法本身
我先打个比方。你把“宫保鸡丁”的做法用文字写下来:鸡胸肉切丁、花生米炸脆、调一碗糖醋汁、起锅热油下鸡丁炒变色,最后倒入料汁和花生米翻匀。这套步骤写到纸上,就是一个菜谱。同一份菜谱,米其林大厨照着做、食堂阿姨照着做、你回家照着做,做出来的口味可能有点差异,但流程是一样的——因为菜谱本身不依赖任何人。
算法就是这么一张“菜谱”。它是一个解决问题的步骤序列,和用哪口锅、哪个铲子没关系。同样一个“从一个数组里找到最大的数”的问题,用Python写、用Java写、用C++写,代码完全不一样,但思路都一样:假设第一个数是最大的,然后遍历剩下所有数,遇到更大的就更新。这个思路,也就是算法,可以写成文字、伪代码、流程图,甚至用画圈的方式表示。代码只是把思路翻译给计算机听,算法才是你真正要设计和理解的东西。
我刚入门的时候犯过一个错:看到一道题,第一反应是“这题用哪个语言能解”。后来才转过弯来,算法跟语言无关。这也是面试里经常出现的考察点——面试官不会满足于你会写代码,他会追问:你想怎么解决?时间复杂度多少?为什么这样做是对的?这些问题,全都在问“算法”,而不是问“代码”。
1.2 面试和刷题真正考的是“思路”,不是背代码
可能有人会说,既然算法不绑定语言,那我刷题时候把快速排序的模板、堆排序的模板背下来,不也算会算法了吗?这里要先泼一盆冷水。背模板能让你写出那几段固定的代码,但题目稍微变一变,就露馅了。
举个面试常见变种题:给定一个整数数组,返回两个数的下标,使这两个数加起来等于某个目标值。如果你只会暴力枚举模板,那就是两层循环,O(n²)。但你要是只背过“两层for循环”的写法,根本想不到用哈希表把时间复杂度降到O(n)。这个能力,只能靠理解思路来获得,背代码背不出来。
所以第一篇一定要把“什么是算法”掰扯清楚。你后面看任何算法教程、任何题解,第一步都是去抓它的思路框架,而不是先盯代码。思路框架长什么样?它就像一份提纲:输入是什么,输出是什么,中间分几步,每一步做什么。有了这个框架,代码只是把框架填上细节而已。
2. 判定算法的五个特征:记住这把尺子
教科书上给算法下过严格定义,但我觉得新手不需要背定义,更要紧的是知道“怎么判断一套做法算不算算法”。经典的说法是,算法需要满足五个特征:有输入、有输出、确定性、可行性、有限性。这五条我逐个拆开说,每条都配个生活例子。
2.1 有输入、有输出、每一步都确定
先看输入和输出。任何算法都要说清楚:它处理的是什么,最后要给你什么。拿最简单的“找最大值”来说,输入是一个数组,输出是数组里最大的那个数。如果题目连输入都没说清楚,算法根本没法设计。菜谱也一样,你得先说清楚食材清单,再说清楚成品是什么样。
再看确定性。确定性的意思是,同样的输入,执行的过程和结果必须是明确、可复现的,不能出现“看情况”这种模糊指令。比如“如果数字差不多大,就随便挑一个”,这句话里“差不多”和“随便”都没有明确定义,那就不是一条合法的算法步骤。计算机程序里一旦出现未定义行为,结果就不可复现。生活里也有类似场景:你跟朋友说“有空咱聚聚”,什么时候算有空、去哪里聚、几点见,全都含糊,所以这只是一句客套话,不是一个可执行的方案。
2.2 能执行、跑得完:可行性与有限性
可行性要求算法的每一步都得是能实际操作完成的,不能只是理论上存在。比如“把数组里所有元素都加1然后输出”,可行;但“把全世界所有计算机的内存都清零”这种步骤,当前条件下做不到,不可行。工程上还要考虑另一层:如果一个算法需要的内存比宇宙里的原子还多,那从现实角度它就没有可行性。
有限性要求算法必须能在有限的步骤内结束,不能死循环。比如“不断输出1”,如果不说什么时候停,它就不是一个算法,而是一个无休止的过程。写代码时忘了给循环加退出条件,程序会卡死——在算法层面,这叫缺乏有限性。一个合格的算法,必须保证对任意合法输入,都在有限步内给出结果。
我给新人的建议是:拿到任何一个算法描述,先用这五条过一遍。没有输入输出,先帮忙补上;看到模糊指令,要追问清楚;如果它跑不完,那就有bug。这套检查动作做顺手了,你看算法题的速度会快很多,而且不容易跑偏。
3. 两个入门必会的经典算法:查找与排序的直觉
前面讲的都是概念,这一节上点具体的东西。算法入门绕不开两座大山:查找和排序。这两个不会,后面几乎所有复杂算法都施展不开。我把它们单独拿出来讲,不是要你背代码,而是要你建立“直觉”。
3.1 二分查找:从猜数字到折半搜索
先来一个生活场景。朋友让你猜他脑子里想的数字,范围是1到100,每次只能问“是大了还是小了”,你要猜几次才能中?最笨的办法是从1开始猜,1不对,2不对,3不对……如果要猜的数字是100,得猜100次。但如果换个策略:先猜50,对方说大了,说明答案在1到49之间;再猜25,对方说小了,答案在26到49之间;继续折半,最多7次就能锁定答案。这就是二分查找的核心直觉。
放到数组里也一样。假设有序数组是 [2, 5, 7, 9, 13, 18, 20],我们要找13。先看中间位置,下标是3,内容是9。9比13小,说明目标只可能在后半段,于是把范围缩到下标4到6;再看中间下标5,内容是18,18比13大,把范围缩到下标4到4;最后看到下标4恰好是13,返回4。整个过程只看了三个数,这就是二分查找的威力:它的时间复杂度是O(log n)。n翻一倍,它只多走1步;n翻十倍,它才多走3步多。
但这里有一个必须记住的前提:数组必须有序。数组无序时二分没法用,因为你不能保证“中间数比目标值大,目标就一定在前半段”。这个前提很多人会忽略,面试一紧张就忘。我建议你学这个算法时,拿笔纸把1到16写成一排,亲手模拟“区间不断减半”的过程,走上几遍比看十遍代码都管用。后面你学二叉搜索树、平衡树,本质上都是在复用这个“折半”思想。
3.2 冒泡排序:为什么它叫“冒泡”,又为什么不高效
排序算法是另一个必练项目,冒泡排序是最直观的一个。给你一个无序数组 [5, 3, 8, 6, 2],你想从小到大排。冒泡的思路是:从前往后,两两比较相邻的元素,如果前一个比后一个大,就交换它们。这样一轮下来,最大的数会像气泡一样“冒”到数组最末尾。
具体走一遍。第一轮:5和3比,交换,数组变成 [3, 5, 8, 6, 2];5和8比,不换;8和6比,交换,变成 [3, 5, 6, 8, 2];8和2比,交换,变成 [3, 5, 6, 2, 8]。第一轮结束,8已经到末尾。第二轮对前四个数重复,6又沉到倒数第二的位置。几轮之后,数组就整齐了。写冒泡排序时,你会有很直观的感觉:每一轮都有一个数字被推到了它最后该待的位置,像气泡从水底浮上来。
为什么说冒泡排序适合入门?因为它贴切人类直觉,代码也好写。但它是不高效的排序方式:两层循环嵌套,时间复杂度是O(n²)。数组只有几十个元素时看不太出来,数据到一万、十万,就能明显感觉到它“慢”了。这也是算法分析的现实意义——同样的活儿,换个做法,性能差几个数量级。学的时候还可以多想一步:如果某一轮没有任何交换发生,说明数组已经有序了,可以提前结束。你能主动想到这个优化点,说明你开始有算法优化的意识了。
4. 算法和数据结构的顺序问题:别被“先学什么”卡住
新手学算法经常纠结:我是不是得先把数据结构全部学完,再开始学算法?还有人一上来就背“数组、链表、栈、队列、树、图”的定义,背完就忘,回头还是不会做题。这个顺序问题,我直接说结论:不要等,两者可以同步学,但要有主次。
4.1 数据结构是食材仓库,算法是做菜流程
我用厨房打个比方。数据结构就像厨房里的各种容器和食材柜:数组是一个带编号的小格子柜,链表是一条串起来的链条,栈是一摞盘子只能从最上面拿,队列是排队打饭的队伍,先进先出。算法则是你要做的事:把食材挑出来、切好、下锅炒熟。你不可能等把全世界的锅碗瓢盆全买齐了才开始做饭,正常做法是手里有什么就用什么,缺哪个补哪个。
入门阶段通常只需要数组就够用了。查找、排序、递归、动态规划的入门题,绝大多数都能在数组上完成。链表、栈、队列、树、哈希表这些结构,等遇到具体问题再学效果反而更好。比如你想快速判断一个元素在不在集合里,用数组一个个找太慢,这时候你自然会想“有没有更快的办法”,哈希表就在这个需求下登场,你也记得更牢。面试中那些KMP、Tarjan、匈牙利算法,名字唬人,学起来也都不需要先堆砌完所有数据结构,而是按“先读问题、再选容器、再用算法解决”的流程走。所以我的建议是:数据结构跟着算法题走,别在开头死磕。
4.2 零基础也能学算法:纸笔演算和画流程图的实操方法
另一种常见说法是:我连代码都不会写,怎么能学算法呢?其实学算法和学编程语言可以解耦。你完全可以用文字、伪代码、流程图来学习和设计算法,不需要先精通一门语言。
我自己带过没写过一行代码的同学,做法很朴素:先拿纸笔画流程图。比如二分查找,他把“取中间数、比较、缩小范围”几个方框画出来,用箭头连起来,反复走几遍,算法步骤就记在脑子里了。后来他学了点Python语法,再看伪代码,很快就写出真正的代码。这个方法特别适合入门,因为它逼着你关注“流程”而不是“语法”。算法本质上是一种流程设计,画流程图的习惯如果保持下来,以后做项目、排查逻辑问题也都用得上。
画流程图有个小技巧:不用追求画得规范,关键是每个步骤必须写清楚“下一步走哪条分支”。如果某个分支条件含糊,比如写了“看情况处理”,说明这一步还没想明白,得回头补定义。这其实就是前面讲的“确定性”在实操中的体现。等流程图画顺了,再翻译成任何一门语言,都只是换一套表达方式的问题。
5. 算法入门实操:伪代码、复杂度和正确的刷题姿势
最后聊点能直接上手的东西。很多同学学算法最大的困惑不是“看不懂”,而是“不知道怎么练”。这一节我给出一套实操路径:先用伪代码拆思路,再用复杂度判断方案好不好,最后聊正确的刷题姿势。
5.1 用伪代码拆思路,先别急着开IDE
拿到一道算法题,最忌讳的是立刻打开IDE开始写代码。新手尤其容易这样,写一半卡住,再回去读题,半天就没了。我的建议是:先在纸上写一段伪代码。
伪代码长什么样?给你写一个二分查找的例子:
输入:有序数组A,目标值target 输出:target在A中的下标,找不到则返回-1 left = 0 right = len(A) - 1 while left <= right: mid = (left + right) / 2 if A[mid] == target: return mid elif A[mid] < target: left = mid + 1 else: right = mid - 1 return -1这段东西,看着像代码又不是代码。它省掉了很多语言细节:不用管用Python还是Java,不用纠结mid的除法要不要取整,更不用处理编译器报错。你只需要关注逻辑本身。能在纸上把伪代码写出来,说明思路是通的;写不出来,说明某个环节没想明白,这时候回头补,比对着IDE的报错猜半天高效得多。平时在Word里写伪代码,我习惯用Consolas或者Courier New这类等宽字体,加上缩进,看起来就跟程序排版一样舒服,也方便标注“这段对应后面的哪几行代码”。
伪代码还有个好处:方便讨论。在群聊里甩一段伪代码,别人一眼看懂你思路,比贴一屏报错信息强多了。等你把伪代码写稳,再翻译成自己熟悉的语言,基本就是体力活。这一步做好了,能省掉大量无谓的调试时间。
5.2 看懂时间复杂度:从生活场景建立直觉
复杂度分析是算法的“体检报告”,不懂它,你就分不清两个解法谁优谁劣。最常用的是时间复杂度,用大O记号表示。这儿不讲严格定义,先建立直觉。
几个常见复杂度,我用生活场景对照说明:
| 复杂度 | 含义 | 生活类比 |
|---|---|---|
| O(1) | 操作时间不随数据量变化 | 鞋柜有编号,你说拿5号,一步拉开 |
| O(n) | 时间与数据量成正比 | 在一堆没标签的纸箱里找东西,只能一个个翻 |
| O(n²) | 数据翻倍,时间翻四倍 | 给n个朋友两两配对握手,人数一多就没完 |
| O(log n) | 数据翻倍,时间只加常数 | 查字典时不断把候选范围砍半,字典再厚也多不了几次翻页 |
写算法题,先看数据范围。如果n是10的5次方,O(n²)基本就超时了,就得想有没有O(n log n)或者O(n)的做法;如果n只有100,那O(n²)甚至O(n³)都能接受。这种估算能力,面试必考,工程实用,做题更是核心技能。空间复杂度意思类似,就是“额外占了多少地方”,能讲清楚时间和空间复杂度,一道题基本就站稳一半。
刷题姿势方面,再给点实在建议:按专题刷,别按难度刷。今天学二分,就做十道二分题;明天学排序,就做十道排序题。这样能在短时间内反复验证同一个思路,形成条件反射。刷题数量不是第一位的,能不能把一道题的思路流利讲出来才是。每做完一道题,花两分钟在纸上写一遍复杂度分析,再对照题解,看看漏了哪些边界条件。这种“慢”练习,比一天刷五十道然后全忘光要强太多。
最后说点我个人的体会。我带过的同学里,凡是能把“什么是算法”用自己的话讲清楚的人,后面学排序、搜索、动态规划都很顺;凡是上来就刷题背模板的,过了一个月基本都会回来问“该怎么复习”。算法这两个字,听起来高大上,本质就是一套解决问题的方法论。你不必背下所有代码,但一定要建立这样的习惯:拿到问题,先拆输入输出,再想用什么容器,然后设计步骤,最后分析复杂度。这篇先把地基打好,下一篇我会挑一个具体的经典算法,完整走一遍从问题描述到方案推导再到代码实现的过程。读到这里,你可以先动手做一件事:拿一张纸,把二分查找的步骤拆成你最能理解的伪代码。写完你会发现,算法入门的第一道门槛,已经迈过来了。