简介:算法300题题目大全PDF整理自芬兰CSES在线评测平台,是面向ACM竞赛选手、算法面试者和系统提升编程能力的学习者的高质量算法题集。原站是芬兰的信息学竞赛训练平台,收集了大量有代表性的题目,但在部分地区访问不便;这份PDF将全部题目离线整理成册,包含约300道不同难度题目,覆盖数据结构、排序与搜索、图论、动态规划、贪心策略等核心算法方向。每题保留完整的问题描述、输入输出格式与样例测试,并附有VJudge评测链接和GitHub题解仓库指引,便于按题号练习、提交验证代码,以及对照不同解法复盘思路。资源以单个PDF文件呈现,整体大小31.43MB,结构紧凑,适合在电脑、平板等设备上随时查阅。目前已有257人学习,对准备ACM竞赛、应对技术面试或日常刷题强化算法基础,都能提供充足的实战训练量。
1. 别急着开刷:算法300题这类题目大全,先解决“从哪下手”
算法300题这类题目大全,在ACM竞赛和面试场景里几乎是标配起点。很多人第一反应是从题号1刷到300,实际效果很差:刷到一半,前面忘得差不多,遇到新题依旧没思路。我拿到PDF后的做法,是先花一天给题目做模块分类,再逐模块攻。它的价值不在“刷完”这个动作,而在于帮你建立按模块拆题、按同构题归类的习惯。适合两类人:一类是校招前想系统覆盖核心算法、又不想自己搜题库的人;另一类是ACM或算法竞赛备赛,需要按数据结构、DP、图论做突击的人。注意,这份资料通常只给题面,正确性验证要靠在线评测,后文会讲怎么配。
2. 先拆题再做题:把300题按模块归档,而不是顺序硬刷
2.1 为什么按模块刷比按顺序刷效果好
题目按编号排,考点却是打散的。我见过有位同学从第1题开始,每天10道,刷到100题时回头一翻,发现排序类、树遍历类早已忘干净。原因很简单:按顺序刷时,同一个考点分散在不同位置,学完一种又夹着七种其他类型,等你遇到同考点的下一题,已经过了两三周。大脑本来就是按“知识点”归档的,你非让它按“页数”记,效果自然不会好。
这本300题的海量价值不在题数,而在同构题密度。所谓同构题,就是用不同场景包装同一个核心解法,比如“两数之和”和“三数之和”是同一套哈希加双指针思路,“最长回文子串”和“回文链表”都要处理中心扩展。面试里常见操作是在经典题外包一层新壳,如果你刷题是零散状态,很难看出这层壳下面是什么。
我的习惯是拿到PDF先不动笔做题,花半天到一天把全部题目过一遍目录和题面,按算法标签归类。这段时间看起来浪费,实际能让你对整个题库的范围有全局感。ACM竞赛备赛更要这么做,因为竞赛题大多是多考点混合,你需要先知道单一考点的常规题型,才能去拆混合题。
模块化刷题还有一个隐性收益——遗忘曲线。集中刷同一考点,相当于在短时间内反复激活同一条神经通路,形成条件反射。面试手写环节时间紧张,条件反射比临场推演可靠得多。
2.2 题组划分建议:数据结构、搜索、动态规划、贪心与回溯
以下是常见300题题库的分类分布,你可以拿着这份表去核对你手里的PDF。如果不完全一致,按“算法名称”关键字重新归类即可,不必拘泥于原始目录。
| 模块 | 高频考点 | 建议题量 |
|---|---|---|
| 数据结构基础 | 数组、链表、栈、队列 | 35 |
| 字符串处理 | 回文、子串、模式匹配 | 25 |
| 哈希与集合 | 两数之和、频次统计、去重 | 20 |
| 双指针与滑动窗口 | 连续子数组、链表交点 | 25 |
| 二分搜索 | 有序序列查找、二分答案 | 20 |
| 排序与堆 | 手写快排、归并、Top K | 20 |
| 树与二叉树 | 遍历、最近公共祖先、路径 | 35 |
| 图与搜索 | BFS、DFS、拓扑、最短路 | 30 |
| 动态规划 | 背包、线性、区间、状态机 | 50 |
| 贪心与回溯 | 区间调度、排列、组合 | 30 |
| 位运算与数学 | 异或、GCD、快速幂 | 15 |
总计约305题,正好落在“300题”量级。表格里的“高频考点”是面试和竞赛里出现频率最高的部分,缺少的那几道一般是冷门模拟题,可以往后放。
按这个表刷的时候,注意一个比例问题:面试场景要把重心放在二分、DP、树、双指针这四个模块,因为这些几乎每次笔试都会出现;ACM竞赛备赛则要额外补贴图论和数学模块,竞赛题很喜欢在最短路径和数论上做文章。我一般会给每个模块设一个优先级,面试前一个月主攻高优先级,竞赛前主攻低优先级但高难度的部分。
2.3 用题号做标签管理,而不是只翻PDF
直接翻PDF刷题最大的问题,是进度不可见。今天做了几道、错在哪道、哪类考点还没碰,全靠脑子记,刷到两周后基本是一笔糊涂账。我建议拿到PDF后顺手建一个状态表,几行代码或Excel都行,成本很低但收益很高。
| 题号 | 模块 | 状态 | 卡点标记 | 备注 |
|---|---|---|---|---|
| 001 | 数组 | 已做 | 无 | 双指针写法值得记 |
| 002 | 链表 | 已做 | 边界 | next指针丢失 |
| 003 | DP | 未做 | - | - |
状态只保留四种:未做、已做、已吃透、待重做。每周统计一次各模块“待重做”的数量,数量最多的模块就是下周主攻方向。这一步把学习过程从黑匣子变成可视化表格,对自律性一般的刷题者特别有用。
具体操作分三步。第一步,通读PDF目录,把每个题号填到模块列;第二步,每做一题就更新状态和备注;第三步,每周日花10分钟看趋势,确定下周开始模块。这个流程一旦养成就很省力,后续不需要额外维护。
提示:状态里的“已吃透”不要轻易标,我的标准是同一道题连续三天重新打开都能在五分钟内讲清思路,才算吃透。
3. 把题目落到手上:一天五题节奏与两套必背代码骨架
3.1 训练节奏:热身、核心、回顾三段式
有了模块表,接下来是每天怎么执行。我常用的是“1+2+2”节奏,每天实际有效题量在五题左右,但真正从零写完整代码的只有两道新题,其余是口述思路和重做旧题。一周下来约25到30题,十周可以覆盖核心模块。
| 阶段 | 时间 | 内容 |
|---|---|---|
| 热身 | 10分钟 | 把状态表里“待重做”的题口述一遍思路 |
| 主体 | 60分钟 | 当前模块新题2道,先口述思路再写代码 |
| 回顾 | 20分钟 | 更新状态表,补90字卡,标记卡点 |
这个节奏的核心理念是防止“只看不写”。很多人一天能刷十道题,但其中八道是看答案然后照着敲一遍,实际没有内化。五题节奏虽然慢,但每一题都经过了“读题→想思路→落代码→对比答案→记录”五个环节,记忆牢度完全不一样。
如果你时间不够,可以把主体阶段的“先口述思路”压缩为30秒,但不要跳过。口述思路是在训练自己把想法组织成可表达的语言,面试手写前考官通常会先问一句“你的思路是什么”,这一步练习就是给那个场景准备的。
3.2 整数二分模板:可套大部分二分题
二分搜索是面试高频考点,也是看起来简单、实际最容易翻车的模块。问题几乎都出在边界条件上。我每次都会用同一套模板,思路统一,出错率低。
// 整数二分模板:在有序数组中查找目标值 int binarySearch(vector<int>& nums, int target) { int left = 0, right = nums.size() - 1; // 左右边界,闭区间 while (left <= right) { int mid = left + (right - left) / 2; // 防溢出写法 if (nums[mid] == target) return mid; // 命中直接返回 if (nums[mid] < target) left = mid + 1; // 目标在右半区 else right = mid - 1; // 目标在左半区 } return -1; // 未找到 }逻辑说明:这里用的是闭区间写法,即 left 和 right 都指向有效的数组元素。while 条件 left <= right 保证只有一个元素时循环仍能进入。mid 用 left + (right - left) / 2 而不是 (left + right) / 2,是为了防止两个大整数相加溢出,这个细节在ACM竞赛和面试里都会被考察。
参数说明:left 是下界,初始为0;right 是上界,初始为 nums.size()-1;target 是要查找的值。这套模板适用于“数组中是否存在目标值”这类标准二分题。
如果题目考的不是“是否存在”,而是“第一个不小于target的位置”,比如查找插入位置,就要换成左闭右开写法:
// 查找第一个 >= target 的位置,返回下标可作插入位置 int lower_bound(vector<int>& nums, int target) { int left = 0, right = nums.size(); // [left, right) while (left < right) { int mid = left + (right - left) / 2; if (nums[mid] < target) left = mid + 1; else right = mid; // 不排除right这个候选 } return left; }这两套模板的区别在于最终答案落在“值”还是“位置”。落位置时建议用左闭右开,收缩步骤更清晰。旋转数组最小值、有序数组插入位置、寻找峰值这几类题都可以直接套第二套模板。面试官看的是边界处理逻辑,不是死记硬背,所以每套模板至少要能解释清楚为什么 while 用 < 而不是 <=。
3.3 回溯模板:排列、组合、子集通吃
回溯是ACM竞赛和面试里另一大重点,排列、组合、子集、数独、八皇后都用同一套框架。我的经验是记住一个核心模板,然后根据题目改剪枝条件。
# 回溯模板:排列/组合/子集通用 def backtrack(path, options): # 终止条件:路径长度达到目标 if len(path) == target_len: res.append(path[:]) # 拷贝当前路径 return for i in range(len(options)): # 剪枝:去除已使用元素 if used[i]: continue # 剪枝:相同元素不重复选(需先排序) if i > 0 and options[i] == options[i - 1] and not used[i - 1]: continue # 选择 used[i] = True path.append(options[i]) backtrack(path, options) # 撤销选择 path.pop() used[i] = False逻辑说明:path 保存当前路径,options 是候选集合,used 标记某个元素是否被使用,res 收集最终结果。关键在于“选择”和“撤销选择”成对出现,这是回溯区别于普通递归的地方。
参数说明:target_len 控制终止条件,排列题通常是 len(options),组合题是固定长度,子集题则是每次进入都收集结果。当题目要求集合里有重复元素时,先对 options 排序,再用i > 0 and options[i] == options[i - 1] and not used[i - 1]去重。
这套模板在“全排列”“组合总和”“子集”这三类核心题里可以原样套用,差别只在终止条件和起始下标。用熟之后,看到“不同顺序算一种”就考虑组合的起始下标控制,看到“顺序不同算不同”就用 used 数组,基本不会偏。
3.4 每题记录一张“90字卡”
每做一题就写一张卡片,这是我认为最被低估的刷题习惯。卡片不用长,控制在90字左右,写多了反而坚持不下来。
| 题号 | 模块 | 核心考点 | 一句话思路 | 踩坑点 |
|---|---|---|---|---|
| 053 | 二分 | 旋转数组最小值 | 对比mid和right判断哪边有序 | 等号情况要单独处理 |
填卡的标准:一句话思路能让自己一周后看懂,踩坑点写当时卡住的原因,比如“忘记更新左边界”“数组越界”“没考虑空串”。面试前复习不是把300题全部重做一遍,而是把90字卡从头扫一遍,能说出思路就过,说不出的才回头重做。这套机制把题库变成了个人索引,检索效率远高于再翻一遍PDF。
4. 避坑排查:刷了100题仍不会新题,通常卡在五个细节
4.1 看得懂答案,动手就空白
现象:做题时翻到答案,每一步都觉得合理,合上书自己写,卡在第一步不知道从哪下手。
原因:这是典型的“被动理解”。看答案时大脑走的是识别路径,不是生成路径;写代码时你需要自己决定变量、循环、返回条件,这完全是另一种脑力活动。
解决:看完答案后顺手把PDF关掉,白纸上重写一遍思路,写不出来就再花30秒想想,实在想不出才允许再看一眼答案。我一般会把这个“重写一遍”的动作直接记录在90字卡的“一句话思路”里,让被动理解变成主动输出。
4.2 暴力解写完了,面试官一加测试就超时
现象:面试手写环节,想法没问题,代码也通了,但把数据规模从100加到100万,运行时间指数级上涨,当场改方案来不及。
原因:写代码前没有先估算复杂度。很多题的暴力解在数据量小的时候能跑通,但面试官要的是最优解,或至少是符合数据范围思路。
解决:动手前先看数据范围,背下三个数量级:n小于等于20,基本可以枚举全排列或二进制状态压缩;n小于等于1000,考虑O(n^2)的DP;n到10^5以上,必须O(n log n)或O(n)。面试时先说出你打算用的复杂度,再开始写,这本身就是加分项。
提示:ACM竞赛题的时限和面试不同,通常更严格,数据范围更大,估算复杂度的习惯在竞赛训练里更早养成越好。
4.3 DP题的边界条件总是错
现象:状态转移方程写对了,样例也能过,提交后数据一大就错,报的错都在数组开头几个位置。
原因:DP题最隐蔽的问题出在初始化阶段。很多人的状态转移是从 i=2 开始的,但 f[0]、f[1] 的取值没有单独推导,直接照搬公式,导致前几项全错。
解决:每个DP题先在纸上算前三个最小规模的值,再对照代码。比如最长上升子序列,先手推长度为1和2的序列预期结果,再检查 dp[0] 和 dp[1] 的赋值。写完代码后用最小输入跑一遍,例如n等于1、n等于2,覆盖初始化路径。
4.4 STL用得太爽,遇到手写排序就翻车
现象:笔试时直接用 sort 加自定义比较器,一切顺利;面试时被要求手写快排或归并,写了半天,边界还不对。
原因:把容器封装当成了算法本身。排序模块的核心题考察的是你能否从零写出稳定、不越界的排序实现,而不是会不会调用API。
解决:把第2章“排序与堆”模块的前几道题单独拎出来,快速排序、归并排序、堆排序各手写三遍,限时15分钟。归并排序要特别注意 merge 函数里左右数组的边界,快排则要小心 partition 后递归区间的开闭。补上这一课,这类翻车就不会再出现。
4.5 难题卡太久,模块推进不了
现象:连续两周卡在同一类难题上,每天想不出来就翻答案,然后自责,进度停滞,心态崩。
原因:难度跳跃没有梯度。直接从“基础变体”跳到“难题挑战”,中间缺了“中等核心”的过渡,相当于没学走就想跑。
解决:每个模块内的题目,按“基础变体→中等核心→难题挑战”的顺序排。难题单题最多给40分钟,到时间还没思路就看答案,把卡点写进90字卡,第二天再重做。不要把一道题当成证明自己的工具,它只是一个需要攻克的知识点。
5. 进阶验证:五题一验,把刷过的题变成能说出口的解法
5.1 五题一验动作与自检清单
每完成同一个模块的五道题,做一次强制验证:合上PDF和所有笔记,在白纸上用15分钟,写出这五道题的代码骨架和核心思路。所谓代码骨架,是函数签名、主循环、边界条件三层结构,不要求完整可运行代码,关键结构和覆盖边界要明确。
| 自检项 | 通过标准 |
|---|---|
| 函数签名 | 参数、返回值类型正确 |
| 主循环 | 循环条件、收缩方向无误 |
| 边界条件 | 空输入、单元素输入、两端取值都覆盖 |
| 复杂度说明 | 能口头解释最优解的时间与空间复杂度 |
这一步的设计思路是:面试手写环节,考官看重的是边界处理和思路可解释性,完整代码反而是其次;ACM竞赛训练里,骨架通过后补上细节就能提交,大部分调试时间都花在边界上。五题一验把整个模块的掌握度压缩到15分钟测试,效率远高于盲目刷新题。
5.2 错题重做机制
错题按“1天、3天、7天”三个时间点重做。第1天重做是趁热打铁,把当天的卡点在第二天验证是否解决;第3天重做看短期记忆是否还在;第7天重做才是真正的长期检验。每次重做不是从零写完整代码,而是先口述思路,再在纸上补骨架。第二次还错的题,直接标记为“重点题”,进入状态表“待重做”列,下一周模块训练时优先处理。这种带时间间隔的重复,比当天连续做三遍有效得多。
以前我刷题最不爱验证,总觉得验证环节耽误刷新题,进度比别人慢。直到某次模拟面试让我写旋转数组的二分,我改了三四遍才把边界改对,才明白“刷过”不等于“会做”。从那以后,我每次刷完一个模块,都强制走一遍五题一验,宁可少做十道新题也不省验证环节。希望帮到你。
本文还有配套的精品资源,点击获取