【BFS/DFS 解决 FloodFill 算法】衣橱整理

文章目录

题目链接: LCR 130. 衣橱整理


题目解析

首先介绍一下什么是 FloodFill算法

FloodFill 算法,也称为洪水填充 算法,指的是在区域中找到 性质相同的联通块,注意这里的联通块指的是 上下左右相邻 ,斜线不能算做相邻。该算法可以使用 深度优先搜索广度优先搜索 来解决。

回到题目:家居整理师将待整理衣橱划分为 m x n 的二维矩阵 grid,其中 grid[i][j] 代表一个需要整理的格子。整理师自 grid[0][0] 开始 逐行逐列 地整理每个格子。

在整理过程中,可以选择 向右移动一格向下移动一格,但不能移动到衣柜之外。

移动到每一个格子的时候,都要计算 数位之和(如12, 23,数位之和是1+2+2+3=8) ,如果某个格子的数位之和 > cnt,就不整理该格子。

给出一个例子:

  • m = 3, n = 3, cnt = 2
♙(起始位置) (0,1) (0,2)
(1,0) (1,1) (1,2)
(2,0) (2,1) (2,2)

红色标注表示可以被整理的格子,返回 6。

方向向量

在继续之前,有必要知道我们在解决 矩阵搜索类 问题时 访问某位置上下左右四个方向 的操作。

坐标 〖i, j〗 的上下左右四个坐标是在 ij 加上了 0、1、-1 :

  • 上下坐标〖i + (-1), j + 0〗〖i + 1, j + 0〗
  • 左右坐标〖i + 0, j + (-1)〗〖i + 0, j + 1〗

因此需要定义两个向量坐标:dx = {0, 0, -1, 1},dy = {-1, 1, 0, 0}

在需要访问时,通过 〖row, col〗坐标和四次循环依次访问即可。


BFS:广度优先搜索

算法原理

我们只需要从起始位置开始宽搜即可,宽搜的条件是:方格的数位之和 <= cnt 且 未被访问过

标记数组

我们在进行 BFS 的时候,可能会重复进入某个方格。可以用两种方式避免重复访问:

  • 在原数组上修改
  • 使用标记数组

本题没有明确能否修改原数组,我们采用第二种方式,定义一个与题目所给的矩阵 grid 大小一致的布尔标记数组 visit,用这个标记数组来记录方格的访问状态,若方格已被访问,就设置对应的标记为 true。

全局变量

用于记录结果的 ret;一个布尔类型的标记数组 visit;方向数组dxdy 辅助我们从某个位置向其上下左右四个方向访问。

java 复制代码
boolean[][] visit;
int[] dx = {0, 0, -1, 1};
int[] dy = {-1, 1, 0, 0};

层序遍历

我们使用一个队列实现层序遍历的操作:

  1. 队列存储与〖row, col〗位置相连的单元格坐标
  2. 当队列不为空时,一直取出队首元素获取坐标,然后根据坐标向该元素的上下左右四个方向访问查找符合条件的单元格(方格的数位之和 <= cnt 且 未被访问过)
  3. 找到符合条件的单元格之后,入队,然后将该位置的标记更新为 true,同时更新结果
  4. 当队列为空,层序遍历完毕,返回结果即可

细节问题

注意可能出现的边界情况:cnt 为0时,直接返回1即可。

代码实现

java 复制代码
class Solution {
    boolean[][] visit;
    int[] dx = {0, 0, -1, 1};
    int[] dy = {-1, 1, 0, 0};

    public int wardrobeFinishing(int m, int n, int cnt) {
        // 边界情况
        if (cnt == 0) {
            return 1;
        }
        // 初始化
        int ret = 0;
        Queue<int[]> queue = new ArrayDeque<>();
        visit = new boolean[m][n];

        // 从起始位置开始宽搜
        queue.offer(new int[]{0, 0});// 入队
        visit[0][0] = true;          // 更新标记
        ret++;                       // 更新结果
        // 层序遍历
        while (!queue.isEmpty()) {
            int[] top = queue.poll();      // 获取队首元素
            int row = top[0], col = top[1];// 获取队首元素的坐标
            for (int k = 0; k < 4; k++) {
                int x = row + dx[k], y = col + dy[k];
                if (x >= 0 && x < m && y >= 0 && y < n && !visit[x][y]) {
                    int count = deal(x, y); // 计算当前位置[x,y]的数位之和
                    if (count <= cnt) {     // 满足条件
                        queue.offer(new int[]{x, y});// 入队
                        visit[x][y] = true;          // 更新标记
                        ret++;                       // 更新结果
                    }
                }
            }
        }
        // 返回结果
        return ret;
    }

