NOI C++ 课程体系 / 语法入门 / 第9讲
CSP-J 入门级 · 语法入门 · 数组专题

C++ 数组 完全指南

用一个名字存一堆数据 —— 从储物柜类比到内存布局,从一维到二维,一站式掌握数组。

6 大教学模块 3 大知识点 5 个精讲案例 12 道分层作业
本讲 · 第 13/56 讲数组
模块 01

情景导航

先用故事和生活中的场景建立直觉,再给出严谨的编程定义,最后用表格和口诀把核心要点固化下来。

故事故事引入 —— 小强的成绩点名系统

小强是班上的学习委员,老师给了他一个任务:统计全班 50 位同学的 C++ 成绩。

小强想了想,决定用变量来记录成绩。于是他写了这样的代码:

C++小强的想法
int score1 = 95;
int score2 = 87;
int score3 = 78;
...
int score50 = 92;// 写到手酸

小强写到第 10 个成绩的时候就崩溃了 —— 50 个变量?!每个都要单独命名?

而且,如果要计算平均分,他得写 50 次 sum = sum + scoreX,这太痛苦了!

有没有一种方法,用一个名字就能存下一堆数据?当然有 —— 这就是数组(Array)的由来!

C++ 中的数组 —— 用一个名字存一组数据

类比生活中的"数组" —— 储物柜、药盒、成绩单、棋盘
🗄️

储物柜

一排编号的储物柜,每个柜子存一件物品。柜号就是"下标",从 1 开始编号。

→ 数组下标从 0 开始
💊

药盒

一周 7 天的药盒,每个格子放一天的药。格子是连续的,每个格子大小一样。

→ 数组元素类型相同、连续存储
📊

成绩单

一张表格,行是学生,列是科目。每个位置由"行号+列号"唯一确定。

→ 二维数组 = 表格
♟️

棋盘

围棋棋盘 19×19,每个交叉点可以放一枚棋子。用 (行,列) 定位。

→ 多维数组 = 多维坐标
定义编程定义 —— 什么是数组?

数组(Array)是 C++ 中的一种复合数据类型,它用一个名字存储相同类型的固定数量的数据。

核心特征:

  • 同类型:数组中所有元素必须是相同的数据类型(如全部是 int)
  • 定长:数组大小在定义时确定,运行中不能改变
  • 连续:元素在内存中连续存放,地址相邻
  • 下标从 0 开始:第 1 个元素的下标是 0,第 n 个元素的下标是 n-1
数组记忆口诀

数组名字起一个,里面装着一堆数。
类型相同大小定,下标从零开始数。
内存连续紧相连,越界访问会出错!

模块 02

知识探究

深入数组的语法细节,通过 5 个由浅入深的案例,掌握一维数组和二维数组的各种用法。

1一维数组 —— 语法详解与解剖

一维数组的声明格式如下:

数据类型 数组名[数组大小];
① 数据类型(如 int, double, char) ② 数组名(变量名) ③ 大小(必须是常量表达式) ④ 分号结尾

示例:

C++数组声明示例
int score[5];          // 声明一个包含5个整数的数组
double price[10];       // 声明一个包含10个双精度浮点数的数组
char name[20];         // 声明一个包含20个字符的数组
int a[100];            // 大小必须是常量,不能是变量!
初始化方式(4种)

方式一:全部初始化为 0
    int a[5] = {0};
方式二:部分初始化(未指定的自动补0)
    int a[5] = {1, 2}; → a[0]=1, a[1]=2, a[2]=0, a[3]=0, a[4]=0
方式三:全部指定
    int a[5] = {1, 2, 3, 4, 5};
方式四:不指定大小(由初始化列表自动推断)
    int a[] = {1, 2, 3, 4, 5}; → 大小自动为5

2内存布局可视化 —— 数组在内存中长什么样?

数组在内存中是连续存放的。假设声明了 int a[5] = {10, 20, 30, 40, 50},内存布局如下:

🧠 内存布局动画 —— 点击单元格高亮访问

10
20
30
40
50
下标 0
下标 1
下标 2
下标 3
下标 4

点击上面的数字,可以看到对应下标的访问效果

⚠️ 越界警告:如果访问 a[5] 或 a[-1],会发生下标越界,访问到不属于数组的内存区域,结果不可预测!

操作说明是否合法
a[0]访问第 1 个元素✅ 合法
a[4]访问第 5 个元素(最后一个)✅ 合法
a[5]访问第 6 个元素(超出范围)❌ 越界!
a[-1]访问负下标❌ 越界!
3数组的读入与输出

逐个读写:

C++逐个读写
int a[5];
a[0] = 10;  // 给第1个元素赋值
a[1] = 20;  // 给第2个元素赋值
cin >> a[2];  // 从键盘读入第3个元素
cout << a[3];  // 输出第4个元素

用 for 循环批量读写(最常用):

C++for 循环遍历数组
int a[5];
// 读入
for(int i = 0; i < 5; i++) {
    cin >> a[i];
}
// 输出
for(int i = 0; i < 5; i++) {
    cout << a[i] << " ";
}
cout << endl;

5 大常用操作模板:

