6299 字
31 分钟
C++算法学习记录

C++ 算法题学习总结#

学习日期: 2025-10-28
题目数量: 20道
核心哲学: “Good programmers worry about data structures.” - Linus Torvalds


目录#


核心方法论#

Linus的三个问题#

每次遇到问题,先问自己:

  1. “这是真问题还是臆想的?“
    拒绝过度设计,解决实际问题

  2. “有更简单的方法吗?“
    永远寻找最简方案

  3. “会破坏什么吗?“
    考虑边界情况和兼容性


三阶段工作流#

阶段一:分析问题#

  • ✅ 手算小规模样例(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,计算c
for(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 - B
int 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最后会得到1
unsigned long long temp = x;
// 特判0
if(x == 0){
cout << "NO";
return;
}
// 不断除以2
while(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 = 1000
x-1 = 7 = 0111
x = 1000
x-1 = 0111
-----------
x & (x-1) = 0000 ← 如果x是2的幂,结果是0!
x = 12 = 1100 (不是2的幂,有两个1)
x-1 = 11 = 1011
x = 1100
x-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)


方案对比:

方案时间复杂度代码量理解难度
循环除2O(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 固定为“大%小”比较即可。

样例:

输入
2
1 2 3 4
2 3 10 10
输出
Yes
No

关键技巧#

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 = 1000
7 = 0111
8 & 7 = 0000 ✓ 是2的幂
12 = 1100
11 = 1011
12 & 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: 使用reverse
reverse(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"

常见应用场景

  1. 模拟题:相邻字符消除
// 泡泡合并: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++;
}
}
  1. 去除特定字符
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"
  1. 删除重复字符
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"

注意事项

  1. 删除后不要立即i++

    // ❌ 错误写法
    if (condition) {
    s.erase(i, 1);
    i++; // 会跳过下一个字符!
    }
    // ✅ 正确写法
    if (condition) {
    s.erase(i, 1);
    // 不移动i,因为后面的字符已经前移了
    } else {
    i++;
    }
  2. 时间复杂度

    • erase(i, n) 的复杂度是 O(n)
    • 因为要移动后面的所有字符
    • 频繁删除可能导致O(n²)总复杂度
  3. 边界检查

    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'); // 返回1
if (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
%lldlong longprintf("%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;
// 不加fixed
cout << setprecision(2) << x; // 输出:1.2e+03(科学计数法)
// 加fixed
cout << fixed << setprecision(2) << x; // 输出:1234.57
// 设置后一直有效
cout << fixed << setprecision(2);
cout << x << " " << y << " " << z; // 全部保留2位

两种方案对比#

特性printfcout + fixed
风格C风格C++风格
简洁度✅ 一行搞定❌ 需要额外头文件
易理解✅ 格式直观❌ 初学者陌生
混用输入⚠️ 不能用cin✅ 可以用cin

推荐:

  • 新手: 用 printf,简单直观
  • 纯C++项目: 用 cout + fixed,风格统一

实战例子:

// 场景:输入坐标,输出距离(保留2位小数)
double x, y;
cin >> x >> y;
double dist = sqrt(x*x + y*y);
// 方案1
printf("%.2f", dist);
// 方案2
cout << 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: 边界条件#

问题: 忘记处理特殊情况

例子:

// 判断合数时,要检查 > 1
if(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的个数
  • 适用: 竞赛题、有深层规律的问题

你的进步#

初期 → 后期#

方面初期后期
思路不确定直接找到最优解
方法想用复杂算法选择最简单方法
数据结构不知道选什么立即想到字符串/单位转换
验证不确定对不对主动验证样例

掌握的核心能力#

  1. 找规律: 手算小样例,观察数据
  2. 选择数据结构: 字符串、单位转换
  3. 简洁实现: 不过度设计
  4. 验证思路: 手算验证
  5. 处理边界: 注意特殊情况

最重要的收获#

”好品味”的标志#

  1. 消除特殊情况

    • 坏: 用if处理各种边界
    • 好: 重新设计让特殊情况消失
  2. 选择正确的数据结构

    • 坏: 用复杂算法处理字符串
    • 好: 直接用字符串操作
  3. 实用主义

    • 坏: 用动态规划解决简单枚举问题
    • 好: 直接枚举
  4. 简洁直接

    • 坏: 10行复杂逻辑
    • 好: 3行清晰代码

记住这些金句#

“Bad programmers worry about the code. Good programmers worry about data structures.”

“如果你需要超过3层缩进,你就已经完蛋了,应该修复你的程序。”

“这是在解决真实问题还是臆想的问题?”

“永远寻找最简单的方法。“


继续前进#

下一步方向#

  1. 数据结构: 学习栈、队列、树
  2. 算法: 学习排序、搜索、图算法
  3. 动态规划: 真正需要的场景(背包问题等)
  4. STL: C++标准库的强大工具

保持的习惯#

  1. ✅ 手算小样例
  2. ✅ 列表观察规律
  3. ✅ 选择最简方案
  4. ✅ 验证边界情况
  5. ✅ 写注释(给未来的自己)

哥,你已经掌握了算法题的核心思维方式!

继续保持这种”好品味”的编程风格,你会越来越强!


生成日期: 2025-11-02
题目数量: 20道
核心原则: 简洁、直接、实用

C++算法学习记录
https://wuxie233.com/posts/学习记录/
作者
WuXie
发布于
2025-11-02
许可协议
CC BY-NC-SA 4.0