力扣-数组-二维数组变换

566 重塑矩阵

566 重塑矩阵

在MATLAB中,有一个非常有用的函数 reshape,它可以将一个矩阵重塑为另一个大小不同的新矩阵,但保留其原始数据。
给出一个由二维数组表示的矩阵,以及两个正整数r和c,分别表示想要的重构的矩阵的行数和列数。
重构后的矩阵需要将原始矩阵的所有元素以相同的行遍历顺序填充。
如果具有给定参数的reshape操作是可行且合理的,则输出新的重塑矩阵;否则,输出原始矩阵。

示例 1:

输入:
nums =
[[1,2],
[3,4]]
r = 1, c = 4
输出:
[[1,2,3,4]]
解释:
行遍历nums的结果是 [1,2,3,4]。新的矩阵是 1 * 4 矩阵, 用之前的元素值一行一行填充新矩阵。

示例 2:

输入:
nums =
[[1,2],
[3,4]]
r = 2, c = 4
输出:
[[1,2],
[3,4]]
解释:
没有办法将 2 * 2 矩阵转化为 2 * 4 矩阵。 所以输出原矩阵。

注意:

给定矩阵的宽和高范围在 [1, 100]。
给定的 r 和 c 都是正数。

可以将整数 x 映射回其在矩阵中的下标,其中 / 表示整数除法,% 表示取模运算。
566

class Solution {
public:
    vector<vector<int>> matrixReshape(vector<vector<int>>& nums, int r, int c) {
        int m = nums.size();
        int n = nums[0].size();
        if (m * n != r * c) return nums;

        vector<vector<int>> ans(r, vector<int> (c));
        for (int x = 0; x < m * n; x++) ans[x / c][x % c] = nums[x / n][x % n];
        return ans;
    }
};
  • 时间复杂度:O(rc)。这里的时间复杂度是在重塑矩阵成功的前提下的时间复杂度,否则当mn≠rc 时,C++ 语言中返回的是原数组的一份拷贝,本质上需要的时间复杂度为 O(mn),而其余语言可以直接返回原数组的对象,需要的时间复杂度仅为 O(1)。
  • 空间复杂度:O(1)。这里的空间复杂度不包含返回的重塑矩阵需要的空间。

48 旋转图像

48 旋转图像

给定一个 n × n 的二维矩阵 matrix 表示一个图像。请你将图像顺时针旋转 90 度。
你必须在 原地 旋转图像,这意味着你需要直接修改输入的二维矩阵。请不要 使用另一个矩阵来旋转图像。

示例 1:
1

输入:matrix = [[1,2,3],[4,5,6],[7,8,9]]
输出:[[7,4,1],[8,5,2],[9,6,3]]

示例 2:
2

输入:matrix = [[5,1,9,11],[2,4,8,10],[13,3,6,7],[15,14,12,16]]
输出:[[15,13,2,5],[14,3,4,1],[12,6,8,9],[16,7,10,11]]

示例 3:

输入:matrix = [[1]]
输出:[[1]]

示例 4:

输入:matrix = [[1,2],[3,4]]
输出:[[3,1],[4,2]]

提示:

matrix.length = = n
matrix[i].length = = n
1 <= n <= 20
-1000 <= matrix[i][j] <= 1000

要求在 原地 旋转图像,就是不能直接乘一个矩阵。
官方题解有三个方法,我用的是法二,原地旋转。

class Solution {
public:
    void rotate(vector<vector<int>>& matrix) {
        int n = matrix.size();
        for (int i = 0; i < n / 2; ++i) {
            for (int j = 0; j < (n + 1) / 2; ++j) {
                int temp = matrix[i][j];
                matrix[i][j] = matrix[n - 1 - j][i];
                matrix[n - 1 - j][i] = matrix[n - 1 - i][n - 1 - j];
                matrix[n - 1 - i][n - 1 - j] = matrix[j][n - 1 - i];
                matrix[j][n - 1 - i] = temp;
            }
        }
    }
};
  1. 使用辅助数组,在遍历完成之后,再将matrix new中的结果复制到原数组中
  • 时间复杂度:O(N2),其中 N 是matrix 的边长。
  • 空间复杂度:O(N2)。我们需要使用一个和 matrix 大小相同的辅助数组。
  1. 原地旋转
    当 nn 为偶数时,我们需要枚举n2/4=(n/2)×(n/2) 个位置,可以将该图形分为四块,以4×4 的矩阵为例
    2
    当 nn 为奇数时,由于中心的位置经过旋转后位置不变,我们需要枚举(n2 −1)/4=((n−1)/2)×((n+1)/2) 个位置,需要换一种划分的方式,以 5×5 的矩阵为例:
    1
    同样保证了不重复、不遗漏,矩阵正中央的点无需旋转。
  • 时间复杂度:O(N2),其中 NN 是matrix 的边长。我们需要枚举的子矩阵大小为 O(⌊n/2⌋×⌊(n+1)/2⌋)=O(N2)。
  • 空间复杂度:O(1)。为原地旋转。
  1. 用翻转代替旋转
    3
  • 时间复杂度:O(N2),其中 N 是 matrix 的边长。对于每一次翻转操作,我们都需要枚举矩阵中一半的元素。
  • 空间复杂度:O(1)。为原地翻转得到的原地旋转。