操作代码模板说明
初始化for(int i=0;i<n;i++) a[i]=0;将所有元素设为0
求和for(int i=0;i<n;i++) sum+=a[i];累加所有元素
求最值max=a[0]; for(i=1;i<n;i++) if(a[i]>max) max=a[i];打擂台法求最大值
查找for(i=0;i<n;i++) if(a[i]==target) { found=true; break; }线性查找目标值
逆序for(int i=0;i<n/2;i++) swap(a[i],a[n-1-i]);原地逆序
4案例解析 —— 5 个由浅入深的数组案例

案例 1:数组遍历求和

题目:读入 n 个整数,计算它们的总和并输出。

C++数组求和
#include <iostream>
using namespace std;
int main(){
    int n;
    cin >> n;
    int a[1000];  // 最多存1000个数
    // 读入数组
    for(int i = 0; i < n; i++){
        cin >> a[i];
    }
    // 求和
    int sum = 0;
    for(int i = 0; i < n; i++){
        sum += a[i];
    }
    cout << sum << endl;
    return 0;
}
运行结果
输入:5 10 20 30 40 50
输出:150

逐行解析:

1
int a[1000] —— 声明一个能存1000个整数的数组
2
第一个 for 循环:读入 n 个整数,存入 a[0] ~ a[n-1]
3
int sum = 0 —— 累加器必须先初始化为0
4
第二个 for 循环:逐个累加 a[i] 到 sum
5
cout << sum —— 输出最终结果

案例 2:打擂台求最值

题目:读入 n 个整数,找出其中的最大值和最小值。

C++打擂台法求最值
#include <iostream>
using namespace std;
int main(){
    int n;
    cin >> n;
    int a[1000];
    for(int i = 0; i < n; i++) cin >> a[i];
    // 打擂台:先假设第1个元素是最大值
    int maxVal = a[0], minVal = a[0];
    for(int i = 1; i < n; i++){
        if(a[i] > maxVal) maxVal = a[i];  // 挑战成功,更新最大值
        if(a[i] < minVal) minVal = a[i];  // 挑战成功,更新最小值
    }
    cout << "max=" << maxVal << " min=" << minVal << endl;
    return 0;
}
运行结果
输入:5 12 5 67 23 8
输出:max=67 min=5

解析:打擂台思想 —— 先让第1个元素"坐擂台"(假设它是最大值),然后从第2个开始逐个挑战,挑战成功就替换。

案例 3:线性查找

题目:读入 n 个整数和一个目标值 target,在数组中查找 target 是否存在,如果存在输出它的位置(下标)。

C++线性查找
#include <iostream>
using namespace std;
int main(){
    int n;
    cin >> n;
    int a[1000];
    for(int i = 0; i < n; i++) cin >> a[i];
    int target;
    cin >> target;
    int pos = -1;  // -1表示没找到
    for(int i = 0; i < n; i++){
        if(a[i] == target){
            pos = i;
            break;  // 找到了就立刻退出
        }
    }
    if(pos != -1)
        cout << "Found at index " << pos << endl;
    else
        cout << "Not found" << endl;
    return 0;
}
运行结果
输入:5 10 20 30 40 50  30
输出:Found at index 2

案例 4:冒泡排序(一维数组经典应用)

题目:读入 n 个整数,将它们从小到大排序后输出。

C++冒泡排序
#include <iostream>
using namespace std;
int main(){
    int n;
    cin >> n;
    int a[1000];
    for(int i = 0; i < n; i++) cin >> a[i];
    // 冒泡排序:外层走 n-1 轮
    for(int i = 0; i < n-1; i++){
        // 内层:相邻比较交换,每轮把最大的"冒泡"到末尾
        for(int j = 0; j < n-1-i; j++){
            if(a[j] > a[j+1]){
                int tmp = a[j];
                a[j] = a[j+1];
                a[j+1] = tmp;
            }
        }
    }
    // 输出排序结果
    for(int i = 0; i < n; i++)
        cout << a[i] << " ";
    cout << endl;
    return 0;
}
运行结果
输入:5 5 3 1 4 2
输出:1 2 3 4 5

解析:冒泡排序的核心是"相邻比较、交换位置"。每轮外层循环走完后,当前未排序部分的最大值会被"冒泡"到末尾。外层走 n-1 轮即可。

案例 5:二维数组与矩阵转置

题目:声明一个 3×3 的二维数组,读入数据后输出它的转置矩阵。

C++二维数组声明与矩阵转置
#include <iostream>
using namespace std;
int main(){
    int a[3][3];
    // 读入二维数组(行优先)
    for(int i = 0; i < 3; i++){
        for(int j = 0; j < 3; j++){
            cin >> a[i][j];
        }
    }
    // 转置:a[i][j] → b[j][i]
    int b[3][3];
    for(int i = 0; i < 3; i++){
        for(int j = 0; j < 3; j++){
            b[j][i] = a[i][j];
        }
    }
    // 输出转置矩阵
    for(int i = 0; i < 3; i++){
        for(int j = 0; j < 3; j++){
            cout << b[i][j] << " ";
        }
        cout << endl;
    }
    return 0;
}
运行结果
输入:
1 2 3
4 5 6
7 8 9
输出:
1 4 7
2 5 8
3 6 9

