第 2.2 章:控制流
📝 前置条件: 第 2.1 章(变量、cin/cout、基本算术)
2.2.0 什么是「控制流」?
到目前为止,我们写的每个程序都是从上到下执行的——第 1 行、第 2 行、第 3 行,结束。就像从头到尾读一本书。
但真正的程序需要做决策并重复操作。这就是「控制流」的含义——控制执行的流(顺序)。
想象一本「选择你的冒险」书:
- 有时书上说「如果你想和龙战斗,翻到第 47 页;否则翻到第 52 页」
- 有时书上说「重复这一段,直到你逃出地牢」
C++ 通过以下方式提供了恰好对应的功能:
if/else—— 根据条件做决策for/while循环 —— 重复执行一段代码
这是控制流的总览:
在循环图示中:程序不断回到「步骤 2」,直到条件变为假,才退出到「步骤 3」。
2.2.1 if 语句
if 语句让你的程序做决策:「如果这个条件为真,就执行这件事。」
基本 if
📄 查看代码:基本 if
#include <bits/stdc++.h>
using namespace std;
int main() {
int score;
cin >> score;
if (score >= 90) {
cout << "优秀!\n";
}
cout << "完成。\n"; // 无论 score 是多少都会执行
return 0;
}
score 为 95:打印「优秀!」然后「完成。」 score 为 80:只打印「完成。」(if 块被跳过)
if / else
int score;
cin >> score;
if (score >= 60) {
cout << "通过\n";
} else {
cout << "不通过\n";
}
else 块仅在 if 条件为假时执行。两个块中恰好只有一个会运行。
if / else if / else 链
需要检查多个条件时:
📄 需要检查多个条件时:
int score;
cin >> score;
if (score >= 90) {
cout << "A\n";
} else if (score >= 80) {
cout << "B\n";
} else if (score >= 70) {
cout << "C\n";
} else if (score >= 60) {
cout << "D\n";
} else {
cout << "F\n";
}
C++ 从上到下按顺序检查这些条件,运行第一个为真的分支。一旦运行了一个块,就会跳过后面所有的 else if/else 块。
若 score = 85:
- 85 >= 90?否 → 跳过
- 85 >= 80?是 → 打印「B」,然后跳过后续所有 else-if
🤔 为什么这样有效? 当我们到达
else if (score >= 80)时,我们已经知道 score < 90(如果它 ≥ 90,第一个条件早就捕获了)。每个else if隐式假设所有前面的条件都为假。
比较运算符
| 运算符 | 含义 | 示例 |
|---|---|---|
== | 等于 | a == b |
!= | 不等于 | a != b |
< | 小于 | a < b |
> | 大于 | a > b |
<= | 小于等于 | a <= b |
>= | 大于等于 | a >= b |
逻辑运算符(组合条件)
| 运算符 | 含义 | 示例 |
|---|---|---|
&& | 且——两者都必须为真 | x > 0 && y > 0 |
|| | 或——至少一个为真 | x == 0 || y == 0 |
! | 非——将真翻转为假 | !finished |
📄 C++ 完整代码
int x, y;
cin >> x >> y;
if (x > 0 && y > 0) {
cout << "两者都为正数\n";
}
if (x < 0 || y < 0) {
cout << "至少有一个是负数\n";
}
bool done = false;
if (!done) {
cout << "还在进行中……\n";
}
🐛 常见 Bug:= vs ==
这是新手(甚至有经验的程序员)最常犯的错误之一:
📄 这是新手(甚至有经验的程序员)最常犯的错误之一:
int x = 5;
// 危险的 BUG:
if (x = 10) { // 这是把 10 赋值给 x,不是比较!
// x 变为 10,由于 10 不为零,这个条件永远为真
cout << "x 是 10\n"; // 总是执行,即使 x 最初是 5!
}
// 正确写法:
if (x == 10) { // 这才是比较 x 和 10
cout << "x 是 10\n"; // 只有 x 真的等于 10 时才执行
}
= 运算符是赋值(存储一个值);== 运算符是比较(检查两个值是否相等)。两者看起来相似,但功能完全不同。
⚡ 专业技巧: 有些程序员写
10 == x而不是x == 10——如果不小心写了=而不是==,就变成了10 = x,会是编译错误(不能对字面量赋值)。这叫「尤达条件式」。
嵌套 if 语句
可以在 if 语句里再放 if 语句:
📄 可以在 `if` 语句里再放 `if` 语句:
int age, income;
cin >> age >> income;
if (age >= 18) {
cout << "成年人\n";
if (income > 50000) {
cout << "高收入成年人\n";
} else {
cout << "普通收入成年人\n";
}
} else {
cout << "未成年人\n";
}
注意:每个 else 匹配最近的、还没有 else 的 if。
2.2.2 while 循环
while 循环在条件为真时一直重复执行一段代码。条件变为假后,执行继续到循环之后。
while (条件) {
循环体(反复执行)
}
📄 C++ 完整代码
#include <bits/stdc++.h>
using namespace std;
int main() {
int i = 1; // 1. 循环前初始化
while (i <= 5) { // 2. 检查条件——若为假则跳过循环
cout << i << "\n"; // 3. 执行循环体
i++; // 4. 更新——非常重要!忘了这步 → 死循环
}
// 循环后:i = 6,条件 6 <= 5 为假,循环退出
return 0;
}
输出:
1
2
3
4
5
🐛 常见 Bug:死循环
如果忘了更新变量(上面的第 4 步),条件永远不会变为假,循环就会一直运行!
int i = 1;
while (i <= 5) {
cout << i << "\n";
// BUG:忘了 i++——这会永远打印「1」!
}
如果程序卡住了,按 Ctrl+C 强制停止。
什么时候用 while vs for
- 当事先不知道需要迭代多少次时,用
while - 当知道次数时,用
for(下一节介绍)
while 的经典使用场景:读取直到满足某个条件。
// 常见 USACO 模式:读取直到输入结束
int x;
while (cin >> x) { // cin >> x 在输入耗尽时返回 false
cout << x * 2 << "\n";
}
do-while 循环
do-while 循环至少执行一次循环体,然后再检查条件:
int n;
do {
cin >> n;
} while (n <= 0); // 一直重新读,直到用户输入正数
当你想在判断是否重复之前先执行某件事时,这很有用。竞赛编程中不常见,但值得了解。
2.2.3 for 循环
for 循环是竞赛编程中最常用的循环。它将初始化、条件检查和更新打包在一行:
for (初始化; 条件; 更新) {
循环体
}
等价于:
初始化;
while (条件) {
循环体
更新;
}
图示:for 循环流程图
流程图展示了执行过程:初始化只运行一次,之后每次迭代前检查条件,条件为假时退出循环。
常见 for 循环模式
📄 查看代码:常见 for 循环模式
// 从 0 数到 9(竞赛编程标准模式)
for (int i = 0; i < 10; i++) {
cout << i << " ";
}
// 打印:0 1 2 3 4 5 6 7 8 9
// 从 1 数到 n(包含)
int n = 5;
for (int i = 1; i <= n; i++) {
cout << i << " ";
}
// 打印:1 2 3 4 5
// 倒数
for (int i = 10; i >= 1; i--) {
cout << i << " ";
}
// 打印:10 9 8 7 6 5 4 3 2 1
// 以 2 为步长
for (int i = 0; i <= 10; i += 2) {
cout << i << " ";
}
// 打印:0 2 4 6 8 10
🧠 循环追踪:精确理解执行过程
学习循环时,手动追踪它们。方法如下:
代码: for (int i = 0; i < 4; i++) cout << i * i << " ";
先在纸上追踪循环,再运行——这能建立直觉并帮你发现 bug。
最常见的 USACO 循环模式
读取 N 个数并逐一处理:
int n;
cin >> n;
for (int i = 0; i < n; i++) {
int x;
cin >> x;
// 在这里处理 x
cout << x * 2 << "\n";
}
⚡ 专业技巧: 竞赛编程中,
for (int i = 0; i < n; i++)以 0 为起点是标准写法。这与数组的索引方式(第 2.3 章)一致,让一切都整齐地对应。
2.2.4 嵌套循环
可以在循环里再放一个循环。内层循环对外层循环的每一次迭代都会完整执行一遍。
// 打印 4×4 乘法表
for (int i = 1; i <= 4; i++) { // 外层:行
for (int j = 1; j <= 4; j++) { // 内层:列
cout << i * j << "\t"; // \t = 制表符
}
cout << "\n"; // 每行结束后换行
}
输出:
1 2 3 4
2 4 6 8
3 6 9 12
4 8 12 16
追踪前两行:
i=1: j=1→打印1, j=2→打印2, j=3→打印3, j=4→打印4, 然后换行
i=2: j=1→打印2, j=2→打印4, j=3→打印6, j=4→打印8, 然后换行
...
⚠️ 嵌套循环的时间复杂度
💡 为什么需要关心循环次数? 竞赛中,你的程序通常需要在 1-2 秒内完成。现代计算机每秒可以执行约 10^8 到 10^9 次简单运算。因此,如果能估算出循环体总共执行多少次,就能判断是否会超时(TLE)。这是「时间复杂度分析」的核心思想——后续章节会深入学习。
单重 N 次循环执行 N 次操作;两层嵌套 N 次循环执行 N × N = N² 次操作。
| 循环层数 | 操作次数 | N 的安全上限 | 示例 |
|---|---|---|---|
| 1 层 | N | ~10^8 | 遍历数组求和 |
| 2 层(嵌套) | N² | ~10^4 | 比较所有对 |
| 3 层(嵌套) | N³ | ~450 | 枚举所有三元组 |
N = 1000 时两层嵌套是 10^6 次操作——没问题。但 N = 100,000 时是 10^10——太慢了!
🧠 快速经验法则: 看到 N 的范围后,反向利用上表判断最多能用几层嵌套循环。例如:N ≤ 10^5 → 只能用 O(N) 或 O(N log N) 算法;N ≤ 5000 → O(N²) 可以接受。这在 USACO 中极为实用!
2.2.5 switch 语句
当你需要检查一个变量的许多具体值时,switch 比一长串 if/else if 更简洁:
📄 当你需要检查一个变量的许多具体值时,`switch` 比一长串 `if`/`else if` 更简洁:
int day;
cin >> day;
switch (day) {
case 1:
cout << "星期一\n";
break; // 重要:break 退出 switch
case 2:
cout << "星期二\n";
break;
case 3:
cout << "星期三\n";
break;
case 4:
cout << "星期四\n";
break;
case 5:
cout << "星期五\n";
break;
case 6:
case 7:
cout << "周末!\n"; // case 6 和 7 共用这段代码
break;
default:
cout << "无效的日期\n"; // 没有 case 匹配时执行
}
什么时候用 switch vs if-else
用 switch 当…… | 用 if-else 当…… |
|---|---|
| 检查一个变量的具体整数/字符值 | 比较范围(x > 10、x < 5) |
| 需要检查 3 个以上具体值 | 只有 1-2 个条件 |
| 各情况互斥 | 复杂的布尔逻辑 |
🐛 常见 Bug:忘记
break—— 没有break,执行会「穿透」到下一个 case!
int x = 2;
switch (x) {
case 1:
cout << "one\n";
case 2:
cout << "two\n"; // 这会执行
case 3:
cout << "three\n"; // 这也会执行(穿透!因为 case 2 后没有 break)
}
// 输出:two\nthree\n(出乎意料!)
2.2.6 break 和 continue
break —— 立即退出循环
// 找 1 到 100 中第一个 7 的倍数
for (int i = 1; i <= 100; i++) {
if (i % 7 == 0) {
cout << "7 的第一个倍数:" << i << "\n"; // 打印 7
break; // 停止搜索——已找到
}
}
continue —— 跳到下一次迭代
// 打印 1 到 10 中除了 3 的倍数之外的所有数
for (int i = 1; i <= 10; i++) {
if (i % 3 == 0) {
continue; // 跳过本次迭代的剩余部分,直接到 i++
}
cout << i << " ";
}
// 输出:1 2 4 5 7 8 10
嵌套循环中的 break
break 只退出最内层的循环。要退出多层,用标志变量:
📄 `break` 只退出**最内层**的循环。要退出多层,用标志变量:
bool found = false;
int target = 25;
for (int i = 0; i < 10 && !found; i++) { // 外层循环也检查 !found
for (int j = 0; j < 10; j++) {
if (i * j == target) {
cout << i << " * " << j << " = " << target << "\n";
found = true;
break; // 退出内层循环;外层循环因为 !found 也会退出
}
}
}
2.2.7 竞赛编程中的经典循环模式
这些模式几乎出现在每一道 USACO 题解中。熟记它们。
模式一:读取 N 个数,计算总和
int n;
cin >> n;
long long sum = 0;
for (int i = 0; i < n; i++) {
int x;
cin >> x;
sum += x;
}
cout << sum << "\n";
复杂度分析:
- 时间:O(N)——遍历 N 个数,每次 O(1) 处理
- 空间:O(1)——只有一个累加变量
sum
模式二:找最大值(和最小值)
📄 查看代码:模式二:找最大值(和最小值)
int n;
cin >> n;
int maxVal, minVal;
cin >> maxVal; // 读第一个元素
minVal = maxVal; // 最大值和最小值都初始化为第一个元素
for (int i = 1; i < n; i++) { // 从第二个元素开始(下标 1)
int x;
cin >> x;
if (x > maxVal) maxVal = x;
if (x < minVal) minVal = x;
}
cout << "最大值:" << maxVal << "\n";
cout << "最小值:" << minVal << "\n";
复杂度分析:
- 时间:O(N)——遍历 N 个数,每次比较 O(1)
- 空间:O(1)——只有
maxVal和minVal两个变量
🤔 为什么初始化为第一个元素? 不要初始化为 0!万一所有数都是负数呢?初始化为第一个元素保证我们从输入中的真实值开始。
模式三:统计满足条件的数量
📄 查看代码:模式三:统计满足条件的数量
int n;
cin >> n;
int count = 0;
for (int i = 0; i < n; i++) {
int x;
cin >> x;
if (x % 2 == 0) { // 条件:偶数
count++;
}
}
cout << "偶数个数:" << count << "\n";
模式四:打印星号三角形
int n;
cin >> n;
for (int row = 1; row <= n; row++) { // row 从 1 到 n
for (int col = 1; col <= row; col++) { // 每行打印 row 个星号
cout << "*";
}
cout << "\n"; // 每行结束后换行
}
n=4 时输出:
*
**
***
****
模式五:计算各位数字之和
int n;
cin >> n;
int digitSum = 0;
while (n > 0) {
digitSum += n % 10; // 最后一位数字
n /= 10; // 删除最后一位
}
cout << digitSum << "\n";
追踪 n = 12345:
n=12345: digitSum += 5, n 变为 1234
n=1234: digitSum += 4, n 变为 123
n=123: digitSum += 3, n 变为 12
n=12: digitSum += 2, n 变为 1
n=1: digitSum += 1, n 变为 0
n=0: 循环退出。digitSum = 15 ✓
2.2.8 完整示例:USACO 风格题目
题目: 你有 N 头奶牛,每头都有一个产奶评分。找出评分最高的奶牛的评分,并统计产奶量高于平均水平的奶牛数量。
📄 C++ 完整代码
#include <bits/stdc++.h>
using namespace std;
int main() {
ios_base::sync_with_stdio(false);
cin.tie(NULL);
int n;
cin >> n;
// 我们需要存储所有值来与平均值比较
//(第 2.3 章学数组/向量——这里先用 vector 预览)
// 第一遍:找总和和最大值
long long sum = 0;
int maxMilk = 0;
vector<int> milk(n); // 存储所有值(第 2.3 章预览)
for (int i = 0; i < n; i++) {
cin >> milk[i];
sum += milk[i];
if (milk[i] > maxMilk) maxMilk = milk[i];
}
double avg = (double)sum / n;
// 第二遍:统计高于平均值的数量
int aboveAvg = 0;
for (int i = 0; i < n; i++) {
if (milk[i] > avg) aboveAvg++;
}
cout << "最大值:" << maxMilk << "\n";
cout << "高于平均值:" << aboveAvg << "\n";
return 0;
}
样例输入:
5
10 20 30 40 50
样例输出:
最大值:50
高于平均值:2
(平均值为 30;产奶量 40 和 50 的奶牛高于平均值 → 2 头)
复杂度分析:
- 时间:O(N)——两遍(读取 + 统计),各 O(N),总计 O(2N) = O(N)
- 空间:O(N)——使用
vector<int> milk(n)存储所有数据
⚠️ 第 2.2 章常见错误
| # | 错误 | 示例 | 错在哪里 | 修复方法 |
|---|---|---|---|---|
| 1 | 混淆 = 和 == | if (x = 10) | = 是赋值而非比较;结果永远为真 | 用 == 比较 |
| 2 | 忘记 i++ 导致死循环 | while (i < n) { ... } 没有 i++ | 条件永远为真,程序卡死 | 确保循环变量被更新 |
| 3 | switch 中忘记 break | case 2: cout << "two"; 没有 break | 执行「穿透」到下一个 case | 每个 case 末尾加 break; |
| 4 | 差一错误 | for (int i = 0; i <= n; i++) 本应是 < n | 多循环一次,可能越界或多计 | 仔细核对 < 和 <= |
| 5 | 最大值初始化为 0 | int maxVal = 0; 但所有数都是负数 | 0 比所有输入都大,结果错误 | 初始化为第一个元素或 INT_MIN |
| 6 | 嵌套循环用了相同的变量名 | 外层 for (int i...) 和内层 for (int i...) | 内层 i 遮蔽外层 i,导致外层循环行为异常 | 内外层循环用不同变量名(如 i 和 j) |
本章总结
📌 核心要点
| 概念 | 语法 | 使用场景 | 为什么重要 |
|---|---|---|---|
if | if (条件) { ... } | 条件为真时执行 | 程序决策的基础;几乎每道题都用 |
if/else | if (...) {...} else {...} | 二选一 | 处理是/否类型的决策 |
if/else if/else | 链式 | 多选一 | 评级系统、分类场景 |
while | while (条件) {...} | 次数未知时重复 | 读取到输入结束、模拟过程 |
for | for (int i=0; i<n; i++) {...} | 次数已知时重复 | 竞赛编程中最常用的循环 |
| 嵌套循环 | 循环套循环 | 需要遍历所有对 | 注意 O(N²) 复杂度限制 |
break | break; | 找到目标后立即退出 | 提前终止节省时间 |
continue | continue; | 跳过当前迭代 | 过滤掉不需要处理的元素 |
switch | switch(x) { case 1: ... } | 检查一个变量的多个精确值 | 比长 if-else 链更简洁 |
&& / || / ! | 逻辑运算符 | 组合多个条件 | 复杂决策的基础构件 |
🧩 五种经典循环模式快速参考
| 模式 | 用途 | 复杂度 | 章节 |
|---|---|---|---|
| 读取 N 个数求和 | 读取 N 个数计算总和 | O(N) | 2.2.7 模式一 |
| 找最大/最小值 | 找最大/最小值 | O(N) | 2.2.7 模式二 |
| 统计满足条件的数量 | 统计满足条件的元素个数 | O(N) | 2.2.7 模式三 |
| 星号三角形 | 用嵌套循环打印图案 | O(N²) | 2.2.7 模式四 |
| 各位数字之和 | 提取各位数字并求和 | O(log₁₀N) | 2.2.7 模式五 |
❓ 常见问题
Q1:for 和 while 可以互换吗?什么时候该用哪个?
A:是的,任何
for循环都可以改写成while,反之亦然。经验法则:知道迭代次数(如「循环 N 次」)用for;不知道次数(如「读到输入结束」)用while。竞赛中大约 90% 的情况用for。
Q2:嵌套循环可以套多少层?有上限吗?
A:语法上没有上限,但实际中超过 3 层就需要谨慎。两层嵌套是 O(N²),三层是 O(N³)。当 N ≥ 1000 时,三层嵌套很容易超时。如果发现需要超过 3 层嵌套,通常意味着需要更高效的算法(后续章节会讲)。
Q3:break 只退出最内层循环,怎么一次退出多层?
A:两种常用方法:① 用
bool found = false标志变量,让外层循环也检查!found;② 将嵌套循环放在函数里,用return直接退出。方法①更常见——参见 2.2.6 节的完整示例。
Q4:switch 和 if-else if 哪个更快?
A:case 数量少(< 10)时,性能几乎一样。
switch的优势在于代码可读性,不是速度。竞赛中两种都可以自由选择。如果条件涉及范围比较(如x > 10),必须用if-else。
Q5:程序本地输出正确,提交后却超时(TLE),怎么办?
A:第一步:估算算法复杂度。查看 N 的范围 → 用本章的「嵌套循环复杂度表」估算总操作次数 → 若超过 10^8,就需要优化。常见优化策略:减少循环层数、用排序+二分查找替代暴力搜索(第 3.3 章)、用前缀和替代重复求和(第 3.2 章)。
🔗 与后续章节的联系
- 第 2.3 章(函数与数组)让你把本章的循环模式封装成函数,并用数组存储数据集合
- 第 3.2 章(数组与前缀和)教你把 O(N²) 区间求和查询优化到 O(N) 预处理 + O(1) 每次查询——这是「嵌套循环太慢」问题的解决方案之一
- 第 3.3 章(排序与搜索)教你二分查找,把本章 O(N) 线性搜索优化到 O(log N)
- 本章学到的五种经典循环模式(求和、找最大/最小、计数、嵌套遍历、数字处理)是本书所有算法的基础构件
- 嵌套循环复杂度分析是理解时间复杂度(贯穿全书的主题)的第一步
练习题
🌡️ 热身题
热身 2.2.1 — 数到十
打印 1 到 10,每行一个数字。用 for 循环。
💡 题解(点击展开)
思路: 从 1 到 10(包含)的 for 循环。
#include <bits/stdc++.h>
using namespace std;
int main() {
for (int i = 1; i <= 10; i++) {
cout << i << "\n";
}
return 0;
}
关键点:
i <= 10(而不是i < 10)因为我们要包含 10- 也可以写
for (int i = 1; i < 11; i++)——效果相同
热身 2.2.2 — 偶数 打印 2 到 20 的所有偶数,每行一个。
💡 题解(点击展开)
思路: 两种方案——步长为 2 循环,或每次循环检查是否为偶数。
#include <bits/stdc++.h>
using namespace std;
int main() {
// 方案一:步长为 2
for (int i = 2; i <= 20; i += 2) {
cout << i << "\n";
}
return 0;
}
关键点:
i += 2每次递增 2 而不是通常的 1- 替代方案:
for (int i = 1; i <= 20; i++) { if (i % 2 == 0) cout << i << "\n"; }
热身 2.2.3 — 正负零
读取一个整数,正数打印 Positive,负数打印 Negative,零打印 Zero。
样例输入: -5 → 输出: Negative
💡 题解(点击展开)
思路: 用三路 if/else if/else 覆盖所有情况。
#include <bits/stdc++.h>
using namespace std;
int main() {
int n;
cin >> n;
if (n > 0) {
cout << "Positive\n";
} else if (n < 0) {
cout << "Negative\n";
} else {
cout << "Zero\n";
}
return 0;
}
关键点:
- 末尾的
else精确捕获n == 0(因为上面两个条件覆盖了 n>0 和 n<0)
热身 2.2.4 — 3 的乘法表 打印 3 的前 10 个倍数(即 3、6、9、……、30),每行一个。
💡 题解(点击展开)
思路: 从 1 循环到 10,每次打印 i*3。
#include <bits/stdc++.h>
using namespace std;
int main() {
for (int i = 1; i <= 10; i++) {
cout << i * 3 << "\n";
}
return 0;
}
关键点:
- 替代方案:
for (int i = 3; i <= 30; i += 3)——效果相同
热身 2.2.5 — 五数之和 读取恰好 5 个整数(可以在同一行或不同行),打印它们的和。
样例输入: 3 7 2 8 5 → 输出: 25
💡 题解(点击展开)
思路: 循环读取 5 次,累加求和。
#include <bits/stdc++.h>
using namespace std;
int main() {
long long sum = 0;
for (int i = 0; i < 5; i++) {
int x;
cin >> x;
sum += x;
}
cout << sum << "\n";
return 0;
}
关键点:
sum用long long以防整数较大- 因为题目说「恰好 5 个整数」,循环读取恰好 5 次
🏋️ 核心练习题
题目 2.2.6 — FizzBuzz 经典编程挑战:打印 1 到 100 的数字,但:
- 如果能被 3 整除,打印
Fizz - 如果能被 5 整除,打印
Buzz - 如果能同时被 3 和 5 整除,打印
FizzBuzz
输出开头几行:
📄 Code 完整代码
1
2
Fizz
4
Buzz
Fizz
7
8
Fizz
Buzz
11
Fizz
13
14
FizzBuzz
💡 题解(点击展开)
思路: 循环 1 到 100。对每个数检查整除性——先检查同时整除的情况(被 3 和 5 整除),否则会被单独的 Fizz 或 Buzz 分支先捕获。
#include <bits/stdc++.h>
using namespace std;
int main() {
for (int i = 1; i <= 100; i++) {
if (i % 3 == 0 && i % 5 == 0) {
cout << "FizzBuzz\n";
} else if (i % 3 == 0) {
cout << "Fizz\n";
} else if (i % 5 == 0) {
cout << "Buzz\n";
} else {
cout << i << "\n";
}
}
return 0;
}
关键点:
- 先检查
i % 3 == 0 && i % 5 == 0——如果先检查i % 3 == 0,15 就会打印「Fizz」而永远到不了 FizzBuzz 分支 - 同时被 3 和 5 整除的数就是被 15 整除:
i % 15 == 0也有效
题目 2.2.7 — N 中最小值 读取 N(1 ≤ N ≤ 1000),然后读取 N 个整数,打印最小值。
样例输入:
5
8 3 7 1 9
样例输出: 1
💡 题解(点击展开)
思路: 用读取的第一个值初始化最小值,每次看到更小的就更新。
#include <bits/stdc++.h>
using namespace std;
int main() {
ios_base::sync_with_stdio(false);
cin.tie(NULL);
int n;
cin >> n;
int first;
cin >> first;
int minVal = first; // 初始化为第一个元素
for (int i = 1; i < n; i++) { // 读取剩余 n-1 个元素
int x;
cin >> x;
if (x < minVal) {
minVal = x;
}
}
cout << minVal << "\n";
return 0;
}
关键点:
- 将
minVal初始化为读取的第一个元素(不是 0 或 INT_MAX),然后在循环中处理剩余元素 - 替代方案:用
INT_MAX作为初始值——保证大于任何 int,第一个元素一定会更新它
题目 2.2.8 — 统计正数 读取 N(1 ≤ N ≤ 1000),然后读取 N 个整数,打印其中严格正数(> 0)的个数。
样例输入:
6
3 -1 0 5 -2 7
样例输出: 3
💡 题解(点击展开)
思路: 维护一个计数器,满足条件(x > 0)时递增。
#include <bits/stdc++.h>
using namespace std;
int main() {
ios_base::sync_with_stdio(false);
cin.tie(NULL);
int n;
cin >> n;
int count = 0;
for (int i = 0; i < n; i++) {
int x;
cin >> x;
if (x > 0) {
count++;
}
}
cout << count << "\n";
return 0;
}
关键点:
count从 0 开始,只在x > 0时递增- 0 不是正数(也不是负数——它是零),所以
x > 0正确地排除了它
题目 2.2.9 — 星号三角形 读取 N,打印一个 N 行的直角三角形,第 i 行有 i 个星号。
样例输入: 4
样例输出:
*
**
***
****
💡 题解(点击展开)
思路: 嵌套循环——外层遍历行,内层打印对应数量的星号。
#include <bits/stdc++.h>
using namespace std;
int main() {
int n;
cin >> n;
for (int row = 1; row <= n; row++) {
for (int star = 1; star <= row; star++) {
cout << "*";
}
cout << "\n";
}
return 0;
}
关键点:
- 第 1 行 1 个星号,第 2 行 2 个,……,第 N 行 N 个
- 内层循环对每个
row值恰好执行row次 - 使用
string的替代方案:cout << string(row, '*') << "\n";——创建row个*的字符串
题目 2.2.10 — 各位数字之和 读取正整数 N(1 ≤ N ≤ 10^9),打印其各位数字之和。
样例输入: 12345 → 样例输出: 15
样例输入: 9999 → 样例输出: 36
💡 题解(点击展开)
思路: 用取模技巧。N % 10 得到最后一位。N / 10 删除最后一位。重复直到 N 变为 0。
#include <bits/stdc++.h>
using namespace std;
int main() {
int n;
cin >> n;
int digitSum = 0;
while (n > 0) {
digitSum += n % 10; // 加上最后一位
n /= 10; // 删除最后一位
}
cout << digitSum << "\n";
return 0;
}
关键点:
n % 10提取个位数(如 12345 % 10 = 5)n /= 10是整数除法,删除最后一位(如 12345 / 10 = 1234)- 循环持续到 n = 0(所有数字都已提取)
- 追踪:12345 → +5 → 1234 → +4 → 123 → +3 → 12 → +2 → 1 → +1 → 0。总和 = 15 ✓
🏆 挑战题
挑战 2.2.11 — 考拉兹序列 从 N 开始的考拉兹序列规则如下:
- N 是偶数:下一项 = N / 2
- N 是奇数:下一项 = N × 3 + 1
- N = 1 时停止
读取 N,打印整个序列(包括 N 和 1),以及到达 1 需要的步数。
样例输入: 6
样例输出:
6 3 10 5 16 8 4 2 1
Steps: 8
💡 题解(点击展开)
思路: 用 while 循环,不断应用规则直到到达 1,同时计数步数。
#include <bits/stdc++.h>
using namespace std;
int main() {
long long n;
cin >> n;
int steps = 0;
cout << n; // 打印起始数字
while (n != 1) {
if (n % 2 == 0) {
n = n / 2;
} else {
n = n * 3 + 1;
}
cout << " " << n; // 打印下一个数字
steps++;
}
cout << "\n";
cout << "Steps: " << steps << "\n";
return 0;
}
关键点:
- 用
long long——即使从小数开始,序列中间值也可能很大(如 N=27 会到达 9232!) - 考拉兹猜想称这个序列总会到达 1,但对所有 N 尚未被证明
- 循环前打印 N(作为起始值),之后每步打印新值
挑战 2.2.12 — 质数判断
读取 N(2 ≤ N ≤ 10^6),若 N 是质数打印 prime,否则打印 composite。
质数是只有 1 和它本身两个因数的数。
样例输入: 17 → 输出: prime
样例输入: 100 → 输出: composite
💡 题解(点击展开)
思路: 试除法——检查 2 到 √N 中有没有能整除 N 的数。如果没有,N 是质数。只需检查到 √N,因为若 N = a×b 且 a > √N,则 b < √N(早就找到了)。
#include <bits/stdc++.h>
using namespace std;
int main() {
int n;
cin >> n;
bool isPrime = true;
if (n < 2) {
isPrime = false;
} else {
// 检查 2 到 sqrt(n) 的因数
for (int i = 2; (long long)i * i <= n; i++) {
if (n % i == 0) {
isPrime = false;
break; // 找到因数,无需继续
}
}
}
cout << (isPrime ? "prime" : "composite") << "\n";
return 0;
}
关键点:
- 用
i * i <= n而不是i <= sqrt(n),避免浮点精度问题(也稍快) (long long)i * i防止 i 较大时溢出(如 i = 1000000,i*i = 10^12)break找到任意因数后立即退出——无需继续检查- 时间复杂度:O(√N),处理 N 最大 10^6 非常轻松(√10^6 = 1000 次迭代)
挑战 2.2.13 — 评分最高的奶牛 读取 N(1 ≤ N ≤ 1000),然后读取 N 对(奶牛名字,评分)。找出评分最高的奶牛并打印其名字。
样例输入:
4
Bessie 95
Elsie 82
Moo 95
Daisy 88
样例输出: Bessie
(若有并列,打印最先出现的那头。)
💡 题解(点击展开)
思路: 跟踪目前见过的最佳评分和名字,只在严格更高时更新。
#include <bits/stdc++.h>
using namespace std;
int main() {
ios_base::sync_with_stdio(false);
cin.tie(NULL);
int n;
cin >> n;
string bestName;
int bestRating = -1; // 初始化为 -1,任何真实评分都高于它
for (int i = 0; i < n; i++) {
string name;
int rating;
cin >> name >> rating;
if (rating > bestRating) {
bestRating = rating;
bestName = name;
}
}
cout << bestName << "\n";
return 0;
}
关键点:
- 初始化
bestRating = -1(或INT_MIN),让第一头奶牛始终成为新的最佳 - 用
>(严格大于)而非>=,这样并列时保留最先出现的(题目要求) cin >> name >> rating从同一行读取字符串和整数——完全有效