二维数组变换
文章内容是自己刷leetcode官方刷题攻略的一些经验与总结。
题目链接详见 leetcode刷题攻略
如果喜欢的话,别忘了点个赞哦 ^ _ ^
一.566重塑矩阵
1.题目描述
2.题目分析与解答
这道题目是 easy 难度,确实也挺简单的。
我们需要将给定的 m 行 n 列的矩阵变换成 r 行 c 列的一个新矩阵。那我们新建一个矩阵行为 r,列为 c。首先判断一下两个矩阵中元素个数是否相同,然后遍历原矩阵来给新矩阵赋值,使用两个变量来控制新矩阵的行和列。
C++ 代码如下:
class Solution {
public:
vector<vector<int>> matrixReshape(vector<vector<int>>& mat, int r, int c) {
int m = mat.size();
int n = mat[0].size();
if(m * n != r * c) return mat;
vector<vector<int>> ans(r, vector<int>(c, 0));
int x = 0, y = 0; //控制行和列
for(int i = 0; i < m; i ++) {
for(int j = 0; j < n; j ++) {
if(y == c) {
y = 0;
x ++;
}
ans[x][y] = mat[i][j];
y ++;
}
}
return ans;
}
};
二.48旋转图像
1.题目描述
2.题目分析与解答
这道题目要把原始矩阵顺时针旋转90°,而且只能原地修改。那么可以用对称的方法得到新矩阵。
我们发现其实可以将矩阵进行两种变换:
- 沿主对角线交换对称元素位置;
- 按照列对称交换元素。
经过上述的两种变换(无先后顺序),可以得到新矩阵。
C++ 代码如下:
class Solution {
public:
void rotate(vector<vector<int>>& matrix) {
//题目要求不能使用另一个矩阵,必须原地旋转图像
//可以沿主对角线交换对称元素位置,然后再按列对称交换元素
int n = matrix.size();
if(n == 1) return ;
//沿主对角线交换对称元素位置
for(int i = 0; i < n; i ++) {
for(int j = i + 1; j < n; j ++) {
swap(matrix[i][j], matrix[j][i]);
}
}
//再按列对称交换元素
for(int i = 0; i < n; i ++) {
for(int j = 0; j < n / 2; j ++) {
swap(matrix[i][j], matrix[i][n - 1 - j]);
}
}
}
};
3.题目拓展
这道题目是要求我们顺时针将矩阵旋转90°,其实旋转其他的角度也类似。
- 旋转180°:将头元素与尾元素对应交换,遍历方式:头元素 ++,尾元素 –;
- 旋转270°:与旋转90°类似,分为按照副对角线对称位置交换,按照列对称位置交换两步(无先后顺序)。
三.73矩阵置零
1.题目描述
2.题目分析与解答
如果该元素为0,那么改行与该列要全部置为零,但是我们不能一边遍历一边操作(自己想一下为什么?)。
我们需要用到标记数组,因为一个元素为零可以控制一行与一列,所以我们用两个标记数组,分别记录行与列是否应该为零。
首先遍历一遍矩阵,将两个标记数组赋初值,然后分别按照行和列遍历矩阵,根据标记数组更新矩阵的值。
C++ 代码如下:
class Solution {
public:
void setZeroes(vector<vector<int>>& matrix) {
int m = matrix.size();
int n = matrix[0].size();
//空间复杂度O(m + n)
//使用额外的两个数组(标记数组)记录行和列是否为零
vector<int> row(m, 0);
vector<int> col(n, 0);
for(int i = 0; i < m; i ++) {
for(int j = 0; j < n; j ++) {
if(matrix[i][j] == 0) {
row[i] = 1;
col[j] = 1;
continue;
}
}
}
for(int i = 0; i < m; i ++) {
if(row[i] == 1) {
for(int j = 0; j < n; j ++) {
matrix[i][j] = 0;
}
}
}
for(int j = 0; j < n; j ++) {
if(col[j] == 1) {
for(int i = 0; i < m; i ++) {
matrix[i][j] = 0;
}
}
}
}
};
3.优化空间复杂度为O(1)
上述方法的空间复杂度为O(m + n),这里将其优化为O(1)。
通过观察可以发现上述使用的两个标记数组的大小分别是矩阵的长和宽,那么其实我们可以用矩阵的第一行与第一列来作为标记数组。再使用两个常数来标记矩阵的第一行与第一列是否应该置零。
更新过程如下:
- 首先根据矩阵的第一行与第一列来给两个标记常数赋值,判断第一行与第一列是否为零;
- 然后根据矩阵除了第一行和第一列的其余位置,来更新两个标记数组的值;
- 根据标记数组的值来更新矩阵除了第一行与第一列的值;
- 最后根据两个标记常数来更新矩阵第一行与第一列的值。
C++ 代码如下:
class Solution {
public:
void setZeroes(vector<vector<int>>& matrix) {
int m = matrix.size();
int n = matrix[0].size();
//空间复杂度O(m + n)
//使用额外的两个数组(标记数组)记录行和列是否为零
//优化空间复杂度为O(1),还是使用标记数组,不过是用矩阵的第一行和第一列来代替
//那么还需要两个标记变量来记录矩阵第一行和第一列是否有0
bool flag_row = 0, flag_col = 0;
//先统计第一行与第一列是否有0
for(int j = 0; j < n; j ++) {
if(matrix[0][j] == 0) {
flag_col = 1;
break;
}
}
for(int i = 0; i < m; i ++) {
if(matrix[i][0] == 0) {
flag_row = 1;
break;
}
}
//再统计矩阵剩余位置0的行和列
for(int i = 1; i < m; i ++) {
for(int j = 1; j < n; j ++) {
if(matrix[i][j] == 0) {
matrix[i][0] = 0;
matrix[0][j] = 0;
}
}
}
//根据标记数组原地修改原始矩阵
for(int i = 1; i < m; i ++) {
for(int j = 1; j < n; j ++) {
if(matrix[i][0] == 0 || matrix[0][j] == 0) {
matrix[i][j] = 0;
}
}
}
//根据两个标记变量修改第一行和第一列的值
if(flag_col) {
for(int j = 0; j < n; j ++) {
matrix[0][j] = 0;
}
}
if(flag_row) {
for(int i = 0; i < m; i ++) {
matrix[i][0] = 0;
}
}
}
};
四.289生命游戏
1.题目描述
2.题目分析与解答
一个细胞的状态是由他周围 8 个细胞的状态决定。
根据题目的描述,有以下几种变化方式:其中 1 代表活细胞,0 代表死细胞。
- 1 的个数小于2,该位置 1 变为 0;
- 1 的个数等于 2 或 3,1 还是 1;
- 1 的个数等于 3,0 变成 1;
- 1 的个数大于 3,1 变成 0;
这道题目与上一道题目更新方式类似都不能一边遍历,一边更新,我们要根据初始状态所有细胞的状态来预测下一个状态所有细胞的状态。
如果直接开一个一模一样的数组来储存更改信息,空间消耗会很大。这里引入一种新的思想,用其他的数字来表示额外的状态。这道题目中,我们用 2 表示 1 变成 0,用 3 表示 0 变成 1,0 和 1 还是原来的含义。
那么我们进行矩阵的遍历即可,在遍历过程中,需要记录每个元素周围 1 的个数,根据 1 的个数来更新细胞的状态。具体过程详见代码。
C++ 代码如下:
class Solution {
public:
void gameOfLife(vector<vector<int>>& board) {
//周围八个位置
//1的个数小于2,该位置1变为0
//1的个数等于2或3,1还是1
// 1的个数等于3,0变成1
//1的个数大于3,1变成0
//直观的想法是重新开一个一样的数组来进行更改,但是空间消耗会很大
//改进:我们可以用额外的数字记录状态的变化
//下面我们用2表示1变成0,用3表示0变成1,0和1还是原来的含义
int m = board.size();
int n = board[0].size();
for(int i = 0; i < m; i ++) {
for(int j = 0; j < n; j ++) {
int n1 = 0; //记录周围1的个数
for(int x = -1; x <= 1; x ++) {
for(int y = -1; y <= 1; y ++) {
if(x == 0 && y == 0) continue; //遇到当前点跳过
if(i + x >= 0 && i + x < m && j + y >= 0 && j + y < n) {
//注意这里要记录活细胞个数,也就是统计1与2(由活变死)的个数
if(board[i + x][j + y] == 1 || board[i + x][j + y] == 2) n1 ++;
}
}
}
cout << n1 << endl;
if((n1 < 2 || n1 > 3) && board[i][j] == 1) board[i][j] = 2;
else if(n1 == 3 && board[i][j] == 0) board[i][j] = 3;
}
}
for(int i = 0; i < m; i ++) {
for(int j = 0; j < n; j ++) {
if(board[i][j] == 2) board[i][j] = 0;
else if(board[i][j] == 3) board[i][j] = 1;
}
}
}
};