当前位置: 首页 > news >正文

leetcode-hot-100 (矩阵)

1、矩阵置零

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

解答

方法一:使用一个二维数组

这是我看到这道题目的第一个想法,就是直接循环遍历该数组的各个元素,然后要是遇到了数组元素为 0 0 0 的位置,直接在上小循环,对该位置所在的行和列进行置零,但是这样的话,元素主 m a t r i x matrix matrix 中的值被改变了,造成了数据污染,因此置零的依据不能是以原数组为依据,于是我又想到可以复制出来一个新的数组,然后以新的数组为置零依据,对原始数组 m a t r i x matrix matrix 进行操作即可,于是代码编写如下:

class Solution {
public:void setZeroes(vector<vector<int>>& matrix) {vector<vector<int>> temp(matrix);int row_len = matrix.size();int col_len = matrix[0].size();for (int i = 0; i < row_len; i++) {for (int j = 0; j < col_len; j++) {if (temp[i][j] == 0) {for (int k = 0; k < col_len; k++)matrix[i][k] = 0;for (int k = 0; k < row_len; k++)matrix[k][j] = 0;}}}}
};

结果也还不错(就是空间复杂度比较的高):在这里插入图片描述

方法二:使用两个一维数组

方法一的局限性在于其空间复杂度实在是太高了,也确实,因为我们在方法一中使用了一个二维数组,因此空间复杂度为 O ( m n ) O(mn) O(mn) ,但是经过仔细分析后我们可以发现,实际上创建一个二维数组是完全没有必要的,我们可以直接引入两个一维数组,一个行数组,一个列数组,分别记录要是某一行出现 0 0 0 ,这将行数组的对应位置置成 1 1 1 ,要是某一列出现 0 0 0 ,则将列数组的对应位置置成 1 1 1 ,即可,于是空间复杂度降到了 O ( m + n ) O(m+n) O(m+n)。(其中 m m m 是矩阵的行数, n n n 是矩阵的列数。)
经过上述的分析,代码编写如下:

class Solution {
public:void setZeroes(vector<vector<int>>& matrix) {int row_len = matrix.size();int col_len = matrix[0].size();vector<int> row(row_len, 0), col(col_len, 0);for (int i = 0; i < row_len; i++) {for (int j = 0; j < col_len; j++) {if (matrix[i][j] == 0) {row[i] = 1;col[j] = 1;}}}for (int i = 0; i < row_len; i++) {for (int j = 0; j < col_len; j++) {if (row[i] == 1 || col[j] == 1) {matrix[i][j] = 0;}}}}
};

方法三:使用两个标记变量

我们觉得方法二的空间复杂度还是太高了,能不能就是再次进行优化,实际上是可以的,方法二中使用了两个一维数组来分别记录各行各列零出现的情况,实际上我们可以使用 m a t r i x matrix matrix 数组的第一第一列充当方法二中的两个数组,但是这里有个问题:在更新第一行和第一列的时候,由于其被修改,无法记录其原来是否被包含 0 0 0,因此我们需要额外使用两个标记变量分别记录第一行和第一列是否原本包含 0 0 0。代码如下:

