ARTICLE · INTELLIGENCE

战地情报 · 详情页

来自尧图项目组的一线实战观察与深度解析

LeetCode刷题必备:C++ STL容器与算法实战指南

LeetCode刷题必备:C++ STL容器与算法实战指南 LeetCode和C STL这两样东西放在一块学效率是最高的。纯刷题不碰STL很多代码写出来又臭又长明明十行能解决的事非要手撸一个红黑树光学STL不刷题又容易陷入“容器都会用、算法全不会”的尴尬。这篇内容就围绕这两条线展开从环境配置到容器实战从高频算法到工程迁移一次性把该踩的坑、该背的模板、该理解的原理都讲透。无论你是刚开始刷LeetCode的C新手还是想系统梳理STL用法的老手都能从这里拿到可以直接抄作业的方案。1. 整体设计与思路拆解1.1 为什么刷LeetCode一定要搭配STL我见过不少刷题的同学明明已经写到第五六十题了代码里还在自己写链表反转、自己实现哈希表甚至排序都要手写快排。这个方向不能说错只能说效率太低。LeetCode考的是算法思维和代码组织能力不是让你在面试时证明自己能徒手写出一个RBTree。STL里的容器和算法本身就是工程验证过无数次的实现刷题时直接拿来用能帮你把注意力集中在“这题怎么解”而不是“数组怎么扩容”上。但反过来说如果你只会用STL不了解底层原理也会出问题。比如有人用unordered_map存pair做键编译半天都过不了还不明白为什么有人用vector存数据后一边遍历一边erase直接踩进迭代器失效的坑里。所以正确的姿势是STL做武器算法做战术遇到容器之间的差异、迭代器的行为这些细节必须停下来搞明白。这才是“刷题指南”和“使用手册”合并成一篇文章的底层逻辑。1.2 从零到周赛的完整进阶路线不管你现在在哪个阶段我都推荐把刷题路径分成四段走而不是按题库顺序硬刷。第一段是“线性结构打底”。数组、字符串、链表、栈、队列配合vector、string、stack、queue、deque这些容器把双指针、滑动窗口、单调栈这几类基础题型过一遍。第二段是“哈希与查找”。用unordered_map、unordered_set解决计数、去重、映射类问题配套掌握lower_bound、upper_bound、二分查找的精髓。第三段是“排序与堆”。sort、stable_sort、partial_sort、priority_queue全部练熟解决TopK、区间合并、贪心调度类问题。第四段才是“进阶结构”。树、图、并查集、Trie、线段树这时候STL能帮的忙变少了但map、set依然能解决很多树形问题。这条路线对应到LeetCode上大概就是前200题热门的覆盖范围。建议每天3道新题加1道旧题复盘每周参加一次周赛检验训练成果。周赛的意义不是让你拿名次而是逼迫你在限时状态下快速选择容器、设计算法这种能力是慢慢刷题练不出来的。1.3 C标准版本与编译器的选择刷题时用哪个C标准很多人不重视实际上影响很大。我现在日常都是用C17LeetCode默认的g环境也支持C17这意味着结构化绑定、if constexpr、optional这些特性都能直接用。老一点的代码会默认C11但C11缺少很多便捷语法比如结构化绑定、string_view写出来的代码就容易啰嗦。编译器方面Windows上刷题最常见的搭配是VSCode加MinGW-w64Mac上用clangd或Xcode自带的clangLinux上就是g。不管你用哪个有一点是一致的你要知道自己当前用的编译器是什么版本、支持哪个C标准。不然写完代码在本机能编译提交到LeetCode却报编译错误多半就是标准版本或编译器扩展的问题。2. 环境准备VSCode搭配C的完整配置2.1 VSCode下配置C开发环境先说结论VSCode写LeetCode完全够用前提是配置不折腾。很多新人把时间浪费在折腾编辑器上今天装这个插件明天配那个路径最后题没刷几道环境倒是重装了五六遍。跟着下面的步骤走正常情况下半小时内能跑通第一个C程序。第一步下载并安装MinGW-w64。我建议用MSYS2来管理工具链因为MSYS2的包更新更及时安装完还能顺便拿到gdb调试器。安装后在Windows的环境变量Path里加入MinGW的bin目录然后在终端里执行g --version确认能输出版本信息。第二步在VSCode里安装三个插件C/C微软官方那个、Code Runner、C Intellisense。注意别装一堆看起来功能相似的插件插件装多了反而会互相干扰比如代码提示就容易被两个插件同时接管。第三步配置c_cpp_properties.json。这个文件是整个环境配置的核心它决定了IntelliSense能不能正常工作。你需要手动指定compilerPath指向g.exe然后在includePath里加上MinGW自带的include目录。如果你不配这个就会出现“所有函数和变量都没办法跳转”的经典问题这个我后面专门讲。第四步配置tasks.json和launch.json用于编译和调试。tasks.json里写好编译命令比如g -g main.cpp -o mainlaunch.json配好gdb调试器路径。这一步做完F5就能一键调试。2.2 Visual C Redistributable缺失的坑还有一个环境问题天天有人问明明代码在VSCode里能跑双击exe却提示缺少VCRUNTIME140.dll。这不是你代码的问题是目标机器上没有装Visual C Redistributable运行库。这个运行库不是Visual Studio本体它只是把C程序运行所需的DLL打包分发文件不大装起来也很快。问题是很多人不知道装哪个版本。如果你用的是MinGW编译那么这个报错一般不会出现因为MinGW依赖的是libgcc、libstdc等而不是VC运行库。报这个错的多半是用Visual Studio或cl.exe编译的程序或者别人给你的exe。遇到这情况直接去微软官网下载最新的Visual C Redistributable注意区分x64和x86现在绝大多数程序都是64位但你装一个x86的版本也不亏很多老程序还在依赖它。我自己的建议是x64和x86都装版本选2015-2022合集那个它会同时安装2015、2017、2019、2022四个版本的运行库。装完基本一劳永逸以后不会再遇到DLL缺失问题。2.3 64位下的编译与运行细节热词里有一个“c 64位 fopen报安全错误”这其实是Visual C编译器对fopen等函数的安全检查闹的。VC编译器默认把fopen标记为废弃的要求你用更安全的fopen_s。而g没有这个限制。如果你在Windows上用Visual C写代码不想改用fopen_s可以在文件开头定义宏_CRT_SECURE_NO_WARNINGS或者直接在项目属性里关掉SDL检查。另外64位环境下有两个细节容易踩坑。第一个是类型尺寸指针变成8字节了int还是4字节size_t是8字节遍历容器时用int i 0; i vec.size(); i这种写法编译器会警告有符号与无符号不匹配。刷题时为了省事直接写for(int i 0; i vec.size(); i)一般不报错但严谨一点应该用size_t或直接范围for。第二个是栈空间64位程序默认栈大小通常还是1MB到8MB深度递归依然可能爆栈。所以DFS这种递归算法如果递归深度超过一万层优先考虑改成迭代写法或增加栈大小。3. STL核心容器在刷题中的实战用法3.1 string与vector的基础操作细节string是刷题最高频的容器没有之一。这里先把几个容易出问题的点说清楚。第一个是字符串数组初始化。很多人写vectorstring strs {abc, def};没问题但想初始化一个字符数组时就容易卡住vectorchar chars {a, b, c};注意这里是花括号不是圆括号。还有string s(5, a)意思是生成aaaaa而string s a是会编译报错的。第二个是字符串转数组。LeetCode里经常遇到把1,2,3这种字符串拆出来。最稳妥的写法是用istringstream加getline按分隔符逐个取出std::getline(ss, token, ,)。这个方法比手写循环找逗号清楚得多也不用担心边界判断漏掉最后一个元素。转数字用stoi、stol、stoll注意如果是超长整数字符串得用stoll否则溢出。如果数值可能超过long long范围那就要自己实现大数处理了。第三个是vector的resize和reserve。reserve只预留容量不改变sizeresize直接改变size并默认初始化元素。刷DP题时我习惯先vectorvectorint dp(n, vectorint(m, 0));这样后来访问dp[i][j]不会踩到未初始化的内存。还有一个细节二维vector传参时尽量用引用避免拷贝开销。LeetCode的函数签名里很多都直接传vector的引用不需要你手动加。3.2 哈希容器unordered_map与unordered_set哈希容器是解决“查找”类题目的利器。两数之和、字母异位词分组、最长连续序列都是它们的经典应用场景。先说unordered_map的基本用法。统计字符频率时最简洁的写法是for(char c : s) mp[c];不必先判断键在不在operator[]会在键不存在时自动插入默认值。这是operator[]最大的便利。但要小心如果你只是想查键存不存在用count(key)或find(key)不要用mp[key]因为后者会在键不存在时插入一个默认值污染数据。这个坑刷题时经常遇到后面排查章节我再展开。再谈自定义哈希的问题。LeetCode里有些题目要用pairint,int或vectorint做键默认的哈希函数不支持这些类型。遇到这种情况最简单的方案是转换成string比如把pair转成to_string(a) , to_string(b)高效一点是自定义结构体哈希模板特化std::hash或自定义仿函数。但刷题求快能转字符串就转字符串省得调试半天。注意区分map和unordered_map。刷题时90%的场景用unordered_map就行因为我们是单次查询不关注有序输出。只有当你需要按键的自然顺序遍历或者需要在查找时快速获取最小/最大键时才用map底层红黑树。不要下意识地全用map性能差不少。3.3 栈与单调栈的模板化用法stack本身用法简单push、pop、top但单调栈是一个非常值得背模板的思想。热词里专门有“单调栈算法c”说明这是高频考点。单调栈的典型框架是这样的遍历一个数组维护一个栈栈内元素按某种单调性排列。比如求每个元素右边第一个比它大的元素可以维护一个从栈底到栈顶递减的栈。每次遇到一个新元素如果它比栈顶大那么栈顶元素的“右边更大元素”就是当前元素弹出栈顶并记录答案然后继续比较新栈顶直到新元素不大于栈顶再把新元素入栈。这段逻辑我建议你亲手敲一遍然后封装成自己熟悉的样子。因为单调栈的题变形非常多接雨水、柱状图中最大矩形、每日温度、去除重复字母全是从这个核心框架变出来的。你理解了“栈里存的是下标”和“什么时候弹栈”这两个关键点任何变形题都能套。3.4 优先级队列与TopK问题priority_queue默认是大顶堆最大元素在堆顶。TopK问题用大顶堆就要先弹出再保留小的比较绕大多数时候你要的是前K个最小或前K个最大的元素这时候就要自定义比较器。小顶堆的写法很多人忘了记住这个模板priority_queueint, vectorint, greaterint pq;。这个greater不是算法里的greater它是functional头文件里的仿函数作用是比较两个值。如果要存的是自定义结构体比如pairint,int需要自己写一个比较结构体struct Compare { bool operator()(const pairint,int a, const pairint,int b) { return a.second b.second; // 小顶堆按second排序 } }; priority_queuepairint,int, vectorpairint,int, Compare pq;刷题时有一个更省事的替代方案用vector存数据配合std::push_heap和std::pop_heap但操作起来还是容易出错。我建议就老老实实用priority_queue写熟了之后合并K个升序链表、数组中的第K个最大元素、前K个高频元素这些题都能在几分钟内搞定。3.5 结构体链表与迭代器注意点LeetCode的链表题是需要自己定义结构体的典型写法struct ListNode { int val; ListNode* next; ListNode() : val(0), next(nullptr) {} ListNode(int x) : val(x), next(nullptr) {} ListNode(int x, ListNode* next) : val(x), next(next) {} };这个结构体的三个构造函数是LeetCode默认带的你不需要修改它。链表题的核心是别丢节点先保存下一个节点的指针再改当前节点的next不然白回改。初学者最容易犯的错是用一个临时指针遍历时把head也搞丢了。记住原则头节点单独用一个指针指向遍历指针随便动但如果你后面要返回head就不要拿head去遍历。另外list容器刷题时用得不多。链表题之所以要手写结构体而不是直接用STL的list是因为LeetCode的节点是它自定义的我们改的是节点之间的指针关系。STL的list把内部结构封装了根本不给你操作next的机会。这两种链表不是一回事别混。迭代器失效问题也在这一块讲掉。vector和deque在插入、删除元素后之后的迭代器都可能失效map、set、list在删除元素时只有指向被删元素的迭代器失效其他迭代器还活着。所以在遍历中erase正确的姿势是it vec.erase(it);而不要it或者把需要删除的元素先用另一个容器收集起来遍历完了再统一删。这个规则刷题时经常踩到尤其是“删除有序数组中的重复项”这类需要原地操作的题。4. 高频算法的STL实现模板4.1 快速幂的优雅实现快速幂在LeetCode里是“数值的整数次方”这类题的主角。核心思想是二分幂把指数拆成二进制看每一位是否为1决定要不要乘上相应的基数。C里有一个非常干净的递归写法也有一个迭代写法。long long fastPow(long long a, long long n) { long long res 1; while (n 0) { if (n 1) res * a; a * a; n 1; } return res; }注意几个细节。第一n要取非负如果题目允许负指数需要先取倒数再算正幂或者直接用double类型。第二乘法可能溢出尤其是底数a很大时所以参数类型用long long。如果题目要求对MOD取模每次乘完之后都要res % MOD; a % MOD;不然中间结果爆炸。第三这个模板不只用于整数幂还能扩展到矩阵快速幂用来求解斐波那契数列的O(log n)算法思路完全一样。4.2 二分查找lower_bound与upper_bound的最佳实践LeetCode上有大量二分查找的题很多题目一眼看过去不是二分但其实答案是单调的这就是“二分答案”。经典例子就是“爱吃香蕉的狒狒”这类题目。题目说狒狒每小时要吃一定数量的香蕉需要多少时间才能吃完所有堆寻找一个最小的速度。这里的速度是单调的速度越大吃完所需时间越短。于是你对速度做二分每次检查在当前速度下能否在限制时间内吃完最后收敛到最小的可行速度。写二分时用STL的lower_bound和upper_bound能省很多事。lower_bound返回第一个大于等于某个值的元素位置upper_bound返回第一个大于某个值的元素位置。它们不只是用来查数组还能用来做区间计数——比如统计有序数组中小于等于x的元素个数直接upper_bound(v.begin(), v.end(), x) - v.begin()。对于“搜索旋转排序数组”这类题目STL的lower_bound不适用那就要自己写一个基于区间的二分。但是可以先用lower_bound判断是否旋转点再决定在哪部分继续二分。自己写二分时要注意的是区间闭开的选择以及退出条件。我推荐统一用左闭右开[l, r)写法退出条件是while (l r)更新时l mid 1或r mid这样不容易死循环。网上很多教程用闭区间[l, r]容易在l和r相邻时陷入无限循环。选一种你习惯且不出错的用到烂熟。4.3 排序算法从手写到sort的进阶冒泡和插入排序是面试的入门考点也是理解稳定排序的起点。但在LeetCode上你几乎不需要自己写这些基础排序算法因为STL的sort就是优化的快速排序或者叫内省排序处理绝大多数数据都是O(n log n)且在数据接近有序时表现比纯快排更好。直接用就好。需要知道的是sort的进阶用法。sort(v.begin(), v.end(), greaterint())降序排stable_sort在需要保持相等元素相对顺序时用比如“按频率对单词排序”。partial_sort求TopK时比全排序更快比如求最大的3个数partial_sort(v.begin(), v.begin()3, v.end(), greaterint())。还有nth_element可以线性时间内找到数组中第K大的元素不求全部有序能省不少计算。如果你刷TopK题时不想到用priority_queue那nth_element是你的第二选择。关于自定义排序最常用的是lambda表达式sort(people.begin(), people.end(), [](const vectorint a, const vectorint b) { if (a[0] ! b[0]) return a[0] b[0]; return a[1] b[1]; });写lambda时注意严格弱排序规则如果两个元素相等比较器必须返回false否则sort可能崩溃。还有一点lambda捕获参数时要小心引用捕获的临时变量容器在排序期间不能修改元素值否则结果不可预期。4.4 单调栈与判断质数的优化思路判断质数是很多数学类题目的基础操作。最简单的是试除法从2遍历到sqrt(n)但复杂度O(sqrt(n))在多次调用时会很吃力。更好的是预处理质数表用埃氏筛。用C写埃氏筛时可以配合vectorbool或bitset注意vectorbool是特化版本会压缩存储但访问速度稍慢。如果你既要快又要省内存bitset也很顺手只是长度需要编译期常量刷题时一般用vector 或vector 更利索。vectorint sieve(int n) { vectorbool isPrime(n 1, true); isPrime[0] isPrime[1] false; for (int i 2; i * i n; i) { if (isPrime[i]) { for (int j i * i; j n; j i) isPrime[j] false; } } vectorint primes; for (int i 2; i n; i) if (isPrime[i]) primes.push_back(i); return primes; }埃氏筛的时间复杂度是O(n log log n)在n为一百万以内时非常快。如果你只需要判断单个大数是否为质数试除法到sqrt(n)就够了不要过度设计。4.5 周赛视角如何用STL提速LeetCode周赛热词经常出现说明关注周赛的人越来越多。周赛的题目一般是四道题前两道考察基础容器和哈希表后两道涉及DP、图论或贪心有时还需要计算几何。参加过几次周赛的用户会发现前三道题完全用STL解决绰绰有余关键问题不是“STL会不会用”而是“用哪个容器、怎么组织数据”的决策速度。对比一下同样是统计一组单词的频率再做排序输出新手可能用vector嵌套pair再手写排序比较器熟练的人直接用unordered_map计数、sort排序三分钟实现完。这就是STL使用熟练度的差距。我建议每周周赛之后不要只看排名把四道题都重新做一遍分别记录自己第一次提交的用时和最终优化后的用时这样能明显看到自己决策速度的变化。5. 刷题之外的工程迁移能力5.1 回调函数的本质与STL中的实际应用热词里有一个“c回调函数例子”。很多人刷题刷到一定程度觉得算法题就是数组和字符串跟真实工程没关系。其实回调函数就是存在于STL各处的一个核心概念。sort的比较器、priority_queue的比较器、for_each里的函数对象本质都是回调。最直接的写法是lambda表达式它是C11引入的语法糖能够就地书写函数逻辑。再解释一层lambda在编译时会生成一个匿名函数对象所以它能被当作“可调用对象”传入算法。回调在工程上最常见的使用场景就是注册事件处理比如某个网络库收到数据后调用你的处理函数。你在刷题时熟练掌握lambda后面看工作代码时也会轻松很多。5.2 面向数据库的C绑定从STL到参数化写入热词里有个特别工程化的案例“tdengine, c绑定写入数据库”。TDengine是一个时序数据库它提供了C/C接口。很多人第一次接触这类接口时有点懵因为这不是STL容器而是C风格的函数调用。TDengine的写入流程大致是这样先用taos_stmt_init创建一个预处理语句对象再用taos_stmt_prepare准备SQL语句SQL里用?占位符然后逐个用taos_stmt_bind_param绑定参数最后taos_stmt_execute执行。这个过程和你在刷题时“先准备数据结构再填充数据”的思维方式完全一致只不过对象换成了数据库。举个例子往TDengine写入一条设备温度记录代码骨架大概是// 伪代码示意 TAOS_STMT* stmt taos_stmt_init(conn); const char* sql INSERT INTO meters VALUES (?, ?, ?); taos_stmt_prepare(stmt, sql, strlen(sql)); TAOS_BIND params[3]; // 分别绑定时间戳、设备ID、温度值 for (int i 0; i 3; i) { taos_stmt_bind_param(stmt, params[i]); } taos_stmt_execute(stmt); taos_stmt_close(stmt);刷题练出来的能力在这里直接体现你会关注参数对齐、类型匹配、内存生命周期因为这些本质和vector下标管理没什么区别。如果你刷题时就能处理好“结构体里的指针指向哪里”这种问题数据库参数绑定自然上手更快。5.3 时间等待、随机数、键盘映射等小工具工程中还有很多需要写的小逻辑等待一定时间、生成随机数、监听键盘输入。C11之后std::this_thread::sleep_for配合std::chrono::seconds就能实现时间等待而老式写法是sleep或Sleep。#include thread #include chrono using namespace std; this_thread::sleep_for(chrono::milliseconds(500));随机数方面不要用老的rand()配srand了那东西质量差还容易踩坑。C11提供了random库用random_device配合mt19937生成高质量随机序列。如果是刷题或者简单脚本用rand()问题不大但工程上请使用random。键盘映射这类需求一般要调用操作系统API比如Windows下用RegisterHotKey或SetWindowsHookEx这已经超出STL范畴。但它的思想仍然是“事件注册回调处理”和前面讲回调函数是一致的。不用被操作系统API吓住底层逻辑和刷题是共通的。5.4 一个经典误会STL库和STL三维模型文件热词里出现了“qopengl 加载stl3dsmax2012修复stl模型的uv”这类词。这里必须澄清一个让人头痛的命名冲突C STL库Standard Template Library和3D打印领域里的STL文件格式STereoLithography是完全不同的东西只是缩写恰好一样。如果你在QOpenGL程序里要加载STL模型你读的是一堆三角形面片数据和C的vector、map没有任何关系。同样3ds Max修复STL模型的UV是在处理网格贴图坐标也不涉及C标准库。很多初期开发者会被这两个同名的概念搞混在一个问题上搜索半天结果发现搜出来全是另一个领域的内容。遇到这类情况搜索时建议带上上下文词比如“C STL容器”和“OpenGL STL模型”就完全走两套路线了。5.5 为什么C看起来没那么“普遍”热词里有个问题很有意思“c为什么没有普遍”。这个问题其实反映了新手的困惑好像周围人都在学Python和JavaC是不是没人用了事实恰恰相反C在操作系统、游戏引擎、高性能计算、数据库内核、嵌入式、量化交易等领域依然是绝对主力。它之所以在“泛程序员”群体里显得不那么普遍是因为学习曲线陡峭几乎无法速成。Python两三天就能实现一个爬虫C三天可能还在和指针、编译错误搏斗。很多人学到指针之后就放弃了所以你在社交媒体上看到的C内容也比Python少。但如果你把LeetCode和STL吃透你会发现C表达算法题特别清晰没有GC的垃圾回收干扰也没有动态类型的隐式转换每一步都要你自己负责。正是这种“显式”的风格才让C成为吃性能的系统和算法的最佳表达语言。6. 常见问题与排查技巧实录6.1 VSCode所有函数变量都没办法跳转这是配置问题不是代码问题。最常见原因是没装C/C插件或者装了插件但没配置c_cpp_properties.json。打开VSCode命令面板输入C/C: Edit Configurations在弹出的json里把compilerPath设成实际g.exe的路径然后重启VSCode。还有一种情况是项目里有多个源文件IntelliSense需要知道每个文件的编译参数这时建议开启compileCommands配置从compile_commands.json读取。如果配置了还不跳转多半是插件版本或工作区缓存问题。把C/C插件禁用再启用或者删除.vscode目录重新生成基本能解决。切记每次切换编译器或更新工具链后要重新生成IntelliSense索引不然它还在用旧的索引数据。6.2 一边遍历一边删除容器元素程序崩溃这个问题我前面提过现在完整展开。vector遍历中执行erase后当前迭代器失效如果你继续用它自增就是未定义行为轻则跳过元素重则崩溃。正确写法是for (auto it v.begin(); it ! v.end(); ) { if (*it % 2 0) it v.erase(it); else it; }但对于刷题场景更优雅的做法是先记住要删除的下标或元素遍历结束后再删除。比如先统计每个元素出现次数再对所有出现次数大于1的键执行erase。相比一边遍历一边删除这种“先收集后处理”的方式能避免多数迭代器失效问题。6.3 string转数字和数字放大溢出问题字符串转数字用stoi、stol、stoll但也可能抛异常。如果字符串包含非数字字符stoi会抛出std::invalid_argument如果超出范围会抛出std::out_of_range。刷题时可以不管异常但工程上最好用std::from_chars它是C17引入的高性能无异常转换函数在GCC 11以上才稳定支持。数字放大溢出这个更常见。LeetCode里经常让求数组的连续子数组乘积、两个大数相加、大数乘法如果题目允许的范围超过int就要使用long long再大就要考虑字符串模拟大数运算。有一个小习惯凡涉及乘积、累加、求幂先想想会不会超过2^31 - 1就基本能规避大部分溢出问题。6.4 错误速查表我整理了一个高频错误速查表你们收藏下来遇到问题直接查症状常见原因解决方法缺少VCRUNTIME140.dll未安装VC运行库安装Visual C Redistributable 2015-2022 x64C4996: fopen 不安全MSVC安全检查定义_CRT_SECURE_NO_WARNINGS或用fopen_s变量无法跳转/IntelliSense无提示includePath或compilerPath配置错误编辑c_cpp_properties.json后重启编译报错未声明标识符头文件忘了include或命名空间冲突检查using namespace std是否与局部变量重名容器遍历时崩溃迭代器失效用it erase(it)配合重新赋值运行结果出现超大负数int溢出换成long long并检查中间结果范围递归深度过大系统栈溢出递归调用过深改写成迭代栈或使用std::stackstring转int抛异常字符串不是纯数字使用从字符串解析或from_chars6.5 几个容易忽略的实战小技巧最后写几个我平时刷题和写工程代码时沉淀下来的小技巧都属于“没人告诉你但你迟早要踩”的类别。第一个调试时善用prlong longf和条件断点。很多人调试C代码喜欢打无数断点然后一直按下一步效率低得吓人。我更推荐在关键位置打cout 输出中间变量或者只打一两个断点配合watch窗口观察特定值的走向。刷题时尤其如此因为你通常只需要看一两轮循环里的状态变化。第二个MinGW用户提交LeetCode前注意本地编译器和LeetCode的编译器差异。最典型的是#include bits/stdc.h在本地能用但有些平台不支持或表达很慢。为了保险最好养成显式include需要的头文件的习惯这也能帮助你理清依赖关系。第三个数据结构尽量在栈上创建。刷题时写vectorvectorint dp(n, vectorint(m, 0));这个dp在函数返回时会自动释放。如果你用new创建了vector记得delete。没有内存泄漏的良好习惯在写LeetCode这种短时程序时看不出问题但迁移到真实工程时就是灾难。第四个善用std::numeric_limitsint::max()获取整数最大值不要手写成0x3f3f3f3f。虽然那个鬼数在竞赛圈很流行但LeetCode刷题写代码时可读性比那一点性能重要得多。6.6 刷题三个月后的进阶建议当你把前200题热门题目刷完一遍STL常见容器都能熟练使用之后会有一种“什么题都能写但写不优雅”的感觉。这是正常的瓶颈期。这时候建议做三件事第一把之前AC过的题重新做一遍要求每道题比第一次提交少用一半时间第二开始限时模拟按30分钟、45分钟、60分钟随机抽题做第三把你自己的代码模板整理成“个人手册”比如二分模板、单调栈模板、DFS模板、BFS模板各存一份形成肌肉记忆。到这个阶段你已经不需要再关注STL容器的基本用法了而是逐渐开始思考“这个容器在这个场景下是否最优”。比如用deque实现滑动窗口求最大值明显优于vector加priority_queue的复杂维护用unordered_map做记忆化搜索的缓存时如何选择键的类型使哈希效率最高。这些思考才是从“会用STL”到“用好STL”的分水岭也是LeetCode刷到两百题以后还能继续变强的关键。我个人在实际操作中的体会是LeetCode题解看十遍不如自己动手写两遍。有一段时间我热衷于收藏各种“万题模板”收藏夹里堆了几十篇真正到用的时候脑子一片空白。后来改成每学一个模板就在当周周赛里故意找一道能套用的题去实践只有被题目“虐”过一遍那些模板才算真正长在你身上。写代码这件事没有捷径但把手里的工具用好、把错误记录好确实是能让人少走很多弯路的。
RELATED READING

延伸阅读

更多一线实战笔记与深度复盘,助您持续精进