算法学习(17)—— FloodFill算法

ops/2024/12/26 19:10:33/

目录

关于FloodFill算法

部分OJ题详解

733. 图像渲染

200. 岛屿数量

695. 岛屿的最大面积

130. 被围绕的区域

417. 太平洋大西洋水流问题

529. 扫雷问题

LCR130. 衣橱整理


关于FloodFill算法

  • 爆搜,深搜,回溯的算法原理并不难,这类题目考察的就是我们的“代码能力”,就是我们能把思路转化为代码的能力
  • FloodFill算法解决的问题主要就是:“在一个有很多区域的矩阵中,找到性质相同的一个连通块”,做法也很简单,就是先遍历矩阵,当找到符合性质的第一个小区域时,对这个区域做一次深度优先遍历即可

部分OJ题详解

733. 图像渲染

733. 图像渲染 - 力扣(LeetCode)

给我们一个矩阵,然后又给了我们一个下标,要我们找到与这个下标的值相同的所有相邻的区域并把它们修改成另一个数color,下面来分析下这道题:

  • 解法很简单,就是从题目给我们的下标位置开始,上下左右依次做深度优先遍历即可,与我们上篇博客的解数独类题目类似,每次遍历后把符合要求的区域的值修改为color即可
  • 需要注意的是,如果原始颜色和目标颜色一样,就直接返回即可
class Solution 
{int dx[4] = {0, 0, 1, -1};int dy[4] = {1, -1, 0, 0};int m, n;int prev; //存原始颜色
public:vector<vector<int>> floodFill(vector<vector<int>>& image, int sr, int sc, int color) {prev = image[sr][sc];m = image.size(), n = image[0].size();dfs(image, sr, sc, color);return image;}void dfs(vector<vector<int>>& image, int sr, int sc, int color){if(image[sr][sc] == color) return;image[sr][sc] =  color;for(int k = 0; k < 4; k++){int x = sr + dx[k], y = sc + dy[k];if((x >= 0 && y >= 0) && (x < m && y < n) && image[x][y] == prev){dfs(image, x, y, color);}}}
};

200. 岛屿数量

200. 岛屿数量 - 力扣(LeetCode)

题目给我们一个二维矩阵,由两个值填充,1表示陆地,0表示水,题目要我们找出有多少个陆地组成的岛屿,如示例二,有3个陆地;下面来分析下这道题

  • 这道题是FloodFill算法的一个应用,题目就是要我们找出所有相通区域的数量
  • 其实有了上篇博客大量的题目练习的基础,这道题其实不难,基础算法也是深度优先遍历,然后把每个遍历后的区域用一个bool vis[][] 标记一下即可,就和“单词搜索”那道题一样
  • class Solution 
    {vector<vector<bool>> vis;int dx[4] = {0, 0, -1, 1};int dy[4] = {1, -1, 0, 0};int m, n;int ret = 0;
    public:int numIslands(vector<vector<char>>& grid) {m = grid.size(), n = grid[0].size();vis = vector<vector<bool>>(m, vector<bool>(n)); for(int i = 0; i < m; i++){for(int j = 0; j < n; j++){if(!vis[i][j] && grid[i][j] == '1'){ret++;dfs(grid, i, j);}}}return ret;}void dfs(vector<vector<char>>& g, int i, int j) //作用是把与最开始位置连通的岛屿全部标记一下{vis[i][j] = true;for(int k = 0; k < 4; k++){int x = i + dx[k], y = j + dy[k];if((x >= 0 && y >= 0) && (x < m && y < n) && (!vis[x][y] && g[x][y] == '1')){dfs(g, x, y);}}}
    };

695. 岛屿的最大面积

695. 岛屿的最大面积 - 力扣(LeetCode)

这道题我们就不细讲算法原理了,只要把上一道题的代码改一下即可,只要每次递归时想办法计算一下递归次数,递归次数代表岛屿的面积,找到最大面积也就是递归次数最多的那一个值即可,如下代码:

class Solution 
{vector<vector<bool>> vis;int dx[4] = {0, 0, -1, 1};int dy[4] = {1, -1, 0, 0};int m, n;int a = 0;
public:int maxAreaOfIsland(vector<vector<int>>& grid) {int ret = 0;m = grid.size(), n = grid[0].size();vis = vector<vector<bool>>(m, vector<bool>(n)); for(int i = 0; i < m; i++){for(int j = 0; j < n; j++){if(!vis[i][j] && grid[i][j] == 1){dfs(grid, i, j);ret = max(ret, a);a = 0;}}}return ret;}void dfs(vector<vector<int>>& g, int i, int j) //作用是把与最开始位置连通的岛屿全部标记一下{a++;vis[i][j] = true;for(int k = 0; k < 4; k++){int x = i + dx[k], y = j + dy[k];if((x >= 0 && y >= 0) && (x < m && y < n) && (!vis[x][y] && g[x][y] == 1)){dfs(g, x, y);}}}
};