class Solution {
public:void setZeroes(vector<vector<int>>& matrix) {int rows = matrix.size();int cols = matrix[0].size();// 标记第一列是否包含0bool firstColHasZero = false;// 标记第一行是否包含0bool firstRowHasZero = false;// 检查第一列是否有0for (int i = 0; i < rows; ++i) {if (matrix[i][0] == 0) {firstColHasZero = true;break;}}// 检查第一行是否有0for (int j = 0; j < cols; ++j) {if (matrix[0][j] == 0) {firstRowHasZero = true;break;}}// 使用第一行和第一列作为标记数组for (int i = 1; i < rows; ++i) {for (int j = 1; j < cols; ++j) {if (matrix[i][j] == 0) {matrix[i][0] = 0;matrix[0][j] = 0;}}}// 根据第一行和第一列的标记,设置其余位置为0for (int i = 1; i < rows; ++i) {for (int j = 1; j < cols; ++j) {if (matrix[i][0] == 0 || matrix[0][j] == 0) {matrix[i][j] = 0;}}}// 处理第一列if (firstColHasZero) {for (int i = 0; i < rows; ++i) {matrix[i][0] = 0;}}// 处理第一行if (firstRowHasZero) {for (int j = 0; j < cols; ++j) {matrix[0][j] = 0;}}}
};

方法四:使用一个标记变量

可以对方法三进一步优化,只使用一个标记变量记录第一列是否原本存在 0 0 0。这样,第一列的第一个元素即可以标记第一行是否出现 0 0 0
注意:为了防止每一列的第一个元素被提前更新,需要从最后一行开始,倒序地处理矩阵元素。
官方的解法如下:

class Solution {
public: void setZeroes(vector<vector<int>>& matrix) {int m = matrix.size();int n = matrix[0].size();int flag_col0 = false;for (int i = 0; i < m; i++) {if (!matrix[i][0]) {flag_col0 = true;}for (int j = 1; j < n; j++) {if (!matrix[i][j]) {matrix[i][0] = matrix[0][j] = 0;}}}for (int i = m - 1; i >= 0; i--) {for (int j = 1; j < n; j++) {if (!matrix[i][0] || !matrix[0][j]) {matrix[i][j] = 0;}}if (flag_col0) {matrix[i][0] = 0;}}}
};作者:力扣官方题解
链接:https://leetcode.cn/problems/set-matrix-zeroes/solutions/669901/ju-zhen-zhi-ling-by-leetcode-solution-9ll7/
来源:力扣(LeetCode)
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。

2、螺旋矩阵

题目链接:螺旋矩阵
题目描述:给你一个 m m m n n n 列的矩阵 m a t r i x matrix matrix ,请按照 顺时针螺旋顺序 ,返回矩阵中的所有元素。

解答

法一

使用四个变量分别记录当前的上下左右边界:
upper(上边界)down(下边界)left(左边界)right(右边界)
按照顺时针方向依次遍历四条边:
从左到右 遍历上边界
从上到下 遍历右边界
从右到左 遍历下边界
从下到上 遍历左边界
每次遍历完一条边后,相应地收缩边界。
当任意一对对边交叉时(如 upper > down 或 left > right),说明所有元素已遍历完毕,退出循环。

class Solution {
public:vector<int> spiralOrder(vector<vector<int>>& matrix) {vector<int> ans; // 存储最终结果的数组// 如果矩阵为空,直接返回空结果if (matrix.empty())return ans;int upper = 0;                   // 上边界int down = matrix.size() - 1;    // 下边界int left = 0;                    // 左边界int right = matrix[0].size() - 1; // 右边界while (1) {// 1️⃣ 从左往右:遍历最上面一行for (int i = left; i <= right; ++i)ans.push_back(matrix[upper][i]);++upper; // 上边界下移if (upper > down) break; // 判断是否越界,即是否还有剩余行// 2️⃣ 从上往下:遍历最右边一列for (int i = upper; i <= down; ++i)ans.push_back(matrix[i][right]);--right; // 右边界左移if (right < left) break; // 判断是否越界// 3️⃣ 从右往左:遍历最下面一行for (int i = right; i >= left; --i)ans.push_back(matrix[down][i]);--down; // 下边界上移if (down < upper) break; // 判断是否越界// 4️⃣ 从下往上:遍历最左边一列for (int i = down; i >= upper; --i)ans.push_back(matrix[i][left]);++left; // 左边界右移if (left > right) break; // 判断是否越界}return ans;}
};

官方有些好的解法(用到了线代中矩阵旋转的相关数学知识),我附上链接。

官解

3、旋转图像

题目链接:旋转图像
题目描述:给定一个 n × n 的二维矩阵 matrix 表示一个图像。请你将图像顺时针旋转 90 度。

你必须在 原地 旋转图像,这意味着你需要直接修改输入的二维矩阵。请不要 使用另一个矩阵来旋转图像。

解答

不太会,直接看解答吧(主要还是空间想象力不足,数学基础不是很牢固)

方法一:使用辅助数组

显然,旋转了,肯定是有些数据会被污染,因此不考虑题目的要求,首先引入一个辅助数组进行题目的求解,可以考虑使用一个新的数组 new_matrix ,存储的是 matrix 数组经过旋转之后的结果,最后 new_matrix 数组赋值完成后,在赋值给 原数组 matrix 即可完成题目。

因此此处主要需要考虑的问题是,原数组经过旋转之后应该填写到新数组的哪个位置上去。
官方给出来结论如下:
在这里插入图片描述
于是我们根据这个方法可以写出如下的代码:

class Solution {
public:void rotate(vector<vector<int>>& matrix) {int len = matrix.size();auto new_matrix = matrix;for (int i = 0; i < len; i++) {for (int j = 0; j < len; j++) {new_matrix[j][len - i - 1] = matrix[i][j];}}matrix = new_matrix;}
};

方法二:原地旋转

听说有些公司要求使用方法二进行题目的求解,于是需要详细的理解这部分的代码和编码思想。
题目要求的是在不使用额外内存空间的情况下进行矩阵的原地旋转。
观察方法一,其中的主要公式如下:
m a t r i x [ c o l ] [ n − r o w − 1 ] = m a t r i x [ r o w ] [ c o l ] matrix[col][n - row - 1]=matrix[row][col] matrix[col][nrow1]=matrix[row][col]
这样的话,原来在 m a t r i x [ c o l ] [ n − r o w − 1 ] matrix[col][n - row - 1] matrix[col][nrow1] 的数据就会被覆盖,这就是在方法一中不得不使用额外的数组的原因。
实际上,可以引入一个辅助变量 t e m p temp temp 来帮助我们进行操作。于是有如下的公式:
{ temp = matrix[row][col] matrix[row][col] = matrix[n - col - 1][row] matrix[n - col - 1][row] = matrix[n - row - 1][n - col - 1] matrix[n - row - 1][n - col - 1] = matrix[col][n - row - 1] matrix[col][n - row - 1] = temp \begin{cases} \text{temp} & = \text{matrix[row][col]} \\ \text{matrix[row][col]} & = \text{matrix[n - col - 1][row]} \\ \text{matrix[n - col - 1][row]} & = \text{matrix[n - row - 1][n - col - 1]} \\ \text{matrix[n - row - 1][n - col - 1]} & = \text{matrix[col][n - row - 1]} \\ \text{matrix[col][n - row - 1]} & = \text{temp} \end{cases} tempmatrix[row][col]matrix[n - col - 1][row]matrix[n - row - 1][n - col - 1]matrix[col][n - row - 1]=matrix[row][col]=matrix[n - col - 1][row]=matrix[n - row - 1][n - col - 1]=matrix[col][n - row - 1]=temp

下面就需要确定需要枚举的元素的个数了,有如下的规则:
n n n 为偶数时,我们需要枚举 n 2 4 = ( n 2 ) × ( n 2 ) \frac{n^2}{4} = \left(\frac{n}{2}\right) \times \left(\frac{n}{2}\right) 4n2=(2n)×(2n) 个位置,可以将该图形分为四块。

n n n 为奇数时,由于中心的位置经过旋转后位置不变,我们需要枚举 n 2 − 1 4 = ( n − 1 2 ) × ( n + 1 2 ) \frac{n^2 - 1}{4} = \left(\frac{n - 1}{2}\right) \times \left(\frac{n + 1}{2}\right) 4n21=(2n1)×(2n+1) 个位置,需要换一种划分的方式。

理解了上述原理之后,编码就非常的简单了,直接把上述列举出来的五个公式编码出来即可,只需要注意一下行列的取值。

class Solution {
public:void rotate(vector<vector<int>>& matrix) {int len = matrix.size();int temp;for (int row = 0; row < len / 2; row++) {for (int col = 0; col < (len + 1) / 2; col++) {temp = matrix[row][col];matrix[row][col] = matrix[len - col - 1][row];matrix[len - col - 1][row] =matrix[len - row - 1][len - col - 1];matrix[len - row - 1][len - col - 1] =matrix[col][len - row - 1];matrix[col][len - row - 1] = temp;}}}
};

方法三:用翻转代替旋转

这部分主要还是需要有空间想象力,官方的文字解法比较的抽象,反正就是两点,首先将原矩阵通过水平轴翻转得到中间矩阵,再将中间矩阵沿着主对角线翻转得到答案。还是直接上代码比较的直观,解释还是比较的抽象了(当然,要是知道关键等式其实数学推导也不是很难)。
关键公式:
m a t r i x [ r o w ] [ c o l ] → 水平轴翻转 m a t r i x [ n − r o w − 1 ] [ c o l ] matrix[row][col] \xrightarrow{\text{水平轴翻转}} matrix[n - row - 1][col] matrix[row][col]水平轴翻转 matrix[nrow1][col]
m a t r i x [ n − r o w − 1 ] [ c o l ] → 主对角线翻转 m a t r i x [ c o l ] [ n − r o w − 1 ] matrix[n-row-1][col] \xrightarrow{\text{主对角线翻转}} matrix[col][n - row - 1] matrix[nrow1][col]主对角线翻转 matrix[col][nrow1]
在这里插入图片描述

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; ++j) {swap(matrix[i][j], matrix[n - i - 1][j]);}}// 主对角线翻转for (int i = 0; i < n; ++i) {for (int j = 0; j < i; ++j) {swap(matrix[i][j], matrix[j][i]);}}}
};