二维数组语法:

数据类型 数组名[行数][列数];
行数(必须指定) 列数(必须指定)

二维数组遍历模板:

C++嵌套 for 循环遍历
for(int i = 0; i < R; i++){    // 外层:控制行
    for(int j = 0; j < C; j++){  // 内层:控制列
        // 处理 a[i][j]
    }
}

行优先存储:C++ 中二维数组按行优先存储,即先存第 0 行的所有列,再存第 1 行的所有列,依此类推。内存中 a[3][3] 的布局为:a[0][0], a[0][1], a[0][2], a[1][0], a[1][1], a[1][2], a[2][0], a[2][1], a[2][2]。

模块 03

实践应用

通过完整的编程题目,练习数组的实际应用。每道题包含题目描述、输入输出样例、多种解法分析和完整代码。

题1成绩统计 —— 平均分、最高分、最低分、及格率

题目描述:读入 n 个学生的 C++ 成绩(0~100 的整数),统计平均分、最高分、最低分,并计算及格率(及格人数/总人数 × 100%)。

输入样例:5 85 92 78 60 45

输出样例:平均分:72.00 最高分:92 最低分:45 及格率:80.00%

方法一:用数组存储所有成绩

C++方法一:数组存储
#include <iostream>
#include <iomanip>
using namespace std;
int main(){
    int n;
    cin >> n;
    int a[1000];
    for(int i = 0; i < n; i++) cin >> a[i];
    // 初始化
    double sum = 0;
    int maxV = a[0], minV = a[0];
    int pass = 0;
    // 遍历统计
    for(int i = 0; i < n; i++){
        sum += a[i];
        if(a[i] > maxV) maxV = a[i];
        if(a[i] < minV) minV = a[i];
        if(a[i] >= 60) pass++;
    }
    cout << fixed << setprecision(2);
    cout << "平均分:" << sum/n
         << " 最高分:" << maxV
         << " 最低分:" << minV
         << " 及格率:" << (double)pass/n*100 << "%" << endl;
    return 0;
}

方法二:不用数组(空间优化)

C++方法二:边读边统计
#include <iostream>
#include <iomanip>
using namespace std;
int main(){
    int n;
    cin >> n;
    double sum = 0;
    int maxV = -1, minV = 101, pass = 0;
    for(int i = 0; i < n; i++){
        int s;
        cin >> s;
        sum += s;
        if(s > maxV) maxV = s;
        if(s < minV) minV = s;
        if(s >= 60) pass++;
    }
    cout << fixed << setprecision(2);
    cout << "平均分:" << sum/n
         << " 最高分:" << maxV
         << " 最低分:" << minV
         << " 及格率:" << (double)pass/n*100 << "%" << endl;
    return 0;
}

说明:方法一用数组存储所有成绩,可以后续再次遍历(如求中位数)。方法二边读边统计,空间复杂度 O(1)。实际编程中根据需求选择。

思考:如果要求输出每个同学与平均分的差值,应该用哪种方法?为什么?
题2杨辉三角 —— 二维数组经典应用

题目描述:输出 n 行杨辉三角。杨辉三角的特点:每行首尾为 1,中间每个数等于它上方两数之和。

输入样例:5

输出样例:1 1 1 1 2 1 1 3 3 1 1 4 6 4 1

解题思路

C++杨辉三角
#include <iostream>
using namespace std;
int main(){
    int n;
    cin >> n;
    int a[100][100] = {0};  // 初始化为0
    // 首尾为1
    for(int i = 0; i < n; i++){
        a[i][0] = 1;       // 每行第1个
        a[i][i] = 1;       // 每行最后一个
    }
    // 中间元素 = 上方两数之和
    for(int i = 2; i < n; i++){
        for(int j = 1; j < i; j++){
            a[i][j] = a[i-1][j-1] + a[i-1][j];
        }
    }
    // 输出
    for(int i = 0; i < n; i++){
        for(int j = 0; j <= i; j++){
            cout << a[i][j] << " ";
        }
        cout << endl;
    }
    return 0;
}

说明:杨辉三角是二维数组的经典应用。核心递推公式:a[i][j] = a[i-1][j-1] + a[i-1][j]。注意数组要初始化为0,否则未赋值的元素会是随机值。

思考:能否只用一维数组实现杨辉三角?提示:从后往前更新可以避免覆盖。
题3字符统计 —— char 数组与字符串

题目描述:读入一行字符串(不含空格),统计其中每个字母出现的次数(不区分大小写)。

输入样例:hello

输出样例:a:0 b:0 c:0 ... e:1 h:1 l:2 o:1 ...

C++字符统计
#include <iostream>
#include <string>
using namespace std;
int main(){
    string s;
    cin >> s;  // 读入字符串
    int cnt[26] = {0};  // 26个字母的计数器
    for(int i = 0; i < s.length(); i++){
        char c = tolower(s[i]);  // 转小写
        if(c >= 'a' && c <= 'z'){
            cnt[c - 'a']++;  // 利用ASCII码映射到0~25
        }
    }
    for(int i = 0; i < 26; i++){
        cout << (char)('a'+i) << ":" << cnt[i] << " ";
    }
    cout << endl;
    return 0;
}

