中 · 二维数组
矩阵转置
你已经会求矩阵元素之和了。转置问的是一个更尖锐的问题:当你写下 a[i][j] 时,你确定 i 和 j 各是什么意思吗?
一句话答案把输入的第 j 列当成输出的第 j 行打印 —— 两层循环要对调:外层遍历列,内层遍历行。
题目
输入: 第一行两个整数 n 和 m(1 ≤ n, m ≤ 50),分别是行数和列数。接下来 n 行,每行 m 个整数。
输出: 转置后的矩阵:m 行,每行 n 个整数。输出的第 j 行就是输入的第 j 列。
样例:
| 输入 | 输出 |
|---|---|
2 3 | 1 4 |
1 1 | 7 |
3 1 | 1 2 3 |
注意形状变了:2×3 的矩阵变成 3×2。所以打印时两个循环的上下界要对调 —— 这既是全部技巧,也是全部陷阱。
你的解答
「运行我的输入」用的自定义输入
判题在浏览器本地运行。死循环会在 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;
}