4、搜索二维矩阵 II

题目链接:搜索二维矩阵 II
题目描述:编写一个高效的算法来搜索 m x n 矩阵 matrix 中的一个目标值 target 。该矩阵具有以下特性:
每行的元素从左到右升序排列。
每列的元素从上到下升序排列。

解答:

方法一:直接查找(暴力搜索)

实际上,暴力破解的复杂度也不是很高(时间复杂度为 O ( m n ) O(mn) O(mn) ),因此完全可以进行暴力破解。

class Solution {
public:bool searchMatrix(vector<vector<int>>& matrix, int target) {int m = matrix.size();int n = matrix[0].size();for (int i = 0; i < m; i++) {for (int j = 0; j < n; j++) {if (matrix[i][j] == target)return true;}}return false;}
};

官方的解法写的优美一点:

class Solution {
public:bool searchMatrix(vector<vector<int>>& matrix, int target) {for (const auto& row: matrix) {for (int element: row) {if (element == target) {return true;}}}return false;}
};作者:力扣官方题解
链接:https://leetcode.cn/problems/search-a-2d-matrix-ii/solutions/1062538/sou-suo-er-wei-ju-zhen-ii-by-leetcode-so-9hcx/
来源:力扣(LeetCode)
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。

方法二:二分查找

由于矩阵 m a t r i x matrix matrix 中每一行的元素都是升序排列的,因此我们可以对每一行都使用一次二分查找,判断 t a r g e t target target 是否在该行中,从而判断 t a r g e t target target 是否出现。或者对每一列都使用一次二分查找,判断 t a r g e t target target 是否在该列中,从而判断 t a r g e t target target 是否出现。