说明:利用 ASCII 码的特性,c - 'a' 可以将小写字母映射到 0~25 的下标范围。这是字符统计的经典技巧。

拓展:如果用 char 数组(而非 string)来读入字符串,需要注意用 cin.getline(s, size) 而非 cin >> s(后者遇空格停止)。另外 char 数组末尾需要预留位置给 '\0' 结束符。
模块 04

总结提升

回顾数组的核心要点,掌握万能模板,并通过拓展案例深化理解。

1核心要点总结
数组万能模板

一维数组:数据类型 数组名[大小];
    int a[1000]; // 声明1000个整数的数组

二维数组:数据类型 数组名[行数][列数];
    int a[100][100]; // 声明100×100的二维数组

记住三要素:类型 + 名字 + 大小,下标从零开始数!

知识点必须记住的内容
一维数组声明int a[1000]; 大小必须是常量表达式
下标规则下标从0开始,a[0]是第1个元素,a[n-1]是第n个
初始化int a[5]={0}; 全部为0 / int a[]={1,2,3}; 自动推断大小
遍历模板for(int i=0; i<n; i++) { 处理 a[i] }
二维数组int a[3][4]; 访问 a[i][j],嵌套for遍历
行优先存储二维数组按行存储,先存第0行所有列,再存第1行...
char数组char s[100]; cin>>s 遇空格停 / cin.getline(s,100) 读整行
越界警告访问 a[n] 或 a[-1] 会访问非法内存,结果不可预测!
2拓展案例 —— 一题多解

拓展 1:数组逆序(三种方法)

方法一:倒序输出(不修改原数组)
C++
for(int i = n-1; i >= 0; i--){
    cout << a[i] << " ";
}
cout << endl;

只改变输出顺序,原数组不变

方法二:原地交换(修改原数组)
C++
for(int i = 0; i < n/2; i++){
    int tmp = a[i];
    a[i] = a[n-1-i];
    a[n-1-i] = tmp;
}
for(int i = 0; i < n; i++)
    cout << a[i] << " ";

交换 a[i] 和 a[n-1-i],只需走 n/2 轮

方法三:用新数组
C++
int b[1000];
for(int i = 0; i < n; i++){
    b[n-1-i] = a[i];
}
for(int i = 0; i < n; i++)
    cout << b[i] << " ";

空间换时间,额外开辟一个数组 b 来存放逆序结果

拓展 2:字符统计(string vs char数组)

方法一:用 string
C++
string s;
cin >> s;
for(int i = 0; i < s.length(); i++){
    // 用 s[i] 访问每个字符
}
方法二:用 char 数组
C++
char s[1000];
cin >> s;  // 遇空格停止
for(int i = 0; s[i] != '\0'; i++){
    // 用 s[i] 访问每个字符
}

拓展 3:二维数组对角线求和

主对角线
C++
int sum = 0;
for(int i = 0; i < n; i++){
    sum += a[i][i];  // 行号=列号
}
副对角线
C++
int sum = 0;
for(int i = 0; i < n; i++){
    sum += a[i][n-1-i];  // 列号 = n-1-行号
}
3思考与启发

对比上面的解法,你有什么启发?

1. 数组逆序:三种方法各有优劣 —— 倒序输出最简单但不修改原数组,原地交换最省空间,新数组最直观但多占空间。

2. 数组适合解决什么问题:需要批量存储同类型数据、需要按位置访问、需要多次遍历的场景。

3. 能否设计新算法:比如求数组中出现次数最多的元素,可以用"桶计数"的思想,把元素值当作下标来统计频率,时间复杂度 O(n)。

4. 二维数组的核心:嵌套 for 循环,外层控制行,内层控制列。转置就是行列互换:b[j][i] = a[i][j]。

模块 05

学习检测

通过 5 个典型错误案例,学会识别和修复数组中的常见 bug。

错误 1

下标越界 —— 访问 a[n](数组大小为 n)

错误代码
int a[5];
for(int i = 0; i <= 5; i++){
    cin >> a[i];  // i=5 时越界!
}
报错原因
数组 a[5] 的下标范围是 0~4,当 i=5 时访问 a[5] 超出了数组边界,会写入非法内存,导致不可预测的行为(程序崩溃、数据错乱等)。
正确写法
int a[5];
for(int i = 0; i < 5; i++){
    cin >> a[i];  // i 最大为4,安全
}
预防口诀:数组大小是 n,循环条件用 i<n,等号一加就越界。
错误 2

数组未初始化就使用

错误代码
int a[100];
int sum = 0;
for(int i = 0; i < 100; i++){
    sum += a[i];  // a[i] 是随机值!
}
报错原因
局部数组不会自动初始化,a[0]~a[99] 的值是内存中的随机垃圾值。直接累加会导致结果不可预测。
正确写法
int a[100] = {0};  // 全部初始化为0
int sum = 0;
for(int i = 0; i < 100; i++){
    sum += a[i];  // 安全,a[i] 都是0
}
预防口诀:数组定义要初始化,{0} 补全免后患。
错误 3

