Leetcode矩阵-day7
记录自己刷力扣备战秋招的刷题笔记❤️
------wosz
矩阵
矩阵简单来说就是二维数组

在C++中可以通过二维数组的形式进行创建:
cpp
vector<vector<int>>matrix
在刷题中,对于矩阵的操作主要有:
-
创建矩阵直接使用 vector 进行创建即可
vector<vector<int>>matrix -
遍历矩阵,两层 for 循环进行遍历
cppfor (int i = 0; i < m; i++) { for (int j = 0; j < n; j++) { cout << matrix[i][j] << " "; } } -
矩阵转置,让行转换成列,列转换成行
cppres[j][i] = matrix[i][j];
先把最基础的掌握了,再刷题中我再总结吧。
矩阵置零
给定一个 m x n 的矩阵,如果一个元素为 0 ,则将其所在行和列的所有元素都设为 0 。请使用 原地 算法**。**

**输入:**matrix = [[1,1,1],[1,0,1],[1,1,1]]
输出:[[1,0,1],[0,0,0],[1,0,1]]
自己的题解
cpp
class Solution {
public:
void setZeroes(vector<vector<int>>& matrix) {
int m=matrix.size(); //行
int n=matrix[0].size(); //列
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;
}
}
}
//处理
for(int i=0;i<m;i++)
{
for(int j=0;j<n;j++)
{
if (row[i] == 1 || col[j] == 1) //通过那个行列标记进行计算
{
matrix[i][j] = 0;
}
}
}
}
};
我的思路就是直接进行遍历,每一个元素进行遍历,然后在内层直接进行判断,然后去将我们的标记数组置1进行标记,之后再进行一次遍历通过行列标记进行置0操作即可,感觉确实是暴力做法。
官方题解
cpp
class Solution {
public:
void setZeroes(vector<vector<int>>& matrix) {
int m = matrix.size();
int n = matrix[0].size();
vector<int> row(m), col(n);
for (int i = 0; i < m; i++) {
for (int j = 0; j < n; j++) {
if (!matrix[i][j]) {
row[i] = col[j] = true;
}
}
}
for (int i = 0; i < m; i++) {
for (int j = 0; j < n; j++) {
if (row[i] || col[j]) {
matrix[i][j] = 0;
}
}
}
}
};
官方的思路也是通过标记数组的形式先对我们的数组进行遍历标记,然后利用标记数组进行那个再次遍历置0即可。
螺旋矩阵
给你一个 m 行 n 列的矩阵 matrix ,请按照 顺时针螺旋顺序 ,返回矩阵中的所有元素。

**输入:**matrix = [[1,2,3],[4,5,6],[7,8,9]]
输出:[1,2,3,6,9,8,7,4,5]
自己的题解
cpp
class Solution {
public:
vector<int> spiralOrder(vector<vector<int>>& matrix) {
vector<int> tag;
if (matrix.empty() || matrix[0].empty()) return tag;
int top = 0;
int left = 0;
int botton = matrix.size() - 1;
int right = matrix[0].size() - 1;
while (top <= botton && left <= right) {
// 向右
for (int i = left; i <= right; i++) {
tag.push_back(matrix[top][i]);
}
top++;
// 向下
for (int i = top; i <= botton; i++) {
tag.push_back(matrix[i][right]);
}
right--;
// 向左
if (top <= botton) {
for (int i = right; i >= left; i--) {
tag.push_back(matrix[botton][i]);
}
botton--;
}
// 向上
if (left <= right) {
for (int i = botton; i >= top; i--) {
tag.push_back(matrix[i][left]);
}
left++;
}
}
return tag;
}
};
我的思路是,我看到这个我就只找到一个规律就是我们的这个走向都是确定的,1.首先就是这个走向时固定的,段数是4段 2.然后这个的每一段的走向也固定的,向右,向下,向左,向上。但是怎么连接起来呢?最简单的就是不断收缩进行每次移动之后进行收缩。
官方题解
cpp
class Solution {
public:
vector<int> spiralOrder(vector<vector<int>>& matrix) {
if (matrix.size() == 0 || matrix[0].size() == 0) {
return {};
}
int rows = matrix.size(), columns = matrix[0].size();
vector<int> order;
int left = 0, right = columns - 1, top = 0, bottom = rows - 1;
while (left <= right && top <= bottom) {
for (int column = left; column <= right; column++) {
order.push_back(matrix[top][column]);
}
for (int row = top + 1; row <= bottom; row++) {
order.push_back(matrix[row][right]);
}
if (left < right && top < bottom) {
for (int column = right - 1; column > left; column--) {
order.push_back(matrix[bottom][column]);
}
for (int row = bottom; row > top; row--) {
order.push_back(matrix[row][left]);
}
}
left++;
right--;
top++;
bottom--;
}
return order;
}
};
官方的两种题解,这个方法就是和我们的做法类似就是进行按照行进路线收缩。
旋转图像
给定一个 n × n 的二维矩阵 matrix 表示一个图像。请你将图像顺时针旋转 90 度。
你必须在**原地** 旋转图像,这意味着你需要直接修改输入的二维矩阵。请不要 使用另一个矩阵来旋转图像。