对每一行都使用一次二分查找

class Solution {
public:bool searchMatrix(vector<vector<int>>& matrix, int target) {int m = matrix.size();int n = matrix[0].size();for (int i = 0; i < m; i++) {int upper = 0;int down = n - 1;while (upper <= down) {int temp = (upper + down) / 2;if (matrix[i][temp] > target)down = temp - 1;else if (matrix[i][temp] < target)upper = temp + 1;elsereturn true;}}return false;}
};

对每一列都使用一次二分查找

class Solution {
public:bool searchMatrix(vector<vector<int>>& matrix, int target) {int m = matrix.size();int n = matrix[0].size();for (int j = 0; j < n; j++) {int left = 0;int right = m - 1;while (left <= right) {int temp = (left + right) / 2;if (matrix[temp][j] > target)right = temp - 1;else if (matrix[temp][j] < target)left = temp + 1;elsereturn true;}}return false;}
};

上述实际上是手搓了一个二分查找,官方直接调用的 C++ 中相关 STL ,更加简洁与通俗易懂。

class Solution {
public:bool searchMatrix(vector<vector<int>>& matrix, int target) {for (const auto& row: matrix) {auto it = lower_bound(row.begin(), row.end(), target);if (it != row.end() && *it == target) {return true;}}return false;}
};作者:力扣官方题解
链接:https://leetcode.cn/problems/search-a-2d-matrix-ii/solutions/1062538/sou-suo-er-wei-ju-zhen-ii-by-leetcode-so-9hcx/
来源:力扣(LeetCode)
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。

