算法与数据结构
灵神:如何科学刷题? https://leetcode.cn/discuss/post/3141566/ru-he-ke-xue-shua-ti-by-endlesscheng-q3yd/
新动计划: https://leetcode.cn/studyplan/primers-list/
| 结果 | 含义 | 核心排查方向 | 出错记录 |
|---|---|---|---|
| PE | 格式错误 | 空格 / 换行 / 大小写 / 输出顺序 / 字符宽度(如 %5d)/ 标点 | 打印时多了一个空格 |
| WA | 答案错误 | 逻辑漏洞、边界条件、数据类型溢出、特殊用例未处理(如 0 / 负数 / 极值) | 边界0没处理 |
| RE | 运行时错误 | 数组越界、除 0、空指针、栈溢出、递归爆栈、scanf 参数漏 & | |
| TLE | 超时 | 算法复杂度太高、死循环、不必要的重复计算 | |
| MLE | 内存超限 | 数组开太大、内存泄漏、重复申请内存 | |
| CE | 编译错误 | 语法错误(少分号 / 括号)、头文件缺失、变量未定义、数据类型写错 |
平衡二叉树要求所有节点的左右子树高度差≤1
C++
1 |
|
输入输出溢出
INT_MAX 表示int类型能表示的最大整数。INT_MIN 表示int类型能表示的最小整数。
原封不动地打印:cout << R"(...内容...)" << endl;
P1000 超级玛丽游戏 - 洛谷
打印:
| 类型 | 格式符 | 备注 |
|---|---|---|
| int | %d | |
| float、double | %f | |
| long long | %lld | |
| char | %c | |
| string | %s | string s; cin >> s; C++里用string替代char数组,无需固定长度,更安全 |
特殊格式控制:
| 说明 | 格式符 | 备注 |
|---|---|---|
| 占 5 个字符宽度(右对齐) | %5d | L1-008 求整数段和 - 团体程序设计天梯赛-练习集 |
| 占 5 个字符宽度(左对齐) | %-5d | |
| 保留 2 位小数(四舍五入) | %.2f | |
| 保留 0 位小数(取整) | %.0f | |
| 补前导 0 到 5 位 | %05d |
cout 有一个自动优化功能!当小数部分是 0 的时候,它会自动不显示小数点和 0!
1 | cout << 32.0 << endl; // 输出:32 |
在 C/C++ 的转义字符规则中:
- 需要转义的字符:只有
\"(双引号)、\\(反斜杠)、\n(换行)、\t(制表符)等少数字符(因为这些字符有特殊含义); - 不需要转义的字符:
!、'(单引号)、,、.、-等普通符号,直接写即可。
sqrt() 返回double。如果需要int类型,要强制转int。例如:int k = (int)sqrt((n + 1) / 2)
L1-002 打印沙漏 - 团体程序设计天梯赛-练习集
pow() 返回double。
◆优化方案
尽量避免循环遍历,最好直接下标访问。
结构体->直接数组下标访问:L1-005 考试座位号 - 团体程序设计天梯赛-练习集
当输入的值很大(比如 1e5)时,cin会比scanf慢很多,导致程序超时。必须加以下代码:
1 | int main(){ |
循环优化(这里有点有点类似JavaScript的)
1 | for(int i=0;i<s.size();i++){ |
关于溢出
1 | while(nums[right] > (long long)k * nums[left]){ |
关于数组
1 | int d[1001]; // 只声明,不初始化。数组里的数是随机垃圾值,不是0! |
◆vectorvector 是 C++ 标准库提供的动态数组容器。
动态 = 你可以手动增加、删除元素,它会自动扩容。
vector<int> count(10, 0) 这一行已经完成了数组声明 + 初始化,和 C 语言的 int count[10] = {0} 等价。
| 写法 | 含义 | 备注 |
|---|---|---|
vector<int> v; |
空的 int 型 vector(长度 0) | |
vector<int> v(10); |
长度 10 的 int 型 vector,初始值为随机值 | |
vector<int> v(10, 0); |
长度 10 的 int 型 vector,初始值全 0 | L1-003 个位数统计 - 团体程序设计天梯赛-练习集 |
vector<int> v = {1,2,3}; |
长度 3 的 vector,元素为 1、2、3 |
访问下标为0的元素值: v[0]
获得数组长度: v.size()
1 | vector<int> v; // 空的 {} |
nums.begin():指向数组第一个元素nums.end():指向数组最后一个元素的后面(不指向任何元素)nums.push_back():在最后添加元素 (有参数)nums.pop_back():在最后删除元素 (无参数)nums.empty():如果空,返回true*max_element(nums.begin(), nums.end()):找到数组中的最大数nums.back():数组最后一个值ans.erase(ans.begin() + i):删除下标i对应的数值reverse(nums.begin(), nums.end()): 数组逆序
普通数组 < vector < string,后面的完全继承前面的功能,函数名、用法几乎全通用!
易错点:
- C++ 不允许直接返回
vector<char>当成string。可以写成string(v.begin(), v.end())。 - C++里用string可以替代char数组,无需固定长度,更安全。
1 | // 已知该函数必须返回string类型 |
vector是容器类型模板,不是具体类型。
1 | vector<pair<char, int>> v; |
◆二维数组
1 | // 正确创建二维数组:行 = matrix[0].size(),列 = matrix.size() |
◆数组+栈 stack
定义
1 | stack<int> stk; // 定义一个栈,存 int 类型 |
内置函数
stk.push(10):往栈顶放元素。stk.pop():删除栈顶元素。stk.empty():判断栈是不是空的。stk.size():看栈里有多少元素。stk.top():看栈顶元素
清空栈:while (!st.empty()) st.pop();
关于字符串
◆字符串
把字符数字‘1’转成数字1:str[i] - '0'
判断是否是数字:isdigit()
L1-016 查验身份证 - 团体程序设计天梯赛-练习集(unsigned char)c把 char类型的通用字符(256个),转换成 0~255 的正整数下标,并且刚好是ASCLL值。例如(unsigned char)'a'=97 。
(unsigned char)'A' = 65 |
|---|
(unsigned char)'Z' = 90 |
(unsigned char)'a' = 97 |
L1-011 A-B - 团体程序设计天梯赛-练习集
709. 转换成小写字母 - 力扣(LeetCode)
统计每个字母出现的次数
1 | int cnt[26] = {0}; // 26个字母计数器 下标为0-25 |
当要读取带空格的字符串时,注意cin >> a 遇到空格就停了,读不完整,必须用:
1 | string a; |
字符与字符串的区别:
'a'是字符"a"是字符串
string(count, ch) 快速生成 count 个相同字符。reverse(str.begin(), str.end()) 把字符串变为逆序。
关于指针
◆指针
- 变量直接用
. - 指针必须用
->
2236. 判断根结点是否等于子结点之和 - 力扣(LeetCode)
哈希表
◆哈希表(字典)
| key | value |
|---|---|
1 | unordered_map<int, int> cnt; |
如果你用
cnt[num]访问一个从来没出现过的数字,C++ 会自动创建这个 key,并把值初始化为 0!
unordered_map 是自动扩容的动态容器!它和 vector 不一样:
vector如果你要存 100 个,最好提前开空间。unordered_map你不用管,它自己会随用随扩。
2461. 长度为 K 子数组中的最大和 - 力扣(LeetCode)
哈希表最牛的地方:查找超快。
数组要找一个数,要从头遍历:O(n)
哈希表找一个数,几乎瞬间找到:O (1)
它内部用了一种叫 哈希函数 的东西,把 key 直接 “映射” 到一个位置,不用一个个找。mp.find(key):在哈希表查找有没有这个key,如果有,返回指向它的迭代器(指针),如果没有,返回idx.end()。mp.count(key):判断是否存在
1 | auto it = cnt.find(5); |
函数
匿名函数
1 | auto f = [](string s) { ... }; // 末尾有; 用auto接收 有返回值 |
功能完全一样!
只是上面那种写法更短,能直接写在别的函数里面。
1170. 比较字符串最小字母出现频次 - 力扣(LeetCode)
[&] = 引用捕获外部所有变量
&= 引用(不是复制,直接用外面原来的变量)- 所有在
check外面定义的变量,都能直接用
1 | auto check = [&](int m) -> bool { |
数学
基础
闰年的判别条件是:该年年份能被4整除但不能被100整除,或者能被400整除。闰年的2月有29天。
素数(质数)的定义:大于 1 的自然数,除了 1 和它本身,不能被其他自然数整除。
1 | // 判断n是否为质数 |
因数的对称性—— 若 N 有一个因子i,则必然有一个因子N/i,且其中一个≤√N、另一个≥√N。所以只需枚举到√N即可覆盖所有可能的因子,时间复杂度从 O (N) 降到 O (√N),这是所有因数 / 质数题的 “提速关键”。
求最大公因数 (数学 + 递归)
1 | typedef long long ll; |
L1-009 N个数求和 - 团体程序设计天梯赛-练习集
263. 丑数 - 力扣(LeetCode)
向上取整:
12/5 = 2.4→ 向上取整 = 3;10/5 = 2.0→ 向上取整 = 2;7/3 = 2.333→ 向上取整 = 3;0/5 = 0→ 向上取整 = 0。(a + b - 1) / b实现向上取整。
| 取整需求 | 公式(整数除法) | 例题 |
|---|---|---|
| 向上取整 | (a + b - 1) / b |
码蹄集OJ-小码哥的魔法实战1 |
| 向下取整 | a / b |
|
| 四舍五入 | (a + b/2) / b |
圆周长:C=2πr;面积:S=πr2;直径:d=2r
球体表面积:S=4*π*r^2;体积:V=3/4*π*r^3
排序
sort()默认从小到大
1 | int a[5] = {3,1,4,2,5}; |
从大到小
1 | vector<int> v = {3,1,4}; |
二分查找 O (log n)
852. 山脉数组的峰顶索引 - 力扣(LeetCode)
逆向思维
1423. 可获得的最大点数 - 力扣(LeetCode)
余数取正
t = (x % k + k) % k
字典序:从左往右,一位一位比,谁先出现更小的字母,谁整体就更小。
aab 和 aba 的字典序比较:
第 1 位,aab → a,aba → a,一样,继续比下一位。
第 2 位,aab → a,aba → b,a < b,后面全部不用看了。
aab < aba
二进制与位运算
两大类:逻辑位运算符、位移运算符
位与& 二进制 同1异0
位或| 都为0才是0 其他全为1
异或^ 同0异1
按位取反 ~ 转化为二进制再取反
左移x<<y x转化为二进制,左移y位,末尾补0
右移x>>y x非负数,高位补0;x负数,高位补1。
C 语言中运算符的优先级:
==(相等判断) > &(按位与) > &&(逻辑与)
异或 (同0异1)
- 3 → 011
- 5 → 101
——— 异或后 - 6 → 110
0与任何数异或都为本身
1486. 数组异或操作 - 力扣(LeetCode)
滑动窗口
【算法题单】滑动窗口与双指针(定长/不定长/单序列/双序列/三指针/分组循环)力扣(LeetCode)
定长窗口
定长滑动窗口就是一个固定长度的小框框,在字符串上从左往右滑。
每滑一步,去掉左边出去的字符,加上右边进来的字符。
关键字:长度为k的子数组
例题:643. 子数组最大平均数 I - 力扣(LeetCode)
1 | class Solution { |
不定长窗口
不定长窗口:长度会变
- 满足条件 → 扩大右边界(拉长窗口)
- 不满足条件 → 移动左边界(缩小窗口)
最终找:满足条件的最长窗口长度
关键词:求最长子数组,求最短子数组,求子数组个数。
例题1(求最大窗口):3. 无重复字符的最长子串 - 力扣(LeetCode)
1 | class Solution { |
思维转换:给你一个二进制数组 nums ,你需要从中删掉一个元素。
请你在删掉元素的结果数组中,返回最长的且只包含 1 的非空子数组的长度。
如果不存在这样的子数组,请返回 0 。
输入:nums =
[0,1,1,1,0,1,1,0,1]
输出:5
解释: 删掉位置 4 的数字后,[0,1,1,1,1,1,0,1]的最长全 1 子数组为[1,1,1,1,1]。
思路: 窗口里面最多1个0, 返回长度-1.
例题2(求最短窗口):209. 长度最小的子数组 - 力扣(LeetCode)
1 | class Solution { |
二分查找
分享丨【算法题单】二分算法(二分答案/最小化最大值/最大化最小值/第K小) - 讨论 - 力扣(LeetCode)
二分查找时间复杂度:O(logn)
普通逐个遍历:O(n),二分查找吊打线性查找。在有序区间上,每次排除一半不可能的答案,。
基础查找
核心算法(闭区间写法):
1 | int lowerBound(vector<int>& nums, int target){ |
例题:34. 在排序数组中查找元素的第一个和最后一个位置 - 力扣(LeetCode)
利用上面的函数补充:
1 | vector<int> searchRange(vector<int>& nums, int target) { |
使用lower_bound内置函数:
1 | lower_bound(arr2.begin(), arr2.end(), target); |
1 | vector<int> searchRange(vector<int>& nums, int target) { |
部分题目需要先排序,然后在有序数组上二分查找。
二分答案
1 | class Solution { |
例题:1283. 使结果不超过阈值的最小除数 - 力扣(LeetCode)
数据结构
枚举
枚举右,维护左:对于双变量问题,例如两数之和a+b=target,可以枚举右边的数 ,转换成单变量问题,也就是在左边查找是否有a=target-b,这可以用哈希表维护。
枚举+哈希表
例题:1. 两数之和 - 力扣(LeetCode)
思路:遍历数组,用哈希表存已经看过的数字,每到一个新数字nums[i],就去哈希表里查有没有数字等于target - nums[i],有 → 直接返回答案!
1 | // 哈希表 枚举 |
买卖股票
最佳时机: 买卖股票的最佳时机是由之前买或不买的状态决定的,而之前买或不买又由更早的状态决定的…
1 | // 枚举 |
遍历每一天:
假设今天卖出,用前面所有天里最便宜的价格买入。
更新最大利润。
再把今天价格纳入,作为以后可以买入的最低价。
必须先算利润,再更新最低价!
前缀和
通过前缀和,我们可以把子数组的元素和转化成两个前缀和的差。
1 | nums[1,0,2,5,0,2,1] |
前缀和+哈希表
例题:560. 和为 K 的子数组 - 力扣(LeetCode)
1 | class Solution { |
二维前缀和
1 | 图形理解:sum[i+1][j+1] = 左边矩形和 + 上边矩形和 - 重叠部分 + 当前格子的值 |
例题:304. 二维区域和检索 - 矩阵不可变 - 力扣(LeetCode)
1 | class NumMatrix { |
差分
一维差分
原数组 arr = [1,3,3,5,8]
差分数组 d = [1,2,0,2,3]
前缀和 s = [1,3,3,5,8] = arr
把连续子数组arr[1],arr[2],arr[3]都加上10 brr = [1,13,13,15,8]
此时的差分数组[1, 12(+10), 0, 2, -7(-10)]
此时连续子数组的操作就转变成对差分数组d中两个数的操作。
对原数组 a 做「区间
[i, j]全部 +1」的操作,等价于在差分数组 d 里只改两个位置:
d[i]+= 1
d[j+1]-= 1
1 | class Solution { |
二维差分
栈
基础
vector:
进:push_back ()
出:pop_back ()
查非空:!ans.empty ()
拿最后一个值:back()
stack:
进:push ()
出:pop()
查非空:!stk.empty ()
拿最后一个值:top()
例题1:2390. 从字符串中移除星号 - 力扣(LeetCode)
1 | class Solution { |
更简洁:
1 | class Solution { |
邻项消除
◆两个字符匹配消除的思路:新建一个栈,遍历原数组,把栈顶元素与当前的遍历值作比较,如果匹配,Pop栈顶元素。
2696. 删除子串后的字符串最小长度 - 力扣(LeetCode)
1 | class Solution { |
◆两个字符匹配合并的思路:先匹配,匹配成功后Pop栈顶元素。在这之前更新当前的遍历值为合并的和,进入循环,不断合并,最后入栈。
例题:3834. 合并相邻且相等的元素 - 力扣(LeetCode)
1 | class Solution { |
◆k个相同字符匹配消除
为方便知道能否消除,栈中不能只存字符,还要存字符连续出现的次数。string(count, ch):快速生成 count 个相同字符。
例题1:1209. 删除字符串中的所有相邻重复项 II - 力扣(LeetCode)
1 | class Solution { |
例题2:3703. 移除K-平衡子字符串 - 力扣(LeetCode)
1 | class Solution { |
思考:
什么时候用 vector 当栈?
stack<>只能访问栈顶v.top()。
vector<>可以通过下标访问任意位置(栈顶、倒数第二个、倒数第三个…)。auto [ch, count] = v.back()什么意思?
把栈里的数据拷贝一份给 ch 和 count。所以上面注释里面的count++是改的临时数据,而不是栈里面存放的数据。
v.back() 是栈里的真实数据。auto [ch, count]到底是什么?
这叫结构化绑定,快速拆开 pair。不加 & → 拷贝,加 & → 引用(改原值)
1 | pair<char, int> p = {'a', 5}; |
- 为什么入栈写
push_back({a, 1}),不是[]?
因为{}代表一个pair!
1 | v.push_back( {a, 1} ); |
- 为什么用
.first.second?能不能写成p[0]p[1]?
pair是一个固定结构,不是数组。
pair、map、unordered_map都可以用first、second。
RBS
RBS-合法括号字符串
例题1:1021. 删除最外层的括号 - 力扣(LeetCode)
1 | class Solution { |
这个思路相当于把同级的括号记录成相同的数字!