用变量作为数组大小

错误代码
int n;
cin >> n;
int a[n];  // 错误!n 是变量
报错原因
C++ 标准规定数组大小必须是常量表达式(编译期可确定的值)。用变量 n 作为大小是 C99 的 VLA(变长数组)特性,C++ 标准不支持(虽然某些编译器如 GCC 作为扩展支持,但不推荐)。
正确写法
const int MAXN = 1000;
int a[MAXN];  // 用 const 常量
// 或者用 vector
int n; cin >> n;
vector<int> a(n);  // 动态数组
预防口诀:数组大小要常量,const 定义或 vector。
错误 4

二维数组行列搞反

错误代码
int a[3][4];  // 3行4列
for(int i = 0; i < 4; i++){    // 错误:i 应该到3
    for(int j = 0; j < 3; j++){  // 错误:j 应该到4
        cin >> a[i][j];
    }
}
报错原因
a[3][4] 表示 3 行 4 列,外层循环应该从 0 到 2(行),内层从 0 到 3(列)。写反了会导致越界访问 a[3][j](第4行不存在)。
正确写法
int a[3][4];
for(int i = 0; i < 3; i++){   // 行:0~2
    for(int j = 0; j < 4; j++){ // 列:0~3
        cin >> a[i][j];
    }
}
预防口诀:先行数后数列,外层行内层列,别搞反了!
错误 5

char 数组忘留 \0 空间

错误代码
char s[5];
cin >> s;  // 输入 "hello"(5个字符)
// 但 char 数组需要 \0 结束符!
// "hello" 实际占 6 字节,越界了!
报错原因
char 数组存储字符串时,末尾自动添加 '\0' 作为结束符。"hello" 有 5 个字符 + 1 个 '\0' = 6 字节,但数组只声明了 5 个字节,导致越界。
正确写法
char s[6];  // 多留1个字节给 \0
cin >> s;  // 安全
// 或者用 string 更安全
string s;
cin >> s;
预防口诀:char 数组存字符串,大小要加一留空位。
速查表数组速查表
操作代码
声明一维数组int a[1000];
全部初始化为0int a[1000] = {0};
部分初始化int a[5] = {1, 2};
自动推断大小int a[] = {1, 2, 3, 4, 5};
遍历(for)for(int i=0; i<n; i++) cout << a[i];
for-each(C++11)for(int x : a) cout << x;
声明二维数组int a[3][4] = {0};
访问元素a[i][j] // 第i行第j列
嵌套遍历for i: 0~R-1, for j: 0~C-1
char数组读入char s[100]; cin.getline(s,100);
string读入string s; cin >> s;
动态数组vector<int> a(n);
笔记知识点速记卡
📌 数组三要素

类型相同、大小固定、下标从0开始。声明时必须指定大小(常量表达式)。

📌 内存连续

数组元素在内存中连续存放,a[0] 地址最小,a[n-1] 地址最大。越界访问 = 访问非法内存。

📌 遍历万能模板

for(int i=0; i<n; i++) { 处理 a[i] }。求和/最值/查找/排序都基于这个模板。

📌 二维数组

int a[R][C]; 嵌套 for 遍历。行优先存储:先存第0行,再存第1行... 转置:b[j][i] = a[i][j]。

📌 char 数组 vs string

char 数组需手动管理大小和 \0;string 自动管理,更安全。竞赛中推荐用 string。

📌 初始化习惯

定义数组时养成 {0} 初始化的习惯,避免使用未初始化的垃圾值。

课后练习

练习作业

巩固数组知识,分层练习,点击展开答案与解析。

总结练习3 道综合题
A 基础 洛谷 P1046 分数段统计(数组版)

题目:统计每个分数段的人数

题目描述:读入 n 个学生的成绩(0~100),统计各分数段人数:[0,59]为不及格,[60,69]为及格,[70,79]为中等,[80,89]为良好,[90,100]为优秀。

输入样例:8 95 82 67 58 73 88 55 91

输出样例:不及格:2 及格:1 中等:1 良好:2 优秀:2

提示:用一个大小为5的数组统计各分数段人数,利用整数除法 score/10 映射到对应的段。
点击查看参考答案与解析
C++参考代码
#include <iostream>
using namespace std;
int main(){
    int n; cin >> n;
    int cnt[5] = {0};  // 5个分数段
    for(int i = 0; i < n; i++){
        int s; cin >> s;
        if(s < 0) s = 0;
        if(s > 100) s = 100;
        int idx;
        if(s < 60) idx = 0;
        else if(s < 70) idx = 1;
        else if(s < 80) idx = 2;
        else if(s < 90) idx = 3;
        else idx = 4;  // 90~100 映射到第4段
        cnt[idx]++;
    }
    cout << "不及格:" << cnt[0]
         << " 及格:" << cnt[1]
         << " 中等:" << cnt[2]
         << " 良好:" << cnt[3]
         << " 优秀:" << cnt[4] << endl;
    return 0;
}