**输入:**matrix = [[1,2,3],[4,5,6],[7,8,9]]
输出:[[7,4,1],[8,5,2],[9,6,3]]
自己的题解
cpp
class Solution {
public:
void rotate(vector<vector<int>>& matrix) {
int n = matrix.size();
for (int i = 0; i < n / 2; i++) { // 控制层数
for (int j = i; j < n - 1 - i; 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;
}
}
}
};
我的思路就是既然不能新开矩阵进行变化,那一定存在规律,可以发现**(m,n)→(n,size-1-m)** 我们就可以利用这个规律来进行变化。就根据这个来让四个位置的进行轮换就行。
cpp
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;
还有一种想法就是我直接先进行转置 然后再进行列平移就行,转置直接就坐标交换比较简单方便。
这个方法的遍历思路就是分为几个层,外层和内层进行变化,然后每一层又要依次减少一个头和尾。
- 列平移就直接每一行进行反转就行
cpp
class Solution {
public:
void rotate(vector<vector<int>>& matrix) {
int n = matrix.size();
for (int i = 0; i < n; i++) { //转置
for (int j = i ; j < n; j++) {
swap(matrix[i][j], matrix[j][i]);
}
}
for (int i = 0; i < n; i++) { //每一行反转
reverse(matrix[i].begin(), matrix[i].end());
}
}
};
我做这个题的时候明显感觉到矩阵中的难点就是对于遍历的时候那个遍历的范围。
官方题解
cpp
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 - j - 1][i];
matrix[n - j - 1][i] = matrix[n - i - 1][n - j - 1];
matrix[n - i - 1][n - j - 1] = matrix[j][n - i - 1];
matrix[j][n - i - 1] = temp;
}
}
}
};
官方的思路也是通过找规律发现这几个中的一个坐标规律和联系来进行的解题。
搜索二维矩阵II
编写一个高效的算法来搜索 *m* x *n* 矩阵 matrix 中的一个目标值 target 。该矩阵具有以下特性:
- 每行的元素从左到右升序排列。
- 每列的元素从上到下升序排列。

**输入:**matrix = [[1,4,7,11,15],[2,5,8,12,19],[3,6,9,16,22],[10,13,14,17,24],[18,21,23,26,30]], target = 5
**输出:**true
自己的题解
cpp
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;
}
};
我的思路是不管了先暴力一手,然后我们再进行根据这个规律进行找更优的方法。
让AI大哥说了一下这题经典做法是:从右上角开始找 ,时间复杂度 O(m+n)。
-
如果当前值
>target,那这一列下面都更大,这一列不用看了,往左走 -
如果当前值
<target,那这一行左边都更小,这一行不用看了,往下走 -
如果相等,直接返回 true
cpp
class Solution {
public:
bool searchMatrix(vector<vector<int>>& matrix, int target) {
int m=matrix.size();
int n=matrix[0].size();
//从右上角开始查找
int i=0,j=n-1;
while(i<m&&j>=0)
{
if(matrix[i][j]==target)
{
return true;
}else if(matrix[i][j]<target) //行不用看了
{
i++;
}else //大于目标的话就是这一列不用看了
{
j--;
}
}
return false;
}
};
官方题解
cpp
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;
}
};
官方也有上面的从右上角开始查找,我们学习一下官方的二分查找法。就是每一行都做一次二分查找。介绍一下C++使用二分查找法的API。
二分查找
为什么这一题可以对每一行做二分查找
这一题已经告诉我们:每一行都是从左到右升序排列 ,所以我们可以把每一行都看成一个有序数组。既然是有序数组,就可以在每一行里用一次二分查找去找 target。
C++ 中常用的二分查找 API
在 C++ 里,二分查找相关 API 主要在 #include <algorithm> 里面,最常用的有这几个:
-
lower_bound(begin, end, target)返回第一个大于等于
target的位置 -
upper_bound(begin, end, target)返回第一个大于
target的位置 -
binary_search(begin, end, target)直接返回
true或false,表示目标值是否存在
这题官方题解使用的是 lower_bound,因为它不只是告诉你"有没有",还会返回一个迭代器位置 ,这样我们就能继续判断这个位置上的值是不是正好等于 target。
lower_bound 怎么理解
假设有一个有序数组:
cpp
vector<int> row = {1, 4, 7, 11, 15};
如果我们写:
cpp
auto it = lower_bound(row.begin(), row.end(), 7);
那么 it 就会指向值为 7 的位置。
如果写:
cpp
auto it = lower_bound(row.begin(), row.end(), 8);
因为数组里没有 8,它会返回第一个大于等于 8 的位置 ,也就是指向 11。
所以这一题里要这样判断:
cpp
auto it = lower_bound(row.begin(), row.end(), target);
if (it != row.end() && *it == target) {
return true;
}
这里分成两步理解:
it != row.end():先判断有没有越界*it == target:再判断当前位置是不是真的等于目标值
为什么不能只判断 it != row.end()
因为 lower_bound 找到的是第一个大于等于 target 的位置 ,不一定就是 target 本身。