中 · 二维数组

矩阵转置

你已经会求矩阵元素之和了。转置问的是一个更尖锐的问题:当你写下 a[i][j] 时,你确定 i 和 j 各是什么意思吗?

一句话答案把输入的第 j 列当成输出的第 j 行打印 —— 两层循环要对调:外层遍历列,内层遍历行。

题目

输入: 第一行两个整数 n 和 m(1 ≤ n, m ≤ 50),分别是行数和列数。接下来 n 行,每行 m 个整数。

输出: 转置后的矩阵:m 行,每行 n 个整数。输出的第 j 行就是输入的第 j 列。

样例:

输入输出
2 3
1 2 3
4 5 6
1 4
2 5
3 6
1 1
7
7
3 1
1
2
3
1 2 3

注意形状变了:2×3 的矩阵变成 3×2。所以打印时两个循环的上下界要对调 —— 这既是全部技巧,也是全部陷阱。

你的解答

main.cpp — 你的解答
「运行我的输入」用的自定义输入

判题在浏览器本地运行。死循环会在 3 秒后被自动中断 —— 但还是确保每个循环都有出口。代码随打随存。

卡住了?提示

提示 1 — 不需要第二个数组

最容易想到的是建一个 b[m][n],写 b[j][i] = a[i][j],再打印。这样对,但可以直接打印:按行读进来、按列打出去,本身就是转置。一个数组,零次拷贝。

提示 2 — 要换的是循环的上下界,不只是下标

外层让 j 从 0 到 m - 1,内层让 i 从 0 到 n - 1。如果下标写成 a[j][i] 却仍用 n、m 当上下界,矩阵一旦不是正方形就越界;而如果是正方形,它会静默地原样输出。

提示 3 — 把下标念出来

a[行][列] —— 在 C++ 和所有用这种写法的语言里,第一个下标永远是行。写任何遍历网格的嵌套循环之前,先念清楚哪个循环变量是行、哪个是列。二维数组的 bug 大多不是逻辑错,而是一个你没注意到的对调。

题解(先自己试试!)
#include <iostream>
using namespace std;

int main() {
    int n, m;
    cin >> n >> m;
    int a[55][55];
    for (int i = 0; i < n; i++)
        for (int j = 0; j < m; j++)
            cin >> a[i][j];

    for (int j = 0; j < m; j++) {
        for (int i = 0; i < n; i++) {
            cout << a[i][j] << " ";
        }
        cout << endl;
    }
    return 0;
}

不建第二个数组也能转置,是因为你从来不需要让转置结果存在 —— 你只需要换个顺序读数据。这个区分,「我是真的需要重排数据,还是只需要换种遍历方式?」,在很多题里能替你省掉一次拷贝。矩阵旋转 90°、矩阵乘法、按对角线遍历网格,都是它的变体。下一题,数据是一个由你决定进制写出来的数:进制转换。

深度解析

为什么这样做对

按行把矩阵读进来、按列打出去,<em>本身就是</em>转置。你从来不需要让转置结果真的存在,只需要换一种遍历方式。

代价

O(n×m) 时间、零额外空间。建第二个矩阵再打印,白多花一个 n×m 的空间。

常见错法

  • 下标换成 a[j][i] 却仍用 n、m 当上下界:矩阵不是正方形就越界;是正方形则静默输出原矩阵。
  • a[行][列] 里第一个下标永远是行。写任何遍历网格的嵌套循环前,先念清楚哪个循环变量是行、哪个是列。

下一步

数据换成一个由你决定进制写出来的数:进制转换。

记住这两句

  • 按行读、按列打,就是转置。
  • a[行][列] —— 第一个下标永远是行。
🐞 这段是错的 —— 点开,亲手让它挂一次

原样输出 —— 转置根本没发生

#include <iostream>
using namespace std;

int main() {
    int n, m;
    cin >> n >> m;
    int a[55][55];
    for (int i = 0; i < n; i++)
        for (int j = 0; j < m; j++)
            cin >> a[i][j];

    for (int i = 0; i < n; i++) {
        for (int j = 0; j < m; j++) cout << a[i][j] << " ";
        cout << endl;
    }
    return 0;
}