    public int deal (int x, int y) {
        // 计算某个位置的数位之和
        int ret = 0;
        while (x != 0) {
            ret += (x % 10);
            x /= 10;
        }
        while (y != 0) {
            ret += (y % 10);
            y /= 10;
        }
        return ret;
    }
}

DFS:深度优先搜索

算法原理

根据题意从起始位置开始进行深度优先搜索:

  1. 进入一个方格(除了起始位置),计算该方格的数位之和,如果数位之和小于等于 cnt,就表示可以整理该方格,继续基于该方格深搜(同时注意方格须未被访问过)
  2. 当经过所有能整理的方格后,返回统计到的方格个数

标记数组

我们在进行 DFS 的时候,可能会重复进入某个方格。可以用两种方式避免重复访问:

  • 在原数组上修改
  • 使用标记数组

本题没有明确能否修改原数组,我们采用第二种方式,定义一个与题目所给的矩阵 grid 大小一致的布尔标记数组 visit,用这个标记数组来记录方格的访问状态,若方格已被访问,就设置对应的标记为 true。

全局变量

为了在 dfs 函数使用 cnt 更方便,将该变量改为全局的;我们在判断坐标合法性时需要用到矩阵的行数和列数,因此将 mn 作为全局变量;用于记录结果的 ret;一个布尔类型的标记数组 visit;方向数组dxdy 辅助我们从某个位置向其上下左右四个方向访问。

java 复制代码
int m, n, cnt, ret;
boolean[][] visit;
int[] dx = {0, 0, -1, 1};
int[] dy = {-1, 1, 0, 0};

dfs 函数

函数头

我们给 dfs 函数设置的任务是:从某个位置向上下左右四个方向搜寻相似的方格 ,这里的 相似 指的是 方格所在位置的数位之和 <= cnt

因此 dfs 函数的参数是某个位置的坐标 rowcol

java 复制代码
void dfs (int row, int col);
函数体

在进入 dfs 函数之后,循环四次分别访问指定位置的上下左右四个方向,找到符合条件的方格之后,先更新标记和结果,再继续递归深搜。

细节问题

注意可能出现的边界情况:cnt 为0时,直接返回1即可。

代码实现

java 复制代码
class Solution {
    int m, n, cnt, ret;
    boolean[][] visit;
    int[] dx = {0, 0, -1, 1};
    int[] dy = {-1, 1, 0, 0};
    
    public int wardrobeFinishing(int givenM, int givenN, int givenCnt) {
        //边界情况
        if (givenCnt == 0) {
            return 1;
        }
        // 初始化
        m = givenM;
        n = givenN;
        cnt = givenCnt;
        visit = new boolean[m][n];
        
        // 从初始位置开始深搜
        visit[0][0] = true;  // 更新标记
        ret++;               // 更新结果
        dfs(0, 0);
        
        // 返回结果
        return ret;
    }
    
    public void dfs (int row, int col) {
        for (int k = 0; k < 4; k++) {
            int x = row + dx[k], y = col + dy[k];
            if (x >= 0 && x < m && y >= 0 && y < n && !visit[x][y]) {
                int count = deal(x, y);    // 计算该位置的数位之和
                if (count <= cnt) {
                    visit[x][y] = true;    // 更新标记
                    ret++;                 // 更新结果
                    dfs(x, y);             // 继续深搜
                }
            }
        }
    }
    
    public int deal (int x, int y) {
        // 计算某个位置的数位之和
        int ret = 0;
        while (x != 0) {
            ret += (x % 10);
            x /= 10;
        }
        while (y != 0) {
            ret += (y % 10);
            y /= 10;
        }
        return ret;
    }
}

文章到这里就告一段落了,若有错误请尽管指出~

相关推荐
zzzll11111 小时前
LangChain 1.3 新特性详解与实战指南
java·数据库·langchain
Knight_AL1 小时前
EasyExcel 导入模板踩坑:row.add(““)导致文本格式失效与单元格保护问题解决方案
java·前端·easyexcel
热心网友俣先生1 小时前
2026年高教社杯数模国赛A题:五年命题规律与预测
算法·机器学习·数学建模
霸道流氓气质1 小时前
Spring AI Alibaba 系列总结:Java 工程师 AI 能力全景图谱
java·人工智能·spring
二十雨辰2 小时前
[Java]-场景题
java·开发语言
MZA6662 小时前
Spring Boot 实战:基于自定义注解 + AOP 实现 Kafka 消息无侵入异步上送
java·spring boot·kafka
我是大猴子2 小时前
为什么SSE无法代替Webscoket
java
重庆小透明2 小时前
Kafka 完全指南:从基础组件到核心原理(包含面试题)
java·分布式·微服务·架构·kafka
CodeStats2 小时前
【Java文件IO】Java 文件 IO 体系深度拆解(中):NIO API 详解、Files 与 FileChannel 巅峰对决
java·io·文件·nio·零拷贝·mmap