73 矩阵置零

73 矩阵置零

给定一个 m x n 的矩阵,如果一个元素为 0,则将其所在行和列的所有元素都设为 0。请使用原地算法。

示例 1:

输入:
[
[1,1,1],
[1,0,1],
[1,1,1]
]
输出:
[
[1,0,1],
[0,0,0],
[1,0,1]
]

示例 2:

输入:
[
[0,1,2,0],
[3,4,5,2],
[1,3,1,5]
]
输出:
[
[0,0,0,0],
[0,4,5,0],
[0,3,1,0]
]

进阶:

  • 一个直接的解决方案是使用 O(mn) 的额外空间,但这并不是一个好的解决方案。
  • 一个简单的改进方案是使用 O(m + n) 的额外空间,但这仍然不是最好的解决方案。
  • 你能想出一个常数空间的解决方案吗?

要求我们使用常数空间,所以我们可以使用对第一行和第一列进行修改,然后根据第一行第一列来改变matrix

具体步骤为:

  1. 首先要记录下第一行和第一列是否全部为零(就是第一行第一列内是否有零,最后再进行处理)
  2. 遍历除第一行第一列以外的数据,如果有修改第一行第一列对应位置为零
  3. 根据第一行第一列修改matrix
  4. 修改第一行第一列
class Solution {
public:
    void setZeroes(vector<vector<int>>& matrix) {
        if (matrix.size() == 0) return;
        const int m = matrix.size();
        const int n = matrix[0].size();
        bool isRow = false;
        bool isCol = false;

        for (int i = 0; i < m; i++) {
            for (int j = 0; j < n; j++) {
                if (matrix[i][j] == 0) {
                    if (i == 0) {
                        isRow = true;
                    }
                    if (j == 0) {
                        isCol = true;
                    }
                    matrix[0][j] = 0;
                    matrix[i][0] = 0;
                }
            }
        }

//得到的标记对应行列元素赋值为零
        for (int i = 1; i < m; i++) {
            for (int j = 1; j < n; j++) {
                if (matrix[0][j] == 0 || matrix[i][0] == 0) {
                    matrix[i][j] = 0;
                }
            }
        }

        if (isRow) {
            for (int j = 0; j < n; ++j) matrix[0][j] = 0; 

        }
        if (isCol) {
            for (int i = 0; i < m; ++i) matrix[i][0] = 0;
        }
    }
};
  • 时间复杂度:O(M×N)
  • 空间复杂度:O(1)

289 生命游戏

289 生命游戏

根据 百度百科 ,生命游戏,简称为生命,是英国数学家约翰·何顿·康威在 1970 年发明的细胞自动机。

给定一个包含 m × n 个格子的面板,每一个格子都可以看成是一个细胞。每个细胞都具有一个初始状态:1 即为活细胞(live),或 0 即为死细胞(dead)。每个细胞与其八个相邻位置(水平,垂直,对角线)的细胞都遵循以下四条生存定律:

  1. 如果活细胞周围八个位置的活细胞数少于两个,则该位置活细胞死亡;
  2. 如果活细胞周围八个位置有两个或三个活细胞,则该位置活细胞仍然存活;
  3. 如果活细胞周围八个位置有超过三个活细胞,则该位置活细胞死亡;
  4. 如果死细胞周围正好有三个活细胞,则该位置死细胞复活;

下一个状态是通过将上述规则同时应用于当前状态下的每个细胞所形成的,其中细胞的出生和死亡是同时发生的。给你 m x n 网格面板 board 的当前状态,返回下一个状态。

示例 1:1

输入:board = [[0,1,0],[0,0,1],[1,1,1],[0,0,0]]
输出:[[0,0,0],[1,0,1],[0,1,1],[0,1,0]]

示例 2:
2

输入:board = [[1,1],[1,0]]
输出:[[1,1],[1,1]]

提示:

m == board.length
n == board[i].length
1 <= m, n <= 25
board[i][j] 为 0 或 1

进阶:

  • 你可以使用原地算法解决本题吗?请注意,面板上所有格子需要同时被更新:你不能先更新某些格子,然后使用它们的更新后的值再更新其他格子。
  • 本题中,我们使用二维数组来表示面板。原则上,面板是无限的,但当活细胞侵占了面板边界时会造成问题。你将如何解决这些问题?

参考官方题解

  1. 复制原数组进行模拟
    根据复制数组中邻居细胞的状态来更新 board 中的细胞状态。
