二维数组变换
重塑矩阵
-
题目介绍
-
思路分析
- 这道题根据题目的意思顺着写就可以,但在遍历二维数组之前,要提前判断给的r、c是否合理,如果不合理直接返回就可以。
- 当给的r和c的乘积等于m和n的乘积时,遍历新的数组,然后按照行遍历顺序分别填充。
-
相关代码片段
public int[][] matrixReshape(int[][] mat, int r, int c) {
int m = mat.length;
int n = mat[0].length;
//判断给出的r和c是否合理
if(r*c!=m*n){
return mat;
}
int k1 = 0, k2 = 0;
int[][] res = new int[r][c];
for(int i = 0; i < r; i++){
for(int j = 0; j < c;j++){
res[i][j] = mat[k1][k2++];
if(k2 == n){
k1++;
k2 = 0;
}
}
}
return res;
}
旋转图像
-
题目介绍
-
思路分析
-
题目很容易理解,就是把原来的二维数组顺时针旋转90°即可。但给的要求不允许我们开辟一个新的数组。
-
我们在这里介绍三种解法:第一种即为借助一个新的二维数组去存储顺时针旋转90°的二维数组;第二种方法为对二维数组进行原地顺时针旋转90°;第三种方法为通过数组翻转来实现数组顺时针的旋转90°。
-
当我们准备用一个新的二维数组来存储改变后的数组时,可以看到只要将原来数组的每一列从下往上依次存到新数组的一行里面即可,如图,
-
下面我们介绍第二种方法:通过示例我们可以发现一个元素旋转后的位置再旋转两次就会回到原点,如图:
于是我们通过可以先改变这四个位置的元素的值。剩下我们只需要考虑一共有多少个类似这样的元素需要旋转。 -
当n为偶数时,需要旋转的元素一共有(n/2 x n/2)个,当n为奇数时,需要旋转的元素一共有((n-1)/2 x (n+1)/2)个,,n为奇数时,剩下的最中间的哪个元素是不需要变化的。如图:
-
第三种方法就比较巧妙了:利用两次翻转来实现题目的要求,首先按行进行翻转,然后按照正对角线进行翻转得到了题目需要的数组:
-
-
相关代码片段
// 第一种方法
public void rotate(int[][] matrix) {
int m = matrix.length;
int n = matrix[0].length;
int[][] mat = new int[m][n];
int k1 = 0,k2 = 0;
for(int j = 0; j < n; j++){
for(int i = m - 1; i >= 0; i--){
mat[k1][k2++] = matrix[i][j];
if(k2 == n){
k1++;
k2 = 0;
}
}
}
for(int i = 0; i < m; i++){
for(int j = 0; j < n; j++){
matrix[i][j] = mat[i][j];
}
}
}
//第三种方法
public void rotate(int[][] matrix) {
int n = matrix.length;
// 水平翻转
for (int i = 0; i < n / 2; ++i) {
for (int j = 0; j < n; ++j) {
int temp = matrix[i][j];
matrix[i][j] = matrix[n - i - 1][j];
matrix[n - i - 1][j] = temp;
}
}
// 主对角线翻转
for (int i = 0; i < n; ++i) {
for (int j = 0; j < i; ++j) {
int temp = matrix[i][j];
matrix[i][j] = matrix[j][i];
matrix[j][i] = temp;
}
}
}
矩阵置零
-
题目介绍
-
思路分析
- 首先这道题难点在于如何区分一个正在变化的数组当中的某个位置上的0是因为另外一个同行同列的元素为0而变成0还是本身就是0。
- 因此我们可以用一个二维数组来表示二维数组中的每个元素是否被访问过,这样就能区分上面提到的疑惑。但是这种方法的空间复杂度为O(n2),第一种优化就是用两个一维数组用来存储二维数组的行和列,但是思路基本上是一样的。
- 这里再介绍一种空间复杂度为O(1)的方法。我们可以设置两个标记符号来标记二维数组的首行和首列中是否存在0,然后在将每行每列中存在的0"移动"到对应的首行首列,然后遍历除去首行首列的所有元素,当其所在行或列的第一个元素有一个为0时,将其置为0。
- 这种方法主要是将二维数组分成了两部分:数组的首行首列和数组的剩余部分,二者相互印证,相互变化。
-
相关代码片段
// 第一种方法:空间复杂度为O(n2) 或者 O(n)
public void setZeroes(int[][] matrix) {
int m = matrix.length;
int n = matrix[0].length;
boolean[][] isVisited = new boolean[m][n];
for (int i = 0; i < m; i++) {
for (int j = 0; j < n; j++) {
if (matrix[i][j] == 0 && !isVisited[i][j]) {
for (int k = 0; k < m; k++) {
if(matrix[k][j] != 0) {
matrix[k][j] = 0;
isVisited[k][j] = true;
}
}
for (int l = 0; l < n; l++) {
if(matrix[i][l] != 0) {
matrix[i][l] = 0;
isVisited[i][l] = true;
}
}
}
}
}
}
// 第二种方法
public void setZeros1(int[][] matrix){
int m = matrix.length; int n = matrix[0].length;
boolean flagRow0 = false;
boolean flagCol0 = false;
//判断首行首列是否有0
for(int i = 0; i < m; i++){
if(matrix[i][0] == 0){
flagCol0 = true;
}
}
for(int i = 0; i < n; i++){
if(matrix[0][i] == 0){
flagRow0 = true;
}
}
//将数组中除去首行首列中的的0元素"移动"到对应的首行首列。
for(int i = 0; i < m; i++){
for(int j = 0; 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(flagCol0){
matrix[i][0] = 0;
}
if(flagRow0){
matrix[0][j] = 0;
}
}
}
生命游戏
-
题目介绍
-
思路分析
- 这道题根据题目中列举的规则进行变换即可
- 首先开辟一个新的二维数组用来存放初始状态,然后统计一个格子周围八个格子当中的活细胞个数。这里要注意边界情况的考虑。
-
相关代码片段
public void gameOfLife(int[][] board) {
//表示一个元素相邻的八个位置
int[] neigh = {1,0,-1};
int m = board.length;
int n = board[0].length;
int[][] cur = new int[m][n];
//保留初始状态
for(int i = 0; i < m; i++){
for(int j = 0; j < n; j++){
cur[i][j] = board[i][j];
}
}
for(int i = 0; i < m; i++){
for(int j = 0; j < n; j++){
int count = 0;
//统计每个格子周围相邻的8个元素中相邻的个数
for(int k = 0; k < 3; k++){
for(int l = 0; l < 3; l++){
if(!(neigh[k] == 0&&neigh[l] == 0)){
int r = i + neigh[k];
int c = j + neigh[l];
//要考虑边界情况,格子周围相邻的格子不能越界
if(r>=0&&r<m&&c>=0&&c<n&&cur[r][c] == 1){
count ++;
}
}
}
}
if(board[i][j] == 1 && (count < 2 || count > 3)){
board[i][j] = 0;
}
if(board[i][j] == 0 && count == 3){
board[i][j] = 1;
}
}
}
}
总结
这次主要总结了关于二维数组变换的相关题目。多次提到原地算法:在我看来,原地算法的优势即减少了空间复杂度,但相应的代价就是逻辑变得复杂。运用原地算法求解题目时,首先一定存在某种"循环"的规律,然后在这个规律的基础上去求解题目。 希望大家能够有所收获!