第九天
542-01矩阵
乍一看题,想到最暴力的解法,就是对于每一个元素都去遍历离它最近的零,得出距离,当然这里是针对1来说,距离则是
abs(r1-r2)+abs(c1-c2)//r1 c1 和 r2 c2表示两个元素的坐标
当然了都中级题目了暴力只能想想了,做题要讲究技巧
思路1:BFS
首先对于所有的零来说,他们到最近零的距离就是到自己的距离所以就是零,则初始化的时候所有的零可以置为0;而对于1来说,我们可以想到,查看它的四周是否存在零,如果存在则它距离最近的零的距离则为0+1=1,如果不存在呢?
由于每个位置记录的是表示当前位置到最近零的位置,
如果旁边的元素非零,且前面被修改过,
则可以认为当前元素到最近0的距离应该是当前元素到四周位置的值+1的值
即当前位置的四周元素的值+1的最小值,就是当前元素到最近零的距离
那么从另一个角度来说,每一个被更新过的值都可以尝试去更新四周的值
使其经过自己到达零的距离与其本来存储的值进行比较,得到最小值
举个例子
0 0 0
0 1 0
1 1 1
当我们更新到第三行第二列的1时,四周的值都为1,说明从四周到达零的最短距离为1,则从该元素位置到达距离最短零的距离应该为1+1 = 2
则我们可以通过广度搜索优先来模拟这个过程,而多个零初始化也叫做多源最短路径问题
- 遍历将所有的0的行列坐标入队
- 取出一个行列坐标,更新其四周的值
- 将四周的行列坐标入队
实际操作时可以有两种考虑:
- 除了零以外,其他的元素考虑赋一个很大的值来表示该点并没有更新过,则进行更新时,这类的值不会成为最小值被更新。
- 开辟一个visited数组,将没有更新过的位置置0,仅将未更新过的位置进行更新和标记。这里可以直接更新四周未标记过的元素的值。
写法一:
class Solution {
public:
vector<vector<int>> updateMatrix(vector<vector<int>>& mat) {
queue<pair<int, int>> q;
//将所有的零加入队列
int row = mat.size();
int col = mat[0].size();
vector<vector<int>> ans(row, vector<int>(col, INT_MAX));
for(int i=0;i<row;i++)
{
for(int j=0;j<col;j++)
{
if(mat[i][j]==0)
{
q.push({i, j});
ans[i][j] = 0;//初始化零
}
}
}
int rows[4] = {0, 0, 1, -1};
int cols[4] = {1, -1, 0, 0};
while(!q.empty())
{
int oldr = q.front().first;
int oldc = q.front().second;
q.pop();
for(int i=0;i<4;i++)
{
int newr = oldr+rows[i];
int newc = oldc+cols[i];
if(newr>=0&&newr<row&&newc>=0&&newc<col&&ans[newr][newc]!=0)
{
if(ans[oldr][oldc]+1<ans[newr][newc])//这里需要比较
{
ans[newr][newc] = ans[oldr][oldc]+1;
q.push({newr, newc});
}
}
}
}
return ans;
}
};
写法二:
class Solution {
private:
static constexpr int dirs[4][2] = {{-1, 0}, {1, 0}, {0, -1}, {0, 1}};
public:
vector<vector<int>> updateMatrix(vector<vector<int>>& matrix) {
int m = matrix.size(), n = matrix[0].size();
vector<vector<int>> dist(m, vector<int>(n));
vector<vector<int>> seen(m, vector<int>(n));
queue<pair<int, int>> q;
// 将所有的 0 添加进初始队列中
for (int i = 0; i < m; ++i) {
for (int j = 0; j < n; ++j) {
if (matrix[i][j] == 0) {
q.emplace(i, j);
seen[i][j] = 1;//表示已更新
}
}
}
// 广度优先搜索
while (!q.empty()) {
auto [i, j] = q.front();
q.pop();
for (int d = 0; d < 4; ++d) {
int ni = i + dirs[d][0];
int nj = j + dirs[d][1];
if (ni >= 0 && ni < m && nj >= 0 && nj < n && !seen[ni][nj])//没有被更新过
{
dist[ni][nj] = dist[i][j] + 1;//则直接更新
q.emplace(ni, nj);
seen[ni][nj] = 1;
}
}
}
return dist;
}
};
思路2:DP动态规划
从BFS中我们可以看到每一个位置的更新依赖于其四周位置的值,那么我们可以看作当前状态的更新需要前面四个状态的信息。
解决坐标(r, c)到零的最短距离的问题
需要解决(r+1, c)到零的最短路径的问题
需要解决(r-1, c)到零的最短路径的问题
需要解决(r, c+1)到零的最短路径的问题
需要解决(r, c-1)到零的最短路径的问题
则有如下状态转换方程
f(i, j) = 0 当位置(i, j)上的元素为0
f(i,j)= 1+min(f(i−1,j),f(i,j−1),f(i+1,j),f(i,j+1)) 当位置(i, j)上的元素为1
由于存在四个方向,所有我们应该从四个角开始,分别dp一遍,取最小值,但可证明,取左上和右下、或者左下和右上,两次遍历便可以得到最小值
class Solution {
public:
vector<vector<int>> updateMatrix(vector<vector<int>>& mat) {
int row = mat.size();
int col = mat[0].size();
vector<vector<int>> ans(row, vector<int>(col, INT_MAX/2));
for(int i=0;i<row;i++)
{
for(int j=0;j<col;j++)
{
if(mat[i][j]==0)
ans[i][j]=0;
}
}
//从上到下,从左到右的动态规划
for(int i=0;i<row;i++)
{
for(int j=0;j<col;j++)
{
if(i-1>=0)
ans[i][j] = min(ans[i][j], ans[i-1][j]+1);
if(j-1>=0)
ans[i][j] = min(ans[i][j], ans[i][j-1]+1);
}
}
for(int i=row-1;i>=0;i--)
{
for(int j=col-1;j>=0;j--)
{
if(i+1<row)
ans[i][j] = min(ans[i][j], ans[i+1][j]+1);
if(j+1<col)
ans[i][j] = min(ans[i][j], ans[i][j+1]+1);
}
}
return ans;
}
};
994-腐烂的橘子
有了上面一题的基础后,看到这道题就有思路了
意思是标记为1的格子每次可以向四周扩散,将周围的1变为2,求几轮后grid中没有1
思路:同样是BFS,利用队列进行模拟
首先遍历一遍grid,将所有的2的行列加入队列中
然后出队列,观察其四周是否有1,有则将其变为2,然后行列入队
思路比较简单,但是有几个需要注意的点:
- 如何统计轮数?——将上一轮入队的行列进行统计,需要设置内循环和两个遍历
(具体看代码) - 全0的情况?——说明刚开始就没有新鲜的橘子,直接返回0
- 全1的情况?——说明没有腐烂的橘子,则无法将新鲜橘子变为腐烂橘子,返回-1
- 仅有01的情况?——同全1的情况
- 有012但是最后存在1没有变为2?——返回-1(具体操作可以最后遍历一遍grid查看是否还有1,或者和代码中一样,利用刚开始的统计信息进行判断)
class Solution {
public:
int orangesRotting(vector<vector<int>>& grid) {
int ans = 0;
int row = grid.size();
int col = grid[0].size();
queue<pair<int, int>> q;
int ret = 0;//每一轮腐烂的橘子数
//统计012的个数——针对全0,全1,只有1,0的情况
int t[3]={0, 0, 0};
for(int i=0;i<row;i++)
{
for(int j=0;j<col;j++)
{
if(grid[i][j]==0)
t[0]++;
else if(grid[i][j]==1)
t[1]++;
else if(grid[i][j]==2)
{
t[2]++;
q.push({i, j});
ret++;
}
}
}
//全0则表示刚开始就没有新鲜橘子
if(t[1]==0&&t[2]==0)
return ans;
if(t[2]==0)//只有01 或全1的情况,无法将新鲜橘子变腐烂
return -1;
int rows[4] = {0, 0, 1, -1};
int cols[4] = {1, -1, 0, 0};
while(!q.empty())
{
int cnt = 0;
while(ret--)
{
int oldr = q.front().first;
int oldc = q.front().second;
q.pop();
for(int i=0;i<4;i++)
{
int newr = oldr+rows[i];
int newc = oldc+cols[i];
if(newr>=0&&newr<row&&newc>=0&&newc<col&&grid[newr][newc]==1)//从1->2
{
grid[newr][newc] = 2;
q.push({newr, newc});
cnt++;//计算该轮被感染的橘子数
t[1]--;//新鲜橘子变成腐烂橘子
}
}
}
ans++;//一轮结束
ret = cnt;//下一轮应该出队的数量
}
if(t[1]!=0)//说明新鲜橘子无法全部腐烂
return -1;
return ans-1;//多计算一轮
}
};
第十天
21-合并两个有序链表
206-翻转链表
详细题解可见以下博客:LeetCode数据结构四-链表