💁♂️个人主页:进击的荆棘
👇作者其它专栏:
目录
1.综合练习相关题解
2.floodfill算法
3.记忆化搜索
2.floodfill算法
2.1图像渲染
算法思路:
可以利用【深搜】或【宽搜】,遍历到与该点相连的所有【像素相同的点】,然后将其修改成指定的像素即可。
递归函数设计:
●参数:
a.原始矩阵;
b.当前所在的位置;
c.需要修改成的颜色。
●函数体:
a.先将该位置的颜色改成指定颜色(因为进入递归的条件为是需要修改的位置);
b.遍历四个方向上的位置:
▢若当前位置合法,并且与初始颜色相同,进入递归。
cpp
class Solution {
int target;
public:
vector<vector<int>> floodFill(vector<vector<int>>& image, int sr, int sc, int color) {
if(image[sr][sc]==color) return image;
target=image[sr][sc];
dfs(image,sr,sc,color);
return image;
}
int dx[4]={1,-1,0,0};
int dy[4]={0,0,1,-1};
void dfs(vector<vector<int>>& image,int i,int j,int color){
image[i][j]=color;
for(int k=0;k<4;k++){
int x=i+dx[k],y=j+dy[k];
if(x>=0&&x<image.size()&&y>=0&&y<image[0].size()&&image[x][y]==target){
dfs(image,x,y,color);
}
}
}
};
2.2岛屿数量
算法思路:
遍历整个矩阵,每次找到【一块陆地】的时候:
●说明找到【一个岛屿】,记录到最终结果ret里面;
●并且将与这块陆地相连的所有陆地,也就是这块【岛屿】,全部【变成海洋】。这样的话,下次遍历到这块岛屿的时候,它【已经是海洋】了,不会影响最终结果。(或另创建一个check数组,标记走过的位置);
●其中【变成海洋】的操作,可以利用【深搜】和宽搜】解决,就是 2.1图像渲染这道题。
这样,当遍历完全部矩阵的时候,ret存的就是最终结果。
算法流程(dfs):
1.初始化ret=0,记录目前找到的岛屿数量;
2.双重循环遍历二维数组,每当遇到一块陆地,标记这是一个新的岛屿,然后将这块陆地相连的陆地全部变成海洋。
递归函数的设计:
1.把当前格子标记成水;
2.向上、下、左、右四个方向寻找陆地,只有在下标位置合理的情况下,才会进入递归:
a.下一个位置的坐标合理;
b.并且下一个位置是陆地
cpp
class Solution {
bool check[300][300];
int ret=0;
public:
int numIslands(vector<vector<char>>& grid) {
for(int i=0;i<grid.size();i++){
for(int j=0;j<grid[0].size();j++){
if(grid[i][j]=='1'&&!check[i][j]){
dfs(grid,i,j);
ret++;
}
}
}
return ret;
}
int dx[4]={0,0,1,-1};
int dy[4]={1,-1,0,0};
void dfs(vector<vector<char>>& grid,int i,int j){
if(grid[i][j]=='0') return ;
for(int k=0;k<4;k++){
int x=i+dx[k],y=j+dy[k];
if(x>=0&&x<grid.size()&&y>=0&&y<grid[0].size()&&grid[x][y]=='1'&&!check[x][y]){
check[x][y]=true;
dfs(grid,x,y);
}
}
}
};
2.3岛屿的最大面积
算法流程:
●主函数内:
a.遍历整个数组,发现一块没有遍历到的土地后,就用dfs,将与这块土地相连的岛屿的面积求出来;
b.然后将面积更新到最终结果ret中。
●深搜函数dfs中:
a.能够进到dfs函数中,说明是一个没遍历到的位置;
b.标记一下已经遍历过,设置一个变量cnt=1(当前这个位置的面积为1),记录最终的面积;
c.上下左右遍历四个位置:
▢若找到一块没有遍历到的土地,就将与这块土地相连的岛屿面积累积加到cnt上;
d.循环结束后,cnt中存的就是整块岛屿的面积,返回即可。
cpp
class Solution {
bool check[50][50];
int cnt=0;
public:
int maxAreaOfIsland(vector<vector<int>>& grid) {
int ret=0;
for(int i=0;i<grid.size();i++){
for(int j=0;j<grid[0].size();j++){
if(grid[i][j]==1){
cnt=0;
dfs(grid,i,j);
ret=max(ret,cnt);
}
}
}
return ret;
}
int dx[4]={1,-1,0,0};
int dy[4]={0,0,1,-1};
void dfs(vector<vector<int>>& grid,int i,int j){
cnt++;
check[i][j]=true;
for(int k=0;k<4;k++){
int x=i+dx[k],y=j+dy[k];
if(x>=0&&x<grid.size()&&y>=0&&y<grid[0].size()&&grid[x][y]==1&&!check[x][y]){
dfs(grid,x,y);
}
}
}
};
2.4被围绕的区域
算法思路:
正难则反。
可以先利用dfs将与边缘相连的'O'区域做上标记,然后重新遍历矩阵,将没有标记过的'O'修改为'X'即可。
cpp
class Solution {
//直接去修改被围绕的'O'会比较困难,可以先将与边界相邻的'O'修改,那么剩下的'O'一定都是被围绕的
public:
void solve(vector<vector<char>>& board) {
for(int i=0;i<board.size();i++){
if(board[i][0]=='O') dfs(board,i,0);
if(board[i][board[0].size()-1]=='O') dfs(board,i,board[0].size()-1);
}
for(int j=0;j<board[0].size();j++){
if(board[0][j]=='O') dfs(board,0,j);
if(board[board.size()-1][j]=='O') dfs(board,board.size()-1,j);
}
for(int i=0;i<board.size();i++){
for(int j=0;j<board[0].size();j++){
if(board[i][j]=='.')
board[i][j]='O';
else if(board[i][j]=='O')
board[i][j]='X';
}
}
}
int dx[4]={1,-1,0,0};
int dy[4]={0,0,1,-1};
void dfs(vector<vector<char>>& board,int i,int j){
board[i][j]='.';
for(int k=0;k<4;k++){
int x=i+dx[k],y=j+dy[k];
if(x>=0&&x<board.size()&&y>=0&&y<board[0].size()&&board[x][y]=='O'){
dfs(board,x,y);
}
}
}
};
2.5太平洋大西洋水流问题
算法思路:
正难则反。
若直接去判断某一个位置是否既能到大西洋也能到太平洋,会重复遍历很多路径。
可以反着来,从大西洋沿岸开始反向dfs,这样就能找出那些点可以流向大西洋;同理,从太平洋沿岸也反向dfs,这样就能找出那些点可以流向太平洋。那么,被标记两次的点,就是要找的结果。
cpp
class Solution {
//直接找会有很多重复的路径,逆向思维,从洋到坐标,看水能逆流到哪里,最后两个洋重叠的区间就是结果
vector<vector<int>> ret;
public:
vector<vector<int>> pacificAtlantic(vector<vector<int>>& heights) {
vector<vector<bool>> check1(heights.size(),vector<bool>(heights[0].size()));;
vector<vector<bool>> check2(heights.size(),vector<bool>(heights[0].size()));
//先从太平洋开始找
for(int j=0;j<heights[0].size();j++){
dfs(heights,0,j,check1);
}
for(int i=0;i<heights.size();i++){
dfs(heights,i,0,check1);
}
//再从大西洋开始找
for(int i=0;i<heights.size();i++){
dfs(heights,i,heights[0].size()-1,check2);
}
for(int j=0;j<heights[0].size();j++){
dfs(heights,heights.size()-1,j,check2);
}
for(int i=0;i<heights.size();i++){
for(int j=0;j<heights[0].size();j++){
if(check1[i][j]&&check2[i][j])
ret.push_back({i,j});
}
}
return ret;
}
int dx[4]={1,-1,0,0};
int dy[4]={0,0,1,-1};
void dfs(vector<vector<int>>& heights,int i,int j,vector<vector<bool>>& check){
check[i][j]=true;
for(int k=0;k<4;k++){
int x=i+dx[k],y=j+dy[k];
if(x>=0&&x<heights.size()&&y>=0&&y<heights[0].size()&&heights[i][j]<=heights[x][y]&&!check[x][y]){
dfs(heights,x,y,check);
}
}
}
};
2.6扫雷游戏
模拟类型的dfs题目。
首先要搞懂题目要求,即游戏规则。
从题目所给的点位置开始,根据游戏规则,来一次dfs即可。
cpp
class Solution {
public:
vector<vector<char>> updateBoard(vector<vector<char>>& board, vector<int>& click) {
//若是地雷
if(board[click[0]][click[1]]=='M')
board[click[0]][click[1]]='X';
else
dfs(board,click[0],click[1]);
return board;
}
int dx[8]={1,-1,0,0,-1,-1,1,1};
int dy[8]={0,0,1,-1,-1,1,-1,1};
int check(vector<vector<char>>& board,int i,int j){
int cnt=0;
for(int k=0;k<8;k++){
int x=i+dx[k],y=j+dy[k];
if(x>=0&&x<board.size()&&y>=0&&y<board[0].size()&&board[x][y]=='M')
cnt++;
}
return cnt;
}
void dfs(vector<vector<char>>& board,int i,int j){
//先查看该位置周围是否有地雷
int cnt=check(board,i,j);
if(cnt!=0){
board[i][j]='0'+cnt;
return ;
}
else{
board[i][j]='B';
for(int k=0;k<8;k++){
int x=i+dx[k],y=j+dy[k];
if(x>=0&&x<board.size()&&y>=0&&y<board[0].size()&&board[x][y]=='E')
dfs(board,x,y);
}
}
}
};
2.7衣橱整理
算法流程:
●递归函数设计:
a.参数:当前所在位置[i,j],行走的边界[m,n],坐标数位之和的边界k;
b.递归出口:
i.[i,j]的坐标不合法,即已经超出能走的范围;
ii.[i,j]位置已经走过了(因此需要创建一个全局变量bool vis[101][101],来标记当前位置是否走过);
iii.[i,j]坐标的数位之和大于k;
上述情况的任一种都是递归出口。
c.函数体内部:
i.若这个坐标是合法的,就将全局变量ret++;
ii.然后标记一下[i,j]位置是否已经遍历过;
iii.然后去[i,j]位置的上下左右四个方向看看。
●辅助函数:
a.检测坐标[i,j]是否合法;
b.计算出i,j的数位之和,然后与k作比较即可。
●主函数:
a.调用递归函数,从[0,0]点出发。
●辅助的全局变量:
a.二维数组bool vis[101][101]:标记[i,j]位置是否已经遍历过;
b.变量ret:记录一共到达多少个合法的位置。
c.上下左右的四个坐标变换。
cpp
class Solution {
bool vis[100][100];
int ret=0;
public:
int wardrobeFinishing(int m, int n, int cnt) {
dfs(m,n,cnt,0,0);
return ret;
}
int dx[2]={0,1};
int dy[2]={1,0};
bool Check(int x,int y,int cnt){
int ret=0;
while(x){
ret+=x%10;
x/=10;
}
while(y){
ret+=y%10;
y/=10;
}
return ret<=cnt;
}
void dfs(int m,int n,int cnt,int i,int j){
if(i==m||n==j) return ;
vis[i][j]=true;
ret++;
for(int k=0;k<2;k++){
int x=i+dx[k],y=j+dy[k];
if(x>=0&&x<m&&y>=0&&y<n&&!vis[x][y]&&Check(x,y,cnt)){
dfs(m,n,cnt,x,y);
}
}
}
};
3.记忆化搜索
3.1斐波那契数
算法思路(暴搜->记忆化搜索->动态规划):
暴搜:
a.递归含义:给dfs一个使命,给它一个数n,返回第n个斐波那契数的值;
b.函数体:斐波那契数的递推公式;
c.递归出口:当n==0或n==1时,不用套公式。
记忆化搜索:
a.加上一个备忘录;
b.每次进入递归的时候,去备忘录里面看看;
c.每次返回的时候,将结果加入到备忘录里面。
动态规划:
a.递归含义->状态表示;
b.函数体->状态转移方程;
c.递归出口->初始化。
cpp
//法一:记忆化搜索
class Solution {
int memo[31];//memory备忘录
public:
int fib(int n) {
memset(memo,-1,sizeof memo);
return dfs(n);
}
//记忆化搜索
int dfs(int n){
if(memo[n]!=-1)
return memo[n];//直接去备忘录里取值
if(n==0||n==1){
memo[n]=n;//记录到备忘录中
return n;
}
memo[n]=dfs(n-1)+dfs(n-2);//记录到备忘录中
return memo[n];
}
};
//法二:动态规划
class Solution {
int dp[31];
public:
int fib(int n) {
//动态规划
dp[0]=0;dp[1]=1;
for(int i=2;i<=n;i++){
dp[i]=dp[i-1]+dp[i-2];
}
return dp[n];
}
};
3.2不同路径
算法思路(暴搜->记忆化搜索->动态规划):
暴搜:
a.递归含义:给dfs一个使命,给它一个下标n,返回从[0,0]位置走到[i,j]位置一共有多少种方法;
b.函数体:只要知道到达上面位置的方法数以及到达左边位置的方法数,然后累加起来即可;
c.递归出口:当下标越界的时候返回0;当位于起点时,返回1。
记忆化搜索:
a.加上一个备忘录;
b.每次进入递归的时候,去备忘录里面看看;
c.每次返回的时候,将结果加入到备忘录里面。
动态规划:
a.递归含义->状态表示;
b.函数体->状态转移方程;
c.递归出口->初始化。
cpp
//法一:记忆化搜索:逆向思维,要到达(m,n)有两个方向,(m-1,n)和(m,n-1)即向下和向右
class Solution {
int memo[101][101];
int ret=0;
public:
int uniquePaths(int m, int n) {
memset(memo,-1,sizeof memo);
return dfs(m,n);
}
int dx[2]={0,1};
int dy[2]={1,0};
int dfs(int i,int j){
if(memo[i][j]!=-1){
return memo[i][j];
}
//越界
if(i==0||j==0) return 0;
if(i==1&&j==1) return 1;
memo[i][j]=dfs(i,j-1)+dfs(i-1,j);
return memo[i][j];
}
};
//法二:动态规划
class Solution {
public:
int uniquePaths(int m, int n) {
vector<vector<int>> dp(m+1,vector<int>(n+1));
//初始化
dp[1][1]=1;
for(int i=1;i<=m;i++){
for(int j=1;j<=n;j++){
if(i==1&&j==1)
continue;
dp[i][j]=dp[i-1][j]+dp[i][j-1];
}
}
return dp[m][n];
}
};
3.3最长递增子序列
算法思路(暴搜->记忆化搜索->动态规划):
暴搜:
a.递归含义:给dfs一个使命,给它一个数i,返回以i位置为起点的最长递增子序列的长度;
b.函数体:遍历i后面的所有位置,看谁能加到i这个元素的后面。统计所有情况下的最大值;
c.递归出口:因为是判断之后才进入递归的,因此没有出口。
记忆化搜索:
a.加上一个备忘录;
b.每次进入递归的时候,去备忘录里面看看;
c.每次返回的时候,将结果加入到备忘录里面。
动态规划:
a.递归含义->状态表示;
b.函数体->状态转移方程;
c.递归出口->初始化。
cpp
//法一:记忆化搜索
class Solution {
int ret;
public:
int lengthOfLIS(vector<int>& nums) {
vector<int> memo(nums.size());
for(int i=0;i<nums.size();i++){
ret=max(ret,dfs(nums,i,memo));
}
return ret;
}
int dfs(vector<int>& nums,int pos,vector<int>& memo){
if(memo[pos]!=0) return memo[pos];
int ret=1;
for(int i=pos+1;i<nums.size();i++){
if(nums[i]>nums[pos]){
ret=max(dfs(nums,i,memo)+1,ret);
}
}
memo[pos]=ret;
return ret;
}
};
//法二:动态规划
class Solution {
public:
int lengthOfLIS(vector<int>& nums) {
vector<int> dp(nums.size(),1);
int ret=0;
//填表从后往前,因为要找最长的子序列,需向后查找
for(int i=nums.size()-1;i>=0;i--){
for(int j=i+1;j<nums.size();j++){
if(nums[i]<nums[j]){
dp[i]=max(dp[i],dp[j]+1);
}
}
ret=max(ret,dp[i]);
}
return ret;
}
};
3.4猜数字大小 II
算法思路(暴搜->记忆化搜索):
暴搜:
a.递归含义:给dfs一个使命,给它一个区间[left,right],返回在这个区间上能完胜的最小费用;
b.函数体:选择[left,right]区间上的任意一个数作为头节点,然后递归分析左右子树,求出所有情况下的最小值;
c.递归出口:当left>=right的时候,直接返回0。
记忆化搜索:
a.加上一个备忘录;
b.每次进入递归的时候,去备忘录里面看看;
c.每次返回的时候,将结果加入到备忘录里面。
cpp
class Solution {
int memo[201][201];
public:
int getMoneyAmount(int n) {
return dfs(1,n);
}
int dfs(int left,int right){
if(left>=right) return 0;
if(memo[left][right]!=0) return memo[left][right];
int ret=INT_MAX;
for(int head=left;head<=right;head++){//选择头节点
int l=dfs(left,head-1);
int r=dfs(head+1,right);
ret=min(ret,head+max(l,r));
}
memo[left][right]=ret;
return ret;
}
};
3.5矩阵中的最长递增路径
算法思路(暴搜->记忆化搜索):
暴搜:
a.递归含义:给dfs一个使命,给它一个下标[i,j],返回从这个位置开始的最长递增路径的长度;
b.函数体:上下左右四个方向看一看,哪里能过去就过去,统计四个方向上的最大长度;
c.递归出口:因为是先判断再进入递归,因此没有出口。
记忆化搜索:
a.加上一个备忘录;
b.每次进入递归的时候,去备忘录里面看看;
c.每次返回的时候,将结果加入到备忘录里面。
cpp
class Solution {
int memo[201][201];
public:
int longestIncreasingPath(vector<vector<int>>& matrix) {
int ret=0;
for(int i=0;i<matrix.size();i++){
for(int j=0;j<matrix[0].size();j++){
ret=max(ret,dfs(matrix,i,j));
}
}
return ret;
}
int dx[4]={1,-1,0,0};
int dy[4]={0,0,1,-1};
int dfs(vector<vector<int>>& matrix,int i,int j){
if(memo[i][j]!=0) return memo[i][j];
int ret=1;
for(int k=0;k<4;k++){
int x=i+dx[k],y=j+dy[k];
if(x>=0&&x<matrix.size()&&y>=0&&y<matrix[0].size()&&matrix[x][y]>matrix[i][j]){
ret=max(ret,dfs(matrix,x,y)+1);
}
}
memo[i][j]=ret;
return ret;
}
};