Skip to content

第 36 课:二维数组模拟 ​

二维数组模拟是通过二维数组模拟现实或抽象的二维场景,如矩阵、棋盘、网格地图等,其核心是用行列索引 (i, j) 对应场景中的位置,并通过数组元素存储状态(如数值、访问标记、属性等)。

36.1 核心考查 ​

1) 索引与边界 ​

a. 明确数组的下标起点(0 或 1),避免越界(比如当 n = 3 时,行尾初始为 3,收缩到 2 → 1)。

b. 边界判断(如外围元素的 i == 1)、螺旋的边界收缩(最后一列或最后一行收缩后,下一轮循环不包含已填充列和行)。

2) 遍历策略 ​

a. 行优先/列优先遍历(稀疏矩阵、矩阵检测)。

b. 定向遍历(螺旋的四方向)、条件过滤(仅处理非零元素、边缘元素)。

3) 状态记录与压缩 ​

稀疏矩阵的三元组压缩、矩阵检测的奇偶性标记。

4) 模拟复杂场景 ​

螺旋填充的方向切换、外围元素的去重累加、奇偶性匹配的逻辑推理。

36.2 实例讲解 ​

例 1:收集露珠 ​

有一只小青蛙在一片荷叶矩阵上玩耍,每片荷叶上都凝结着露珠。

妈妈告诉它:"池塘最外围的荷叶(第一行、最后一行、第一列、最后一列)上的露珠最甜,但要注意角落的荷叶只能算一次哦!"小青蛙需要计算这些外围荷叶的露珠总和,证明自己是"露珠收集小能手"。

这个池塘是一个 m 行 n 列的网格,每片荷叶上的露珠数是整数。小青蛙经过努力,最终收集到了所有的最甜露珠,问小青蛙一共收集了多少露珠呢?

【输入格式】输入 m+1 行。

第 1 行为两个正整数,分别表示池塘的行数 m 和列数 n,中间以一个空格隔开。

接下来的 m 行为该矩阵 m 行 n 列的露珠数量,都为整数,每行相邻元素之间以一个空格隔开。

【输出格式】输出一行,一个整数,表示小青蛙收集的所有最甜露珠之和。

【输入样例】

3 4
1 2 2 1
5 6 7 8
9 3 0 5

【输出样例】

36

【数据范围】1 ≤ m ≤ 100,1 ≤ n ≤ 100,每片荷叶上的露珠数量不超过 100。

解析

本题其实是要求在一个 m 行 n 列矩阵中,位于第一行、最后一行、第一列和最后一列的所有元素之和,也就是矩阵外围元素的总和。

可以通过二维数组来存储矩阵元素,在输入每个元素时,判断其是否位于矩阵的外围(第一行、最后一行、第一列、最后一列),如果是则将其累加到总和中,最后输出总和即可,主要步骤如下。

  1. 判断当前元素是否位于矩阵的外围。如果 i 等于 1(第一行)或者 i 等于 m(最后一行),或者 j 等于 1(第一列)或者 j 等于 n(最后一列),则说明该元素位于矩阵的外围,即:
cpp
if (i == 1 || i == m || j == 1 || j == n)
  1. 如果当前元素位于矩阵的外围,则将其值累加到 sum 中,即:
cpp
sum += a[i][j];

参考代码

cpp
#include <iostream>
using namespace std;
int a[110][110];
int main()
{
    int m, n, sum = 0;
    cin >> m >> n;
    for (int i = 1; i <= m; i++)
    {
        for (int j = 1; j <= n; j++)
        {
            cin >> a[i][j]; // 输入元素后即可判断
            if (i == 1 || i == m || j == 1 || j == n)
                sum += a[i][j]; // 满足任一条件求和
        }
    }
    cout << sum;
    return 0;
}

例 2:稀疏矩阵 ​

大部分元素是 0 的矩阵称为稀疏矩阵。假设有 n 个非零元素,则可把稀疏矩阵用 n × 3 的矩阵简记下来,其中第一列是行号,第二列是列号,第三列是该行、该列下的非零元素的值。

如:

0  0  0  3
0  2  0  0
0  5  0  0

简记成:

1  4  3    表示第 1 行第 4 列有个数是 3。
2  2  2    表示第 2 行第 2 列有个数是 2。
3  2  5    表示第 3 行第 2 列有个数是 5。

试编程读入一稀疏矩阵,转换成简记形式,并输出。

【输入格式】输入 a + 1 行。

第 1 行输入两个正整数,表示原始矩阵的行数 a 和列数 b。

之后的 a 行为 a 行 b 列的矩阵元素。

