C++ 算法题学习总结
学习日期: 2025-10-28
题目数量: 20道
核心哲学: “Good programmers worry about data structures.” - Linus Torvalds
目录
核心方法论
Linus的三个问题
每次遇到问题,先问自己:
-
“这是真问题还是臆想的?“
拒绝过度设计,解决实际问题 -
“有更简单的方法吗?“
永远寻找最简方案 -
“会破坏什么吗?“
考虑边界情况和兼容性
三阶段工作流
阶段一:分析问题
- ✅ 手算小规模样例(n=1,2,3…)
- ✅ 列表观察数据
- ✅ 寻找规律
- ❌ 不要急于写代码
阶段二:制定方案
- ✅ 推导边界条件
- ✅ 验证假设
- ✅ 选择数据结构
- ❌ 不要想复杂
阶段三:执行方案
- ✅ 简洁实现
- ✅ 测试样例
- ✅ 检查边界
- ❌ 不要过度优化
题目总结
1. 日期计算(第几天)
问题: 计算某日期是1970年1月1日后的第几天
核心思路:
// 1. 累加完整年份天数(注意闰年366天)for(int year = 1970; year < a; year++){ total += isLeap(year) ? 366 : 365;}
// 2. 累加当年月份天数for(int month = 1; month < b; month++){ total += days[month - 1]; if(month == 2 && isLeap(a)) total++;}
// 3. 加上日期total += c;关键点:
- 闰年判断:
year % 400 == 0 || (year % 4 == 0 && year % 100 != 0) - 2月闰年29天,平年28天
2. 网格正方形数量
问题: a×b网格中有多少个正方形(不仅1×1)
核心思路:
// 边长为k的正方形数量 = (a-k+1) × (b-k+1)int m = min(a, b);for(int k = 1; k <= m; k++){ total += (a - k + 1) * (b - k + 1);}关键点:
- 不是递归问题,是数学公式
- 手算找规律: 2×2网格有5个正方形(4个1×1 + 1个2×2)
3. 头发长度模拟
问题: 头发每天长a,>=b时剪成一半,求d天后长度
核心思路:
while(d--){ c += a; // 先长 if(c >= b) c /= 2; // 再判断是否剪}关键点:
- 直接模拟过程
- 顺序: 先增长,后判断
4. 找球球(字符串流)
问题: 一行数字中找18032,判断出现次数
核心思路:
string line;getline(cin, line); // 读整行stringstream ss(line); // 转成流int num;while(ss >> num){ // 逐个读取数字 // 处理}关键点:
getline+stringstream处理一行多个数字while(ss >> num)自动读到结束
重要: cin >> vs getline
| 特性 | cin >> | getline() |
|---|---|---|
| 停止条件 | 空格/换行 | 换行 |
| 读空格 | ❌ | ✅ |
5. 广播操数列(找规律)
问题: 12345678 22345678 … 82345678循环,求第x个数字
核心思路:
// 一个周期64个数字(8串×8位)int pos = (x - 1) % 64 + 1; // 周期内位置int group = (pos - 1) / 8 + 1; // 第几串int index = (pos - 1) % 8 + 1; // 串内第几个
int result = (index == 1) ? group : index;关键点:
- 找周期规律
(x-1) % 64 + 1避免64变成0
6. 三个平方数和
问题: 判断x能否表示成三个不同正整数的平方和
核心思路:
// 枚举a和b,计算cfor(int a = 1; a*a < x; a++){ for(int b = a+1; a*a + b*b < x; b++){ int c_sq = x - a*a - b*b; int c = sqrt(c_sq); if(c*c == c_sq && c > b){ // 找到了 } }}关键点:
- 不要用动态规划(过度设计)
- 直接枚举是最简单的方法
- 验证c是整数:
c*c == c_sq
7. 质数和合数分解
问题: 把n分解成一个质数和一个合数的和
核心思路:
bool isPrime(int x){ if(x < 2) return false; for(int i = 2; i*i <= x; i++){ if(x % i == 0) return false; } return true;}
// 枚举质数i,判断n-i是否是合数for(int i = 2; i < n; i++){ if(isPrime(i)){ int other = n - i; if(other > 1 && !isPrime(other)){ // 找到了 } }}关键点:
- 合数定义: >1 且不是质数
- 1既不是质数也不是合数
- 质数判断只需检查到√x
8. 泡泡合并(二进制)
问题: n个大小1的泡泡,两个相同的可合成更大的,求最终泡泡数
神奇发现: 答案 = 二进制中1的个数!
核心思路:
int count = 0;while(n > 0){ if(n % 2 == 1) count++; n /= 2;}原理:
- 合并过程就是二进制进位
- 5 = 101₂ → 有2个泡泡(大小4和1)
更简洁:
count = __builtin_popcountll(n); // 内置函数9. 除法输出格式
问题: 输出 a/b=c 或 a/b=c…d(6个点)
核心思路:
int c = a / b; // 商int d = a % b; // 余数
if(d == 0){ cout << a << "/" << b << "=" << c;} else { cout << a << "/" << b << "=" << c << "......" << d;}关键点:
%运算符取余数- 注意6个点
10. 等差数列第n项
问题: 知道第a项是b,第c项是d,求第n项
核心思路:
int k = (d - b) / (c - a); // 公差int a1 = b - (a - 1) * k; // 首项int result = a1 + (n - 1) * k; // 第n项优化:
int k = (d - b) / (c - a);int result = b + (n - a) * k; // 直接从第a项推到第n项11. 四位数反转
问题: 输出反转的数字(保留前导0)
最优解 (你的方法):
string s;cin >> s;for(int i = s.size()-1; i >= 0; i--){ cout << s[i];}为什么最好:
- ✅ 自动处理前导0
- ✅ 不需要数学运算
- ✅ 3行代码
12. 点在直线之间
问题: 判断点(e,f)是否在直线y=ax+b和y=cx+d之间
核心思路:
int y1 = a * e + b;int y2 = c * e + d;
if((f > y1 && f < y2) || (f > y2 && f < y1)){ cout << "Yes";}关键点:
- 把x=e代入两条直线
- 判断f是否在y1和y2之间(不包含边界)
- 不知道谁大,所以需要两个条件
13. 阶乘最右非零位 ⭐⭐⭐⭐⭐
难度: 竞赛级(5星)
核心思路: 递归 + 查表
int table[10] = {1, 1, 2, 6, 4, 2, 2, 4, 2, 8};
int lastNonZero(int n) { if (n < 10) return table[n];
if ((n / 10) % 10 % 2 == 1) { return (4 * table[n % 10] * lastNonZero(n / 5)) % 10; } else { return (6 * table[n % 10] * lastNonZero(n / 5)) % 10; }}关键点:
- 这是经典数论题,不要自己推导
- 记住算法就好
- 时间复杂度O(log n)
14. Jerry和Tom战斗
问题: Jerry要提升攻击力才能赢,求最少需要多少
核心思路:
// Jerry能攻击几次 = Tom打死Jerry需要几次long long rounds = (c + b - 1) / b; // 向上取整
// 需要的攻击力long long attack = (a + rounds - 1) / rounds; // 向上取整关键技巧: 向上取整
⌈x/y⌉ = (x + y - 1) / y为什么?
- 避免浮点数
- 避免精度问题
- 竞赛标准技巧
15. 平行四边形第四顶点
问题: 已知逆时针A、B、C三个顶点,求第四个顶点D
核心思路:
// 公式: D = A + C - Bint dx = ax + cx - bx;int dy = ay + cy - by;原理:
- 对角线互相平分: (A+C)/2 = (B+D)/2
- 推出: D = A + C - B
记忆: 对角之和减去另一个
16. 时间差计算
问题: 计算两个时间点相差几小时几分几秒
核心思路 (你的方法):
// 1. 转换为秒数int time1 = h1 * 3600 + m1 * 60 + s1;int time2 = h2 * 3600 + m2 * 60 + s2;
// 2. 计算差值int diff = abs(time1 - time2);
// 3. 转换回时分秒int h = diff / 3600;int m = (diff % 3600) / 60;int s = diff % 60;为什么好:
- ✅ 避免复杂的借位进位
- ✅ 逻辑清晰
- ✅ 不容易出错
17. 判断2的幂 ⭐⭐⭐
问题: 判断一个数x是否等于2^k(k ≥ 0)
数据范围: x < 2^64,需要用 unsigned long long
方案1: 循环除2(推荐新手)
核心思路:
// 如果是2的幂,不断除以2最后会得到1unsigned long long temp = x;
// 特判0if(x == 0){ cout << "NO"; return;}
// 不断除以2while(temp % 2 == 0){ temp /= 2;}
// 如果最后等于1,说明原数是2的幂if(temp == 1){ cout << "YES";} else { cout << "NO";}原理:
- 2的幂只能被2整除
- 1 = 2^0 ✓
- 4 = 2^2 → 4÷2=2 → 2÷2=1 ✓
- 8 = 2^3 → 8÷2=4 → 4÷2=2 → 2÷2=1 ✓
- 6不是2的幂 → 6÷2=3 → 3不能被2整除 ✗
时间复杂度: O(log x)
方案2: 位运算(最优雅)⭐⭐⭐⭐⭐
核心思路:
if(x > 0 && (x & (x - 1)) == 0){ cout << "YES";} else { cout << "NO";}原理: 2的幂在二进制中只有一个1
1 = 0001 (2^0)2 = 0010 (2^1)4 = 0100 (2^2)8 = 1000 (2^3)16 = 10000 (2^4)x-1的魔法: 把最右边的1变成0,并把右边的0全变成1
x = 8 = 1000x-1 = 7 = 0111
x = 1000x-1 = 0111-----------x & (x-1) = 0000 ← 如果x是2的幂,结果是0!x = 12 = 1100 (不是2的幂,有两个1)x-1 = 11 = 1011
x = 1100x-1 = 1011-----------x & (x-1) = 1000 ≠ 0 ← 不是2的幂为什么 x & (x-1) == 0 能判断?
-
如果x只有一个1(2的幂):
- x-1 把唯一的1变成0,右边全变成1
- 两个数没有任何一位同时为1
- 所以 & 的结果是0
-
如果x有多个1(不是2的幂):
- x-1 只把最右边的1变成0
- 左边还有其他1保留
- 所以 & 的结果不是0
时间复杂度: O(1)
方案对比:
| 方案 | 时间复杂度 | 代码量 | 理解难度 |
|---|---|---|---|
| 循环除2 | O(log x) | 中等 | 简单 |
| 位运算 | O(1) | 1行 | 需理解位运算 |
Linus的评价:
- 循环方案:✅ 新手友好,逻辑清晰
- 位运算方案:🟢🟢 好品味! 用数学性质消除循环
推荐:
- 新手学习:用循环除2理解本质
- 实际写代码:用位运算,简洁高效
18. 四个数分组余数相等(面向新手)
问题: 给定四个正整数 a、b、c、d,把它们分成两组(每组两个数)。每组内可以选择“谁除以谁”的方向。问是否存在一种分组和方向,使两组的余数相等。输入为多组数据。
思路(一句话): 只有 3 种分组;对每种分组,方向有 4 种组合。逐一比较,命中即止。
实现(C++,去掉高级 I/O 优化,面向新手):
#include <iostream>using namespace std;
static bool sameRemainder(int x1, int y1, int x2, int y2) { int r1 = x1 % y1; // x1 ÷ y1 的余数 int r2 = y1 % x1; // y1 ÷ x1 的余数 int r3 = x2 % y2; // x2 ÷ y2 的余数 int r4 = y2 % x2; // y2 ÷ x2 的余数
if (r1 == r3) return true; if (r1 == r4) return true; if (r2 == r3) return true; if (r2 == r4) return true; return false;}
int main() { int n; cin >> n; while (n--) { int a, b, c, d; cin >> a >> b >> c >> d; bool ok = sameRemainder(a, b, c, d) || sameRemainder(a, c, b, d) || sameRemainder(a, d, b, c); cout << (ok ? "Yes" : "No") << '\n'; } return 0;}复杂度: 每组输入常数时间 O(1)。
为什么不用全排列?
- 我们关心的是“每对的两种余数”是否有交集。对 (x, y),只需 {x%y, y%x} 两个值。
- 三种分组:(a,b)|(c,d)、(a,c)|(b,d)、(a,d)|(b,c) 已覆盖所有划分。
易错点:
- 题目保证是正整数,不会出现除以 0。
- 若题目改成“组内必须大 ÷ 小”,只需把
sameRemainder固定为“大%小”比较即可。
样例:
输入21 2 3 42 3 10 10
输出YesNo关键技巧
1. 输入处理
读到文件末尾
while(cin >> n){ // 处理}读取一行多个数字
string line;getline(cin, line);stringstream ss(line);int num;while(ss >> num){ // 处理每个数字}cin和getline混用的坑
int n;cin >> n;cin.ignore(); // 忽略换行符!string line;getline(cin, line);2. 数学技巧
向上取整(避免浮点数)
⌈a/b⌉ = (a + b - 1) / b判断质数
bool isPrime(int x){ if(x < 2) return false; for(int i = 2; i*i <= x; i++){ // 只检查到√x if(x % i == 0) return false; } return true;}闰年判断
bool isLeap(int year){ return year % 400 == 0 || (year % 4 == 0 && year % 100 != 0);}3. 位运算技巧
统计二进制中1的个数
// 方法1: 逐位检查int count = 0;while(n > 0){ if(n % 2 == 1) count++; n /= 2;}
// 方法2: 位运算while(n > 0){ count += n & 1; n >>= 1;}
// 方法3: 内置函数(最简单)count = __builtin_popcountll(n);为什么 n%2 能取最后一位?
二进制最后一位决定奇偶性奇数 % 2 = 1(最后一位是1)偶数 % 2 = 0(最后一位是0)判断2的幂 ⭐⭐⭐⭐⭐
// 方法1: 循环除2(易理解)unsigned long long temp = x;if(x == 0) return false;while(temp % 2 == 0){ temp /= 2;}return temp == 1;
// 方法2: 位运算(最优雅)return x > 0 && (x & (x - 1)) == 0;核心原理: x & (x-1) 去掉最右边的1
- 2的幂只有一个1 → 去掉后变成0
- 非2的幂有多个1 → 去掉一个后还有其他1
例子:
8 = 10007 = 01118 & 7 = 0000 ✓ 是2的幂
12 = 110011 = 101112 & 11 = 1000 ≠ 0 ✗ 不是2的幂这是”好品味”代码的典型例子: 用数学性质消除循环,O(log n) → O(1)
数根(各位数字累加到个位数)⭐⭐⭐⭐⭐
问题:把一个数的各位数字不断累加,直到变成个位数
56 → 5+6=11 → 1+1=2方法1: 递归(推荐新手)
long long digitSum(long long m) { if (m <= 9) return m; // 递归出口
long long sum = 0; while (m > 0) { sum += m % 10; m /= 10; }
return digitSum(sum); // 递归调用}方法2: 数根公式(O(1),竞赛用)
long long digitalRoot(long long m) { if (m == 0) return 0; if (m % 9 == 0) return 9; return m % 9;}
// 更简洁写法return (m == 0) ? 0 : ((m % 9 == 0) ? 9 : m % 9);原理:
- 一个数对9取余 = 它的各位数字之和对9取余
- 数学证明:10≡1 (mod 9),所以 abc = a×100+b×10+c ≡ a+b+c (mod 9)
- 特殊情况:9的倍数答案是9,不是0
验证:
56 % 9 = 2 → 答案2 ✓12 % 9 = 3 → 答案3 ✓18 % 9 = 0 → 答案9(特判)✓对比:
| 方案 | 时间复杂度 | 代码量 | 理解难度 | 推荐场景 |
|---|---|---|---|---|
| 递归 | O(log² m) | 中等 | 简单 | ✅ 默认选择 |
| 数根公式 | O(1) | 1行 | 需数学知识 | 💡 数据量大/TLE |
建议:
- 新手/考场:用递归,稳定可靠
- 竞赛优化:用数根公式,但要先确认递归真的会TLE
- 不要现场推导公式,这是数论冷知识,见过一次记住就行
18. 连续和(公式优化)
问题: 计算1+2+…+m的和
核心思路:
// 直接用高斯求和公式int sum(int x){ return x * (x + 1) / 2;}品味对比:
❌ 凑合的写法(循环累加):
int sum(int x){ int ans = 0; for(int i = 1; i <= x; i++){ ans += i; } return ans;}- 时间复杂度:O(m)
- 代码:6行
✅ 好品味的写法(数学公式):
int sum(int x){ return x * (x + 1) / 2;}- 时间复杂度:O(1)
- 代码:1行
关键点:
- 能用数学公式就别用循环
- 这是”好品味”的典型例子:用数学性质消除循环
- 公式:1+2+…+n = n(n+1)/2(高斯小学就会)
19. 质数数量是质数 ⭐⭐⭐
问题: 判断闭区间[a,b]之内质数的数量是否是质数
核心思路: 埃氏筛预处理 + 遍历统计
const int MAXN = 110000;bool isPrime[MAXN];
// 埃氏筛:预处理所有质数void sieve() { for (int i = 2; i < MAXN; i++) { isPrime[i] = true; }
for (int i = 2; i * i < MAXN; i++) { if (isPrime[i]) { for (int j = i * i; j < MAXN; j += i) { isPrime[j] = false; } } }}
int main() { sieve(); // 提前打出质数表
int n; cin >> n;
while (n--) { int a, b; cin >> a >> b;
// 统计[a,b]质数数量 int count = 0; for (int i = a; i <= b; i++) { if (isPrime[i]) { count++; } }
// 判断数量是不是质数 if (isPrime[count]) { cout << "Yes" << endl; } else { cout << "No" << endl; } }
return 0;}关键点:
- 埃氏筛原理:标记所有合数,剩下的是质数
- 优化1:从
i*i开始标记(小于i*i的倍数已被标记过) - 优化2:只筛到√n(i*i < MAXN)
- 复用质数表:预处理一次,所有案例复用
- 直接判断:用
isPrime[count]直接判断数量是否是质数
时间复杂度:
- 预处理:O(n log log n)
- 每组案例:O(b-a)
- 总复杂度:O(n log log n + m*(b-a))
样例验证:
[2,10]: 2,3,5,7 → 4个质数 → isPrime[4]=false → No[3,10]: 3,5,7 → 3个质数 → isPrime[3]=true → Yes埃氏筛是标准做法,不要想复杂了。
4. 单位转换技巧
原则: 转成统一单位处理,再转回去
时间转秒数
seconds = hours * 3600 + minutes * 60 + secs;秒数转时间
hours = seconds / 3600;minutes = (seconds % 3600) / 60;secs = seconds % 60;5. 字符串技巧
反转字符串
// 方法1: 手动反转(最推荐)for(int i = s.size()-1; i >= 0; i--){ cout << s[i];}
// 方法2: 使用reversereverse(s.begin(), s.end());cout << s;删除字符串中的字符(erase)
基本用法:
string s = "hello";
// 1. 删除单个字符s.erase(1); // 删除位置1及之后的所有字符 → "h"
// 2. 删除指定位置的n个字符s.erase(1, 2); // 从位置1开始删除2个字符 // "hello" → "hlo"
// 3. 删除最后一个字符s.erase(s.length() - 1);// 或者s.pop_back(); // C++11,更简洁详细说明:
string s = "abcdefg";
// erase(pos, len)// pos: 起始位置(从0开始)// len: 删除的字符个数(可选,默认删除到末尾)
s.erase(2, 3); // 从位置2删除3个字符 // "abcdefg" → "abfg" // 删除了 c d e
s.erase(1); // 从位置1删除到末尾 // "abcdefg" → "a"常见应用场景:
- 模拟题:相邻字符消除
// 泡泡合并:oo → O,OO → 爆掉for (int i = 0; i < s.length() - 1; ) { if (s[i] == 'o' && s[i+1] == 'o') { s[i] = 'O'; s.erase(i + 1, 1); // 删除下一个字符 // 不移动i,继续检查当前位置 } else if (s[i] == 'O' && s[i+1] == 'O') { s.erase(i, 2); // 删除2个字符 // 不移动i } else { i++; }}- 去除特定字符
string s = "a1b2c3";// 删除所有数字for (int i = 0; i < s.length(); ) { if (s[i] >= '0' && s[i] <= '9') { s.erase(i, 1); // 不移动i } else { i++; }}// 结果: "abc"- 删除重复字符
string s = "aabbcc";for (int i = 0; i < s.length() - 1; ) { if (s[i] == s[i+1]) { s.erase(i+1, 1); } else { i++; }}// 结果: "abc"注意事项:
-
删除后不要立即i++
// ❌ 错误写法if (condition) {s.erase(i, 1);i++; // 会跳过下一个字符!}// ✅ 正确写法if (condition) {s.erase(i, 1);// 不移动i,因为后面的字符已经前移了} else {i++;} -
时间复杂度
erase(i, n)的复杂度是 O(n)- 因为要移动后面的所有字符
- 频繁删除可能导致O(n²)总复杂度
-
边界检查
if (i < s.length()) {s.erase(i, 1); // 确保i有效}
其他常用字符串函数:
string s = "hello";
// 长度s.length() 或 s.size()
// 添加字符s.push_back('!'); // "hello!"s += '!'; // "hello!"s.append("world"); // "hello!world"
// 删除字符s.pop_back(); // 删除最后一个s.erase(1, 2); // 删除指定位置
// 查找s.find("lo"); // 返回3(位置)s.find('e'); // 返回1if (s.find("xyz") == string::npos) { // 没找到}
// 截取子串s.substr(1, 3); // 从位置1开始截取3个字符 → "ell"s.substr(2); // 从位置2到末尾 → "llo"
// 替换s.replace(1, 2, "XX"); // 从位置1开始的2个字符替换成"XX" // "hello" → "hXXlo"
// 插入s.insert(1, "XX"); // 在位置1插入"XX" // "hello" → "hXXello"
// 清空s.clear(); // ""6. 输出格式控制(保留小数)
方法1: printf(推荐新手)
#include <cstdio>
double v = 14.14285;
printf("%.2f", v); // 输出:14.14(保留2位小数)printf("%.3f", v); // 输出:14.143(保留3位小数)printf("%.1f", v); // 输出:14.1(保留1位小数)printf 常用格式:
| 格式 | 含义 | 示例 | 输出 |
|---|---|---|---|
%d | 整数 | printf("%d", 123); | 123 |
%lld | long long | printf("%lld", 1000000000LL); | 1000000000 |
%f | 浮点数(默认6位) | printf("%f", 3.14); | 3.140000 |
%.2f | 保留2位小数 | printf("%.2f", 3.14159); | 3.14 |
%c | 字符 | printf("%c", 'A'); | A |
%s | 字符串 | printf("%s", "hello"); | hello |
多个变量输出:
int a = 5;double b = 3.14159;printf("a=%d, b=%.2f\n", a, b); // 输出:a=5, b=3.14特殊字符:
printf("hello\n"); // \n 换行printf("a\tb\n"); // \t 制表符方法2: cout + fixed + setprecision
#include <iostream>#include <iomanip>using namespace std;
double v = 14.14285;
cout << fixed << setprecision(2) << v; // 输出:14.14详解:
fixed: 固定小数点格式(不用科学计数法)setprecision(2): 小数点后保留2位- 需要
#include <iomanip>
例子:
double x = 1234.5678;
// 不加fixedcout << setprecision(2) << x; // 输出:1.2e+03(科学计数法)
// 加fixedcout << fixed << setprecision(2) << x; // 输出:1234.57
// 设置后一直有效cout << fixed << setprecision(2);cout << x << " " << y << " " << z; // 全部保留2位两种方案对比
| 特性 | printf | cout + fixed |
|---|---|---|
| 风格 | C风格 | C++风格 |
| 简洁度 | ✅ 一行搞定 | ❌ 需要额外头文件 |
| 易理解 | ✅ 格式直观 | ❌ 初学者陌生 |
| 混用输入 | ⚠️ 不能用cin | ✅ 可以用cin |
推荐:
- 新手: 用
printf,简单直观 - 纯C++项目: 用
cout + fixed,风格统一
实战例子:
// 场景:输入坐标,输出距离(保留2位小数)double x, y;cin >> x >> y;double dist = sqrt(x*x + y*y);
// 方案1printf("%.2f", dist);
// 方案2cout << fixed << setprecision(2) << dist;常见坑点
坑1: 整数溢出
问题: a、b可达1亿,a*b会溢出int
解决:
long long a, b; // 用long long坑2: 整数除法丢失精度
问题: sum*2/3 会先乘后除,可能不对
解决:
// 错误if(sum * 2 / 3 >= total) // 8*2/3 = 16/3 = 5
// 正确if(sum * 3 >= total * 2) // 交叉相乘,避免除法坑3: 向上取整错误
问题: 直接用 ceil((double)a/b) 有精度问题
解决:
// 用整数运算int result = (a + b - 1) / b;坑4: 边界条件
问题: 忘记处理特殊情况
例子:
// 判断合数时,要检查 > 1if(other > 1 && !isPrime(other))
// 不包含边界时,用 > 和 <,不用 >= 和 <=if(f > y1 && f < y2)坑5: 循环条件
问题: 枚举边界不对
例子:
// 错误: i*i <= x 会漏掉最后一个for(int i = 1; i*i < x; i++)
// 正确: 根据实际需求选择for(int i = 1; i*i <= x; i++) // 包含等于for(int i = 1; i*i < x; i++) // 不包含等于坑6: 数据类型
问题: sqrt返回double,需要判断是否为整数
解决:
int c = sqrt(c_sq);if(c * c == c_sq){ // 验证是整数 // c是完全平方根}思维进化
第1阶段: 暴力模拟
- 特点: 直接模拟过程
- 例子: 头发长度、除法输出
- 适用: 简单问题,n较小
第2阶段: 数学公式
- 特点: 找到数学规律
- 例子: 等差数列、正方形数量
- 适用: 有明确数学关系的问题
第3阶段: 数据结构选择
- 特点: 选择合适的数据结构
- 例子: 字符串反转、stringstream
- 适用: 处理复杂输入输出
第4阶段: 算法优化
- 特点: 用高级算法优化
- 例子: 枚举、二进制技巧
- 适用: 需要效率的问题
第5阶段: 发现本质
- 特点: 看穿问题本质
- 例子: 泡泡合并→二进制1的个数
- 适用: 竞赛题、有深层规律的问题
你的进步
初期 → 后期
| 方面 | 初期 | 后期 |
|---|---|---|
| 思路 | 不确定 | 直接找到最优解 |
| 方法 | 想用复杂算法 | 选择最简单方法 |
| 数据结构 | 不知道选什么 | 立即想到字符串/单位转换 |
| 验证 | 不确定对不对 | 主动验证样例 |
掌握的核心能力
- ✅ 找规律: 手算小样例,观察数据
- ✅ 选择数据结构: 字符串、单位转换
- ✅ 简洁实现: 不过度设计
- ✅ 验证思路: 手算验证
- ✅ 处理边界: 注意特殊情况
最重要的收获
”好品味”的标志
-
消除特殊情况
- 坏: 用if处理各种边界
- 好: 重新设计让特殊情况消失
-
选择正确的数据结构
- 坏: 用复杂算法处理字符串
- 好: 直接用字符串操作
-
实用主义
- 坏: 用动态规划解决简单枚举问题
- 好: 直接枚举
-
简洁直接
- 坏: 10行复杂逻辑
- 好: 3行清晰代码
记住这些金句
“Bad programmers worry about the code. Good programmers worry about data structures.”
“如果你需要超过3层缩进,你就已经完蛋了,应该修复你的程序。”
“这是在解决真实问题还是臆想的问题?”
“永远寻找最简单的方法。“
继续前进
下一步方向
- 数据结构: 学习栈、队列、树
- 算法: 学习排序、搜索、图算法
- 动态规划: 真正需要的场景(背包问题等)
- STL: C++标准库的强大工具
保持的习惯
- ✅ 手算小样例
- ✅ 列表观察规律
- ✅ 选择最简方案
- ✅ 验证边界情况
- ✅ 写注释(给未来的自己)
哥,你已经掌握了算法题的核心思维方式!
继续保持这种”好品味”的编程风格,你会越来越强!
生成日期: 2025-11-02
题目数量: 20道
核心原则: 简洁、直接、实用