方法三:Z 字形查找

其实我初步的想法就是找到矩阵中间位置(或靠近中间位置的地方,然后判断与 t a r g e t target target 的大小,然后进行类似于二分查找的过程,但是我这样发现实际上进行不下去,因为这样就相当于将原有的矩阵划分成了四个区域,左上角的一定小于该值,右下角的一定大于该值,但是剩下的左下角和右上角的区域就无法判断了,因此这种方法就实行不了了,因为不知道判断后究竟是向哪个反向移动。)

看了一下官方的题解,可以从右上角的位置出发,判断
发现这种方法确实可以,代码也通俗易懂,也知道每次判断后究竟是向哪个位置移动。

class Solution {
public:bool searchMatrix(vector<vector<int>>& matrix, int target) {int m = matrix.size();int n = matrix[0].size();int x = 0, y = n - 1;while (x < m && y >= 0) {if (matrix[x][y] == target) {return true;} else if (matrix[x][y] > target) {y--;} else {x++;}}return false;}
};

由于矩阵具有对称性,因此要是右上角可以的话,左下角也应该是可以的,于是写出类似的代码如下:

class Solution {
public:bool searchMatrix(vector<vector<int>>& matrix, int target) {int m = matrix.size();int n = matrix[0].size();int x = m - 1, y = 0;while (x >= 0 && y < n) {if (matrix[x][y] == target) {return true;} else if (matrix[x][y] > target) {x--;} else {y++;}}return false;}
};

相关文章:

  • 图像数据如何表示为概率单纯形
  • 刷leetcode hot100--矩阵6/1
  • 408《数据结构》——第二章:线性表
  • 【金融基础学习】债券回购方式
  • 【金融基础学习】债券市场与债券价值分析
  • Maven(黑马)
  • 数论——质数和合数及求质数
  • Flask中关于app.url_map属性的用法
  • 力扣HOT100之动态规划:416. 分割等和子集
  • 2025年目前最新版本Android Studio自定义xml预览的屏幕分辨率
  • flutter 构建报错Unsupported class file major version 65
  • Scratch节日 | 六一儿童节射击游戏
  • 深度学习---负样本训练
  • 深度学习篇---人脸识别中的face-recognition库和深度学习
  • 科研学习|科研软件——激活后的Origin导出图时突然出现了demo水印
  • Python数学可视化——坐标系与变换
  • ssm 学习笔记day03
  • 如何利用自动生成文档工具打造出色的技术文档
  • Vue 核心技术与实战智慧商城项目Day08-10
  • 打打基础 | 从翻转链表到寄存器、汇编与内存
  • 营销型企业网站的建设方案/网络推广客服好做吗
  • 云梦做网站的优势/做网络推广一般是什么专业
  • 网站制作 网站/迅雷磁力链bt磁力种子
  • 厚街网站仿做/云优客seo排名公司
  • 沧州哪里有做网站的公司4000-/百度拉新推广平台
  • 可以用足球做的游戏视频网站/站长查询域名