解析:核心技巧是利用 score/10 将分数映射到 0~10 的范围,再映射到 0~4 的数组下标。0~59→段0(不及格),60~69→段1,70~79→段2,80~89→段3,90~100→段4。

A 基础 洛谷 P1307 数字反转(数组版)

题目:数组逆序输出

题目描述:读入 n 个整数,将它们逆序输出。

输入样例:5 1 2 3 4 5

输出样例:5 4 3 2 1

提示:先读入数组,然后从最后一个元素开始倒序输出。
点击查看参考答案与解析
C++参考代码
#include <iostream>
using namespace std;
int main(){
    int n; cin >> n;
    int a[1000];
    for(int i = 0; i < n; i++) cin >> a[i];
    for(int i = n-1; i >= 0; i--){
        cout << a[i] << " ";
    }
    cout << endl;
    return 0;
}
B 提高 洛谷 P1068 分数线划定(数组排序)

题目:按成绩排序并输出前 k 名

题目描述:读入 n 个考生的编号和成绩,按成绩从高到低排序,输出前 k 名考生的编号和成绩(成绩相同编号小的排在前面)。

输入样例:6 3 1 90 2 85 3 90 4 70 5 60 6 85

输出样例:1 90 3 90 2 85

提示:用结构体或两个数组分别存编号和成绩,用冒泡排序或选择排序按成绩降序排列。
点击查看参考答案与解析
C++参考代码
#include <iostream>
using namespace std;
int main(){
    int n, k; cin >> n >> k;
    int id[5000], score[5000];
    for(int i = 0; i < n; i++){
        cin >> id[i] >> score[i];
    }
    // 冒泡排序:成绩降序,成绩相同编号升序
    for(int i = 0; i < n-1; i++){
        for(int j = 0; j < n-1-i; j++){
            if(score[j] < score[j+1]){
                swap(score[j], score[j+1]);
                swap(id[j], id[j+1]);
            } else if(score[j] == score[j+1] && id[j] > id[j+1]){
                swap(id[j], id[j+1]);
            }
        }
    }
    for(int i = 0; i < k && i < n; i++){
        cout << id[i] << " " << score[i] << endl;
    }
    return 0;
}
A组基础(4题)
A 基础 (自编题) 你的 m 是多少

题目:统计正负数个数

题目描述:读入 n 个整数,统计正数、负数和零的个数。

输入样例:6 -1 0 3 5 -2 0

输出样例:正数:2 负数:2 零:2

点击查看参考答案与解析
C++参考代码
#include <iostream>
using namespace std;
int main(){
    int n; cin >> n;
    int pos = 0, neg = 0, zero = 0;
    for(int i = 0; i < n; i++){
        int x; cin >> x;
        if(x > 0) pos++;
        else if(x < 0) neg++;
        else zero++;
    }
    cout << "正数:" << pos << " 负数:" << neg << " 零:" << zero << endl;
    return 0;
}

易错:用 else 收尾会把 0 也算成负数,必须单独判断 0,或写成 else if (x < 0)。

A 基础 基础题

题目:求数组中偶数的和

题目描述:读入 n 个整数,计算其中所有偶数的和。

输入样例:5 1 2 3 4 5

输出样例:6

点击查看参考答案与解析
C++参考代码
#include <iostream>
using namespace std;
int main(){
    int n; cin >> n;
    int a[1000];
    for(int i = 0; i < n; i++) cin >> a[i];
    int sum = 0;
    for(int i = 0; i < n; i++){
        if(a[i] % 2 == 0) sum += a[i];
    }
    cout << sum << endl;
    return 0;
}

易错:累加变量 sum 忘记初始化为 0;或把判断写成 a[i] % 2 = 0(赋值语句)导致编译报错。

A 基础 基础题

题目:查找目标值的位置

题目描述:读入 n 个整数和一个目标值,输出目标值在数组中的第一个出现位置(下标),如果不存在输出 -1。

输入样例:5 10 20 30 40 50 30

输出样例:2

点击查看参考答案与解析
C++参考代码
#include <iostream>
using namespace std;
int main(){
    int n; cin >> n;
    int a[1000];
    for(int i = 0; i < n; i++) cin >> a[i];
    int target; cin >> target;
    int pos = -1;
    for(int i = 0; i < n; i++){
        if(a[i] == target){ pos = i; break; }
    }
    cout << pos << endl;
    return 0;
}

易错:数组下标从 0 开始,找到时输出的是 i+1;找不到要输出 -1,忘记给 pos 初值会输出 0。

A 基础 基础题

题目:计算平均值并统计高于平均值的人数

题目描述:读入 n 个整数,计算平均值,然后统计高于平均值的人数。

输入样例:5 80 90 70 60 100

输出样例:2

点击查看参考答案与解析
C++参考代码
#include <iostream>
using namespace std;
int main(){
    int n; cin >> n;
    int a[1000];
    double sum = 0;
    for(int i = 0; i < n; i++){
        cin >> a[i];
        sum += a[i];
    }
    double avg = sum / n;
    int cnt = 0;
    for(int i = 0; i < n; i++){
        if(a[i] > avg) cnt++;
    }
    cout << cnt << endl;
    return 0;
}