class Solution {
public:
    void gameOfLife(vector<vector<int>>& board) {
        int neighbors[3] = {0, 1, -1};

        int rows = board.size();
        int cols = board[0].size();

        vector<vector<int>> copyBoard(rows, vector<int>(cols, 0)); //创建复制数组
        for (int row = 0; row < rows; row++) {
            for (int col = 0; col < cols; col++) copyBoard[row][col] = board[row][col];
        }

        //遍历
        for (int row = 0; row < rows; row++) {
            for (int col = 0; col < cols; col++) {
                //统计相邻活细胞
                int liveNeighbors = 0;
                for (int i = 0; i < 3; ++i) {
                    for (int j = 0; j < 3; ++j) {
                        if (!(neighbors[i] == 0 && neighbors[j] == 0)) {
                            int r = (row + neighbors[i]);
                            int c = (col + neighbors[j]);
                            if ((r < rows && r >= 0) && (c < cols && c >= 0) && (copyBoard[r][c] == 1)) liveNeighbors += 1;
                        }
                    }
                }
                //规则1 规则3
                if ((copyBoard[row][col] == 1) && (liveNeighbors < 2 || liveNeighbors > 3)) board[row][col] = 0;
                //规则4
                if (copyBoard[row][col] == 0 && liveNeighbors == 3) board[row][col] = 1;
            }
        }
    }
};
  • 时间复杂度:O(mn),其中 m 和 n 分别为 board 的行数和列数。
  • 空间复杂度:O(mn),为复制数组占用的空间。
  1. 使用额外的状态
    定义一个复合状态 2。这样我们看到 2,既能知道目前这个细胞是活的,还能知道它之前是死的。
    定义一个复合状态-1。这样我们看到-1,既能知道目前这个细胞是死的,还能知道它之前是活的。
    再遍历一次数组,将复合状态为 2 的细胞的值改为 1,复合状态为 -1 的细胞的值改为 0。
class Solution {
public:
    void gameOfLife(vector<vector<int>>& board) {
        int neighbors[3] = {0, 1, -1};

        int rows = board.size();
        int cols = board[0].size();

        // vector<vector<int>> copyBoard(rows, vector<int>(cols, 0)); //创建复制数组
        // for (int row = 0; row < rows; row++) {
        //     for (int col = 0; col < cols; col++) copyBoard[row][col] = board[row][col];
        // }

        //遍历
        for (int row = 0; row < rows; row++) {
            for (int col = 0; col < cols; col++) {
                //统计相邻活细胞
                int liveNeighbors = 0;
                for (int i = 0; i < 3; ++i) {
                    for (int j = 0; j < 3; ++j) {
                        if (!(neighbors[i] == 0 && neighbors[j] == 0)) {
                            int r = (row + neighbors[i]);
                            int c = (col + neighbors[j]);
                            //if ((r < rows && r >= 0) && (c < cols && c >= 0) && (copyBoard[r][c] == 1)) liveNeighbors += 1;
                            if ((r < rows && r >= 0) && (c < cols && c >= 0) && (abs(board[r][c]) == 1)) liveNeighbors += 1; 
                        }
                    }
                }
                //规则1 规则3
                if ((board[row][col] == 1) && (liveNeighbors < 2 || liveNeighbors > 3)) //board[row][col] = 0;
                board[row][col] = -1;
                //规则4
                if (board[row][col] == 0 && liveNeighbors == 3) //board[row][col] = 1;
                board[row][col] = 2;
            }
        }

        // 遍历 board 得到一次更新后的状态
        for (int row = 0; row < rows; row++) {
            for (int col = 0; col < cols; col++) {
                if (board[row][col] > 0) board[row][col] = 1;
                else board[row][col] = 0;
            }
        }
    }
};
  • 时间复杂度:O(mn),其中 m,n 分别为 board 的行数和列数。
  • 空间复杂度:O(1),除原数组外只需要常数的空间存放若干变量。

对于额外的状态,下面还有一种比较容易懂的题解

首先遍历时不让每一个格子都看一眼周围的八个格子去更新自己状态。
我换个角度,遍历的时候,如果这个格子里是活的,我就让它去“影响”周围的八个格子。这样一来,大批原来是死了的格子就省了很多检查的时间。
怎么“影响”?给被影响的格子里的数字加10。这样一来,个位存着这格子原来的状态,十位就存着它周围有多少个活格子了。比如遍历完了之后一个格子里是41,那就表示它原来自己是1,然后被周围的四个活格子加了四个10,于是周围有四个活细胞。

class Solution {
public:
    void gameOfLife(vector<vector<int>>& board) {
        vector<int> neighbor = { 0, 1, -1 };

        int m = board.size();
        int n = board[0].size();

        for (int i = 0; i < m; ++i) {
		    for (int j = 0; j < n; ++j) {
			    if (board[i][j] % 10 == 1) {
				    for (int k = 0; k < 3; ++k) {
					    for (int l = 0; l < 3; ++l) {
						    int left = i + neighbor[k];
						    int right = j + neighbor[l];
                            if (!neighbor[k] && !neighbor[l]) continue;
						    if (left >= 0 && right >= 0 && left < m && right < n) board[left][right] += 10;
					    }
				    }
			    }
		    }
        }

	    for (int i = 0; i < m; ++i) {
		    for (int j = 0; j < n; ++j) {
			    if (board[i][j] == 21 || board[i][j] == 31) board[i][j] = 1;
			    else if (board[i][j] == 30) board[i][j] = 1;
			    else board[i][j] = 0;
		    }
        }
	}
};
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

国服最强貂蝉

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值