📖 第 2.2 章 ⏱️ 约 60 分钟 🎯 入门

第 2.2 章:控制流

📝 前置条件: 第 2.1 章(变量、cin/cout、基本算术)


2.2.0 什么是「控制流」?

到目前为止,我们写的每个程序都是从上到下执行的——第 1 行、第 2 行、第 3 行,结束。就像从头到尾读一本书。

但真正的程序需要做决策重复操作。这就是「控制流」的含义——控制执行的(顺序)。

想象一本「选择你的冒险」书:

  • 有时书上说「如果你想和龙战斗,翻到第 47 页;否则翻到第 52 页」
  • 有时书上说「重复这一段,直到你逃出地牢」

C++ 通过以下方式提供了恰好对应的功能:

  • if/else —— 根据条件做决策
  • for/while 循环 —— 重复执行一段代码

这是控制流的总览:

Control Flow Overview

在循环图示中:程序不断回到「步骤 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:

  1. 85 >= 90? → 跳过
  2. 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 Loop Flowchart

流程图展示了执行过程:初始化只运行一次,之后每次迭代前检查条件,条件为假时退出循环。

常见 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 << " ";

Loop Trace Example

先在纸上追踪循环,再运行——这能建立直觉并帮你发现 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 嵌套循环

可以在循环里再放一个循环。内层循环对外层循环的每一次迭代都会完整执行一遍

Nested Loop Clock Analogy

// 打印 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 层(嵌套)~10^4比较所有对
3 层(嵌套)~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 breakcontinue

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)——只有 maxValminVal 两个变量

🤔 为什么初始化为第一个元素? 不要初始化为 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++条件永远为真,程序卡死确保循环变量被更新
3switch 中忘记 breakcase 2: cout << "two"; 没有 break执行「穿透」到下一个 case每个 case 末尾加 break;
4差一错误for (int i = 0; i <= n; i++) 本应是 < n多循环一次,可能越界或多计仔细核对 <<=
5最大值初始化为 0int maxVal = 0; 但所有数都是负数0 比所有输入都大,结果错误初始化为第一个元素或 INT_MIN
6嵌套循环用了相同的变量名外层 for (int i...) 和内层 for (int i...)内层 i 遮蔽外层 i,导致外层循环行为异常内外层循环用不同变量名(如 ij

本章总结

📌 核心要点

概念语法使用场景为什么重要
ifif (条件) { ... }条件为真时执行程序决策的基础;几乎每道题都用
if/elseif (...) {...} else {...}二选一处理是/否类型的决策
if/else if/else链式多选一评级系统、分类场景
whilewhile (条件) {...}次数未知时重复读取到输入结束、模拟过程
forfor (int i=0; i<n; i++) {...}次数已知时重复竞赛编程中最常用的循环
嵌套循环循环套循环需要遍历所有对注意 O(N²) 复杂度限制
breakbreak;找到目标后立即退出提前终止节省时间
continuecontinue;跳过当前迭代过滤掉不需要处理的元素
switchswitch(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:forwhile 可以互换吗?什么时候该用哪个?

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:switchif-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;
}

关键点:

  • sumlong 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 从同一行读取字符串和整数——完全有效