易错:平均值用 sum / n 做整数除法,小数被截断,导致「高于平均值」的人数统计出错。

B组提高(4题)
B 提高 洛谷 P1059 明明的随机数(数组去重)

题目:数组去重并排序

题目描述:读入 n 个 1~1000 之间的整数,去掉重复的数后从小到大排序输出。

输入样例:10 20 40 32 67 40 20 89 300 400 15

输出样例:8 15 20 32 40 67 89 300 400

提示:方法一:用数组存储,排序后去重。方法二:用"桶"的思想,开一个大小为1001的数组,读入时标记出现。
点击查看参考答案与解析
C++方法二:桶排序去重(推荐)
#include <iostream>
using namespace std;
int main(){
    int n; cin >> n;
    int cnt[1001] = {0};  // 桶数组
    for(int i = 0; i < n; i++){
        int x; cin >> x;
        if(x >= 1 && x <= 1000) cnt[x] = 1;  // 标记出现
    }
    int ans[1001], m = 0;
    for(int i = 1; i <= 1000; i++){
        if(cnt[i] == 1) ans[m++] = i;
    }
    cout << m << endl;
    for(int i = 0; i < m; i++){
        cout << ans[i] << " ";
    }
    cout << endl;
    return 0;
}

解析:桶排序思想 —— 开一个大小为 1001 的数组,读入每个数时将其对应位置标记为 1。最后从 1 到 1000 扫描,标记为 1 的就是去重后的升序结果。时间复杂度 O(n+1000)。

易错:桶数组开得比数据范围小导致越界;或只标记了出现过的数,忘记从小到大依次输出。

B 提高 洛谷 P1047 校门外的树(区间覆盖)

题目:区间覆盖问题

题目描述:一条路长 L 米,从 0 到 L 每米种一棵树(共 L+1 棵)。现在要移走 M 个区间内的树,问最后还剩多少棵树。

输入样例:500 3 150 300 100 200 450 500

输出样例:249

提示:用数组标记每棵树是否被移走(0=保留,1=移走),遍历区间标记,最后统计 0 的个数。
点击查看参考答案与解析
C++参考代码
#include <iostream>
using namespace std;
int main(){
    int L, M; cin >> L >> M;
    int tree[10000] = {0};  // 0表示保留
    for(int i = 0; i < M; i++){
        int l, r; cin >> l >> r;
        for(int j = l; j <= r; j++){
            tree[j] = 1;  // 标记移走
        }
    }
    int cnt = 0;
    for(int i = 0; i <= L; i++){
        if(tree[i] == 0) cnt++;
    }
    cout << cnt << endl;
    return 0;
}

易错:树的总数是 L+1(编号 0 到 L 共 L+1 棵);区间端点边界写错会多移或少移一棵树。

B 提高 基础题

题目:矩阵乘法(2×2)

题目描述:计算两个 2×2 矩阵的乘积。

输入样例:1 2 3 4 5 6 7 8

输出样例:19 22 43 50

提示:矩阵乘法 C[i][j] = Σ A[i][k] * B[k][j],需要三层嵌套 for 循环。
点击查看参考答案与解析
C++参考代码
#include <iostream>
using namespace std;
int main(){
    int a[2][2], b[2][2], c[2][2] = {{0,0},{0,0}};
    for(int i = 0; i < 2; i++)
        for(int j = 0; j < 2; j++) cin >> a[i][j];
    for(int i = 0; i < 2; i++)
        for(int j = 0; j < 2; j++) cin >> b[i][j];
    // 矩阵乘法
    for(int i = 0; i < 2; i++){
        for(int j = 0; j < 2; j++){
            for(int k = 0; k < 2; k++){
                c[i][j] += a[i][k] * b[k][j];
            }
        }
    }
    for(int i = 0; i < 2; i++){
        cout << c[i][0] << " " << c[i][1] << endl;
    }
    return 0;
}

易错:累加前忘记把 c[i][j] 清零;三层循环里 k 的位置写错,行列相乘的关系就颠倒了。

B 提高 (自编题) 虫食算(数组模拟)

题目:数组元素循环右移

题目描述:读入 n 个整数,将它们循环右移 m 位后输出。例如 [1,2,3,4,5] 右移 2 位 → [4,5,1,2,3]。

输入样例:5 2 1 2 3 4 5

输出样例:4 5 1 2 3

提示:右移 m 位等价于从第 n-m 个元素开始输出,到末尾后接上从第 0 个到第 n-m-1 个元素。
点击查看参考答案与解析
C++参考代码
#include <iostream>
using namespace std;
int main(){
    int n, m; cin >> n >> m;
    int a[1000];
    for(int i = 0; i < n; i++) cin >> a[i];
    m = m % n;  // 防止 m >= n
    int start = n - m;
    for(int i = 0; i < n; i++){
        cout << a[(start + i) % n] << " ";
    }
    cout << endl;
    return 0;
}

易错:m 没有先对 n 取模,m 比 n 大时起始下标会算成负数;或输出时下标越过数组末尾。

C组挑战(4题)
C 挑战 洛谷 P1115 最大子段和(DP+数组)