【输出格式】输出为化为简记形式之后的矩阵(行数不确定,列数为 3)。

【输入样例】

3 4
0 0 0 3
0 2 0 0
0 5 0 0

【输出样例】

1 4 3
2 2 2
3 2 5

【数据范围】

原始矩阵的行数 a 和列数 b 满足:1 ≤ a ≤ 20,1 ≤ b ≤ 20。

对于存储非零元素的简记矩阵,其行数 n 满足:1 ≤ n ≤ 10000。

解析

题目要求读取一个稀疏矩阵,将其转换为简记形式,然后输出该矩阵的简记形式。简记形式为一个 n × 3 的矩阵,这里的 n 代表非零元素的数量,第一列是行号,第二列是列号,第三列是该位置的非零元素的值。

  1. 首先读取原始矩阵,我们可以运用两层嵌套的 for 循环来遍历原始矩阵的每一个元素并存储到 a 数组中。

  2. 然后判断当前元素是否为非零元素,若为非零元素,则将其行号存储到 b 数组第一列中,列号存储到 b 数组第二列中,元素值存储到 b 数组第三列中。

  3. 最后再输出矩阵的简记形式,可以利用一个 for 循环遍历 b 数组的每一行,按顺序输出 b 数组每一行的第一、二、三列,各列用空格分隔,每行结束后换行。

参考代码

cpp
#include <iostream>
using namespace std;
int a[110][110], b[10010][4];
int main()
{
    int m, n, t = 1;
    cin >> m >> n;
    for (int i = 1; i <= m; i++)
        for (int j = 1; j <= n; j++)
        {
            cin >> a[i][j];
            if (a[i][j] != 0)
            {
                b[t][1] = i;        // 把非零元素的行号保存到新数组第一列
                b[t][2] = j;        // 把列号保存在新数组第二列
                b[t++][3] = a[i][j]; // 把元素值保存在新数组第三列,且新数组的行号加 1
            }
        }
    for (int i = 1; i < t; i++) // 按顺序输出新数组每一行的第一、二、三列
        cout << b[i][1] << " " << b[i][2] << " " << b[i][3] << endl;
    return 0;
}

例 3:矩阵检测 ​

给定由 0 和 1 组成的 n × n 矩阵,如果矩阵的每一行和每一列中 1 的数量都是偶数,则认为符合条件。

你的任务就是检测矩阵是否符合条件,或者在仅改变一个矩阵元素的情况下能否符合条件。"改变矩阵元素"的操作定义为 0 变成 1 或者 1 变成 0。

【输入格式】输入 n + 1 行。

第 1 行输入一个正整数 n,表示矩阵的行数和列数。

之后的 n 行为 n 行 n 列的矩阵元素。

【输出格式】输出一行。

如果矩阵符合条件,则输出 yes!。

如果矩阵仅改变一个矩阵元素就能符合条件,则输出需要改变的元素所在的行号和列号,以一个空格分开。

如果不符合以上两条,输出 no!。

【输入样例】

4
1 0 1 0
0 0 1 0
1 1 1 1
0 1 0 1

【输出样例】

2 3

【数据范围】1 ≤ n ≤ 20。

解析

样例分析

输入 4 行 4 列的元素,如表 8-1 所示。

表 8-1 输入 4 行 4 列的元素

1010
0010
1111
0101

先看行,第 1 行有 2 个 1,1 的数量为偶数,满足条件;第 2 行有 1 个 1,1 的数量为奇数,不满足条件;第 3 行和第 4 行,1 的数量分别为 4 个和 2 个,均为偶数,满足条件。

再看列,第 1 列和第 2 列都有 2 个 1,1 的数量为偶数,满足条件;第 3 列有 3 个 1,1 的数量为奇数,不满足条件;第 4 列有 2 个 1,1 的数量为偶数,满足条件。

总结:根据样例,我们发现第 2 行和第 3 列不满足条件,仅有一行一列不满足,将第 2 行第 3 列的数字 1 变成 0 既能满足行上 1 的数量均为偶数,也能满足列上 1 的数量均为偶数,如表 8-2 所示。

表 8-2 改变第 2 行第 3 列的数字可满足要求

1010
0000
1111
0101
  1. 统计奇数行和奇数列的数量,若没有,直接输出 yes!。

  2. 如果奇数行和奇数列的数量都为 1,则改变它们的交点处的元素即可满足条件。

  3. 若奇数行或奇数列的数量大于 1,则无法通过改变一个元素来满足条件,输出 no!。

参考代码