130. 被围绕的区域

130. 被围绕的区域 - 力扣(LeetCode)

题目很好懂,主要就是“包围”是指上下左右包围的,斜着的不算,下面来分析下这道题:

  • 除了上面一个区域在边界的情况外,还有就是只要有一个连续的圈圈区域,只要有一个圈圈在边界上,那么这个大的圈圈连通区域都不要修改
  • 解法一:直接dfs,遇到边界了就回溯;但是不建议这样搞,因为代码不太好写,并且我们是在搜索过程中捕捉到边界区域的,所以我们得搞两个dfs函数,但是在dfs前我们无法知道一个联通区域是否含有边界区域,所以解法一pass
  • 解法二:正难则反;既然边界问题是这道题的难点,那么我们就先处理下边界,把与在边界的0区域相通的区域先干掉,那么剩下的自然就是在内部的没有边界的区域
  • 所以以解法二为例,先扫描边界,当扫描到边界0区域时,对其做深度优先遍历,把与边界0相通的区域全部打上标记,由于题目允许我们对内部做修改,我们就可以把边界区域修改成 ' . ',这样就不必再创建同等大小的bool vis 数组了
  • 处理完边界情况后,遇到‘ . ’,我们把它修改成0,遇到0时,我们把它修改成‘ × ’,这样就能从侧面完成值的修改
class Solution {int dx[4] = {1, -1, 0, 0};int dy[4] = {0, 0, -1, 1};int m, n;
public:void solve(vector<vector<char>>& board) {m = board.size(), n = board[0].size();// 1,把与边界O相连的连通块修改成点for (int j = 0; j < n; j++) {if (board[0][j] == 'O') dfs(board, 0, j); // 修改第一行if (board[m - 1][j] == 'O') dfs(board, m - 1, j); // 修改最后一行}for (int i = 0; i < m; i++) {if (board[i][0] == 'O') dfs(board, i, 0); // 修改第一列if (board[i][n - 1] == 'O') dfs(board, i, n - 1); // 修改最后一列}// 2,还原for (int i = 0; i < m; i++) {for (int j = 0; j < n; j++) {if (board[i][j] == '.') board[i][j] = 'O';else if (board[i][j] == 'O') board[i][j] = 'X';}}}void dfs(vector<vector<char>>& b, int i, int j) {b[i][j] = '.';for (int k = 0; k < 4; k++) {int x = i + dx[k], y = j + dy[k];if ((x >= 0 && y >= 0) && (x < m && y < n) && b[x][y] == 'O') {dfs(b, x, y);}}}
};

417. 太平洋大西洋水流问题

417. 太平洋大西洋水流问题 - 力扣(LeetCode)

这道题描述不好懂,这里来解释一下:给我们一个矩阵,里面的数字相当于海平面高度,左边和上面代表太平洋,右边和下面代表大西洋;水可以从高往低处流,如果数字相等也可以流,然后题目就是要我们找到所有的水可以同时流向两个大洋的坐标。下面来分析下这道题:

  • 我们可以暴力枚举里面所有的点,判断能不能同时去两个大洋,能同时去就保存然后继续枚举下一个,这就是解法一,也就是直接解决问题;这种解法很简单,代码也不难写,但是有一个问题,直接枚举判断有很多重复的路径,所以,这种解法存在很大的优化空间
  • 解法二:正难则反;和上一道题一样,我们可以反过来,就是看看水向上能逆向流到什么位置,假设以示例一的图为例,从最左上角的1开始逆向推导,如下图:
  • 大西洋也是同样的道理,然后在最后,再遍历一次矩阵,找到有两种标记的坐标,然后记录一下即可
class Solution 
{int dx[4] = {0, 0, 1, -1};int dy[4] = {1, -1, 0, 0};int m, n;
public:vector<vector<int>> pacificAtlantic(vector<vector<int>>& heights) {m = heights.size(), n = heights[0].size();vector<vector<bool>> Pac(m, vector<bool>(n));vector<vector<bool>> Atl(m, vector<bool>(n));//1,先处理太平洋for(int j = 0; j < n; j++) dfs(heights, 0, j, Pac); //处理第一列for(int j = 0; j < m; j++) dfs(heights, j, 0, Pac); //处理第一行//2,再处理大西洋for(int j = 0; j < n; j++) dfs(heights, m - 1, j, Atl); //处理最后一行for(int j = 0; j < m; j++) dfs(heights, j, n - 1, Atl); //处理最后一列//3,统计vector<vector<int>> ret;for(int i = 0; i < m; i++){for(int j = 0; j < n; j++){if(Pac[i][j] && Atl[i][j])ret.push_back({i, j});}}return ret;}void dfs(vector<vector<int>>& h, int i, int j, vector<vector<bool>>& vis){vis[i][j] = true;for(int k = 0; k < 4; k++){int x = i + dx[k], y = j + dy[k];if((x >= 0 && y >= 0) && (x < m && y < n) && !vis[x][y] && h[x][y] >= h[i][j]){dfs(h, x, y, vis);}}}
};

529. 扫雷问题

529. 扫雷游戏 - 力扣(LeetCode)

这道题题意照样不好懂,下面来解释下题意:

题目给我们一个矩阵 board 和一个 坐标 click,其中矩阵由很多值:

  • ‘M’表示未挖出的地雷
  • ‘E’表示未挖出的空方块
  • ‘B’表示八个方向都没有地雷的已挖出的方块
  • 数字表示有多少地雷与这块已被挖出的方块相邻
  • ‘X’表示已挖出的地雷

click 表示我们要点击的位置,然后对于这个位置:

  • 如果这个位置有地雷('M'),就把它改为‘X’,然后游戏直接结束,直接返回盘面
  • 如果一个没有相邻地雷的空方块‘E’被挖出,将其修改为‘B’,并且所有和其相邻的未挖出方块都需要递归揭露
  • 如果一个至少与一个地雷相邻的‘E’被挖出,修改为数字,表示周围地雷的数量

最后就是如果在此次点击中,无更多方块可被揭露,则返回盘面,下面来分析下这道题 :

  •  把题目搞懂之后,就可以发现这道题其实是个模拟题,而且题目都告诉你用递归了,所以这道题的算法就是深搜
  • 当点击之后,如果周围没有地雷,就要递归地把周围地方块打开,所以在点击之后,我们需要统计下周围地雷地个数,也就是统计‘M’的个数,如果个数为0,修改成‘B’然后继续递归
  • 然后递归进入下一个区域后,继续统计地雷数量,没有地雷就全部打开;如果有地雷,就修改为地雷个个数,然后往没有地雷的空方格递归
  • 所以函数头的参数就是两个,就是给我一个坐标即可
  • 然后就是向量数组,前面几道题是4个方向,这次变成了8个方向, 我们其实不需要再搞其它的数组,只要把dx和dy扩展一下即可,具体会在代码中实现 
class Solution 
{int dx[8] = {0, 0, 1, -1, -1, -1, 1, 1};int dy[8] = {1, -1, 0, 0, -1, 1, -1, 1};int m, n;
public:vector<vector<char>> updateBoard(vector<vector<char>>& board, vector<int>& click) {m = board.size(), n = board[0].size();int x = click[0], y = click[1];if(board[x][y] == 'M') //如果直接点到地雷,就直接结束游戏{board[x][y] = 'X';return board;}    dfs(board, x, y);return board;}void dfs(vector<vector<char>>& b, int i, int j){//先统计下周围地雷的个数int count = 0;for(int k = 0; k < 8; k++){int x = i + dx[k], y = j + dy[k];if((x >= 0 && y >= 0) && (x < m && y < n) && b[x][y] == 'M') count++;}if(count != 0) //周围有地雷的话,就不用再递归展开了,修改为数字之后直接返回{b[i][j] = count + '0';return;}else //周围没有地雷{b[i][j] = 'B';for(int k = 0; k < 8; k++){int x = i + dx[k], y = j + dy[k];if((x >= 0 && y >= 0) && (x < m && y < n) && b[x][y] == 'E') dfs(b, x, y);}}}
};

LCR130. 衣橱整理

LCR 130. 衣橱整理 - 力扣(LeetCode)

给我们一个矩阵(大小为m × n)和一个数cnt,从最左上角开始,可以上下左右遍历,如果用 i 和 j 表示途中遍历的横纵坐标,那么不能遍历digit(i) + digit(j) > cnt 的格子,然后最后返回我们可以遍历的格子的数量,下面来分析下这道题:

  • 算法原理很简单,就是对左上角做一次深搜即可
class Solution 
{int ret = 0;int dx[4] = {0, 0, -1, 1};int dy[4] = {1, -1, 0, 0};int m, n, cnt;bool vis[101][101];
public:int wardrobeFinishing(int _m, int _n, int _cnt) {m = _m, n = _n, cnt = _cnt;dfs(0, 0);return ret;}void dfs(int i, int j){ret++;vis[i][j] = true;for(int k = 0; k < 4; k++){int x = i + dx[k], y = j + dy[k];if((x >= 0 && y >= 0) && (x < m && y < n) && (!vis[x][y] && check(x, y))){dfs(x, y);}}}bool check(int i, int j){int tmp = 0;while(i){tmp += i % 10;i /= 10;}while(j){tmp += j % 10;j /= 10;}return tmp <= cnt;}
};

http://www.ppmy.cn/ops/145188.html

相关文章

vue调试工具 Vue.jsDevtools

文件下载 Vue.js Devtools 通过网盘分享的文件&#xff1a;ddebf336f8a44293bd4db9d0f287bc1c.crx 链接: https://pan.baidu.com/s/1uS3a49CwW-B000p5GwUQmQ 提取码: ko89 下载完了 &#xff0c;拖入chrome里&#xff0c;打开详情配置. 打开红框中的开关 重启浏览器&#xff…

React Native 集成 iOS 原生功能

React Native 集成 iOS 原生功能完整指南 前言 在 React Native 项目中集成 iOS 原生功能是一个常见需求。本文将同样以打印机功能为例&#xff0c;详细介绍如何在 React Native 项目中集成 iOS 原生功能。 集成步骤概述 创建原生模块&#xff08;Native Module&#xff09…

【Redis经典面试题六】Redis的持久化机制是怎样的?

目录 一、Redis的持久化机制 1.1 RDB 1.2 AOF 1.3 比较 1.4 混合持久化 二、RDB 和 AOF 的写回策略分别是什么&#xff1f; 2.1 RDB的写回策略 定期触发 手动触发 2.2 AOF 的写回策略 三、Redis能完全保证数据不丢失吗&#xff1f; 一、Redis的持久化机制 Redis提供…

Kubernetes(k8s)离线部署DolphinScheduler3.2.2

1.环境准备 1.1 集群规划 本次安装环境为&#xff1a;3台k8s现有的postgreSql数据库zookeeper服务 1.2 下载及介绍 DolphinScheduler-3.2.2官网&#xff1a;https://dolphinscheduler.apache.org/zh-cn/docs/3.2.2 官网安装文档&#xff1a;https://dolphinscheduler.apach…

AWS IAM Roles Anywhere 使用 OpenSSL 自签 CA 过程

背景介绍 相比于传统使用 AK/SK 在第三方应用中访问 AWS 资源的认证方式, IAM Roles Anywhere 使用证书认证的方式为应用生成临时的身份凭证, 可以有效避免 AK/SK 意外泄漏造成的安全隐患. AWS IAM Roles Anywhere 官方介绍 工作流程示意 主要涉及到的几个概念 Private CA:…

JAVA开发 在 Spring Boot 中集成 Swagger

Swagger 是一个广泛使用的 API 文档生成工具&#xff0c;可以帮助你自动生成和维护 RESTful API 的文档。在不同的框架中集成 Swagger 通常需要添加相应的依赖项。以下是几种常见 Java 框架&#xff08;如 Spring Boot&#xff09;中集成 Swagger 的依赖配置。 在 Spring Boot…

前端编程训练 异步编程篇 请求接口 vue与react中的异步

文章目录 前言代码执行顺序的几个关键点接口请求vue与react中的异步 vue中的异步react的state修改异步 前言 本文是B站三十的前端课的笔记前端编程训练,异步编程篇 代码执行顺序的几个关键点 我们可以理解为代码就是一行一行&#xff0c;一句一句是执行&#xff08;定义变…

使用envoyfilter添加请求头

该envoyfilter实现了这样一个功能&#xff0c;如果请求头中含有Sw8&#xff0c;则添加请求头HasSw8: true。 1. 内嵌lua脚本 apiVersion: networking.istio.io/v1alpha3 kind: EnvoyFilter metadata:name: add-header-filternamespace: demo-bookinfo # 可根据实际情况调整命…