题目:最大子数组和(Kadane 算法)

题目描述:给定 n 个整数(可能有负数),找到一个连续子数组,使得其元素之和最大,输出这个最大值。

输入样例:9 -2 1 -3 4 -1 2 1 -5 4

输出样例:6

提示:Kadane 算法:维护当前子段和 cur,如果 cur<0 就重置为0,否则累加。时间复杂度 O(n)。
点击查看参考答案与解析
C++参考代码(Kadane 算法)
#include <iostream>
#include <algorithm>
using namespace std;
int main(){
    int n; cin >> n;
    int a[100000];
    for(int i = 0; i < n; i++) cin >> a[i];
    int cur = a[0], maxSum = a[0];
    for(int i = 1; i < n; i++){
        cur = max(a[i], cur + a[i]);  // 要么从当前开始,要么延续前面
        maxSum = max(maxSum, cur);
    }
    cout << maxSum << endl;
    return 0;
}

解析:Kadane 算法是求最大子段和的经典 O(n) 算法。核心思想:对于每个位置,决定是"从当前位置重新开始"还是"延续前面的子段",取两者中较大的。

易错:cur 和 maxSum 的初值设成 0,数组全是负数时答案会错成 0,应该用 a[0] 初始化。

C 挑战 基础题

题目:螺旋矩阵

题目描述:输入一个正整数 n,生成一个 n×n 的螺旋矩阵(从 1 开始顺时针填入数字)。

输入样例:4

输出样例:1 2 3 4 12 13 14 5 11 16 15 6 10 9 8 7

提示:用二维数组存储,维护上下左右四个边界,按右→下→左→上的顺序填充,每填完一圈缩小边界。
点击查看参考答案与解析
C++参考代码
#include <iostream>
using namespace std;
int main(){
    int n; cin >> n;
    int a[100][100] = {0};
    int top = 0, bottom = n-1, left = 0, right = n-1;
    int num = 1;
    while(num <= n*n){
        for(int j = left; j <= right && num <= n*n; j++) a[top][j] = num++;
        top++;
        for(int i = top; i <= bottom && num <= n*n; i++) a[i][right] = num++;
        right--;
        for(int j = right; j >= left && num <= n*n; j--) a[bottom][j] = num++;
        bottom--;
        for(int i = bottom; i >= top && num <= n*n; i--) a[i][left] = num++;
        left++;
    }
    for(int i = 0; i < n; i++){
        for(int j = 0; j < n; j++){
            cout << a[i][j] << " ";
        }
        cout << endl;
    }
    return 0;
}

易错:每填完一条边忘记收缩对应边界,会重复填充或越过边界;n 为奇数时中心的格子容易漏填。

C 挑战 (自编题) 多项式输出(数组+分类讨论)

题目:约瑟夫环问题

题目描述:n 个人围成一圈,从第 1 个人开始报数,报到 m 的人出列,下一个人重新从 1 报数,直到剩下最后一个人。输出最后剩下的那个人的编号。

输入样例:5 2

输出样例:3

提示:用数组模拟圆圈,0表示在圈内,1表示已出列。或者用数学递推公式:f(n,m) = (f(n-1,m)+m) % n。
点击查看参考答案与解析
C++方法一:数组模拟
#include <iostream>
using namespace std;
int main(){
    int n, m; cin >> n >> m;
    int a[1000] = {0};  // 0表示在圈内
    int remain = n, pos = 0, cnt = 0;
    while(remain > 1){
        if(a[pos] == 0){  // 在圈内
            cnt++;
            if(cnt == m){
                a[pos] = 1;  // 出列
                cnt = 0;
                remain--;
            }
        }
        pos = (pos + 1) % n;  // 循环
    }
    for(int i = 0; i < n; i++){
        if(a[i] == 0){ cout << i+1 << endl; break; }
    }
    return 0;
}

易错:pos 每次加 1 后忘记 % n 回到圈首会导致越界;出列后忘记 remain-- 会陷入死循环。

C 挑战 基础题

题目:图像旋转(90度)

题目描述:给定一个 n×m 的矩阵,将其顺时针旋转 90 度后输出。

输入样例:2 3 1 2 3 4 5 6

输出样例:4 1 5 2 6 3

提示:原矩阵 a[n][m] 旋转后变成 b[m][n],旋转公式:b[j][n-1-i] = a[i][j]。
点击查看参考答案与解析
C++参考代码
#include <iostream>
using namespace std;
int main(){
    int n, m; cin >> n >> m;
    int a[100][100];
    for(int i = 0; i < n; i++)
        for(int j = 0; j < m; j++) cin >> a[i][j];
    int b[100][100];
    for(int i = 0; i < n; i++){
        for(int j = 0; j < m; j++){
            b[j][n-1-i] = a[i][j];  // 顺时针旋转90度
        }
    }
    for(int i = 0; i < m; i++){
        for(int j = 0; j < n; j++){
            cout << b[i][j] << " ";
        }
        cout << endl;
    }
    return 0;
}

易错:旋转前后行列数互换(n×m 变成 m×n),输出循环的边界写反会越界或漏打印。