cpp
#include <iostream>
using namespace std;
int main()
{
    int m, t[21][21], h, l, hs = 0, ls = 0, a = 0, b = 0;
    cin >> m;
    for (int i = 1; i <= m; i++)
        for (int j = 1; j <= m; j++)
            cin >> t[i][j];
    for (int i = 1; i <= m; i++)
    {
        a = 0; // 每行找 1,到下一行清零
        for (int j = 1; j <= m; j++)
            if (t[i][j] == 1)
                a++;
        if (a % 2 == 1) // 如果 1 的数量是奇数
        {
            hs++; // 行数增加
            h = i; // 保存行下标
        }
    }
    for (int i = 1; i <= m; i++)
    {
        b = 0; // 每列找 1,到下一列清零
        for (int j = 1; j <= m; j++)
            if (t[j][i] == 1) // 按列查找元素
                b++;
        if (b % 2 == 1)
        {
            ls++; // 列数增加
            l = i; // 保存列下标
        }
    }
    if (ls > 1 || hs > 1)        // 行数或者列数大于 1 就不满足
        cout << "no!";
    else if (hs == 0 && ls == 0) // 都没有就满足
        cout << "yes!";
    else                         // 修改保存的行下标和列下标位置元素后满足条件
        cout << h << " " << l;
    return 0;
}

例 4:蛙蛙的惩罚 ​

蛙蛙上课哼着"爱的魔力转圈圈"被老师听见,老师为了"惩罚"他,出了一道与转圈圈相关的题目,需要大家帮忙解决。

题目是要构建一个 n × n 的二维数组,然后按照顺时针转圈的方式,从数组的最右上角开始,依次填入数字 1、2、3、4……直至填满整个数组。

最后,将这个数组中的所有数字以三个位宽、左对齐的格式输出。

这就好像是数字在数组这个"大舞台"上顺时针"转圈圈",每个数字都有其固定的位置和顺序,而我们的任务就是找到这个顺序并按照要求输出结果。

【输入格式】输入一行,一个正整数 n,表示二维数组的行数和列数。

【输出格式】输出 n 行,顺时针转圈输出二维数组内的所有元素,从 1 开始,到 n × n 结束。

【输入样例】

3

【输出样例】

7   8   1
6   9   2
5   4   3

【数据范围】1 ≤ n ≤ 20。

对齐方式

左对齐:cout << left << setw(n) << a;

left 操作符的作用是设定对齐方式为左对齐,也就是说,后续输出的数据会在指定的字段宽度内靠左排列。

setw(n) 操作符用于设置下一个输出项的宽度为 n,如果输出项的实际宽度小于 n,那么会按照指定的对齐方式(这里是左对齐)来填充剩余的空间。

右对齐:cout << setw(n) << a;

setw(n) 操作符同样是设置下一个输出项的宽度为 n,如果输出项的实际宽度小于 n,默认会采用右对齐的方式来填充剩余的空间。

无论是左对齐还是右对齐,都需要使用 iomanip 头文件,即:#include <iomanip>。

解析

  1. 从右上角开始,从 1 开始数数,所以这个数字需要放入一个变量中,在循环中不停地重复自加。

  2. 在数组中走来走去,其实只有四种走法,分别是:

a. 从行头到行尾(向下)

b. 从列尾到列头(向左)

c. 从行尾到行头(向上)

d. 从列头到列尾(向右)

还要注意的是走完一行或者一列,行和列的数量要减少 1。

参考代码

cpp
#include <iostream>
#include <iomanip>
using namespace std;
int a[21][21];
int main()
{
    int n;
    cin >> n;
    int t = 1;                      // 定义初始值为 1 的变量 t,用于计数
    int ht = 1, hw = n, lt = 1, lw = n; // 行头 1,行尾 n,列头 1,列尾 n
    while (t <= n * n)
    {
        for (int i = ht; i <= hw; i++)
            a[i][lw] = t++;  // 从行头到行尾,在最后一列上行走
        lw--;                // 列尾减 1(少一列)
        for (int i = lw; i >= lt; i--)
            a[hw][i] = t++;
        hw--;                // 行尾减 1(少一行)
        for (int i = hw; i >= ht; i--)
            a[i][lt] = t++;
        lt++;                // 列头加 1(少一列)
        for (int i = lt; i <= lw; i++)
            a[ht][i] = t++;
        ht++;                // 行头加 1(少一行)
    }
    for (int i = 1; i <= n; i++)
    {
        for (int j = 1; j <= n; j++)
            cout << left << setw(3) << a[i][j] << " ";
        cout << endl;
    }
    return 0;
}

基于 MIT 协议发布