16.2:岛屿数量问题

文章目录

  • 岛屿数量问题
  • 方法一:采用递归的方法
  • 方法二:使用并查集的方法(map)
  • 方法三:使用并查集的方法(数组)

岛屿数量问题

测试链接:https://leetcode.com/problems/number-of-islands/

方法一:采用递归的方法

在这里插入图片描述

遇到1后将其周围的感染成2

在这里插入图片描述

在这里插入图片描述

将感染的改成2,这个很重要,否则递归跑不完。

	public static int numIslands3(char[][] board) {
        int count = 0;
        //遍历整个二维数组,碰到‘1’字符就将其上下左右四个区域是‘1’的都感染为‘2’.
        for (int i = 0; i < board.length; i++) {
            //注意二维数组列数的写法。
            for (int j = 0; j < board[i].length; j++) {
                //被感染的变成了2,下次就不会再进入了。
                if (board[i][j] == '1') {
                    infect(board, i, j);
                    count++;
                }
            }
        }
        return count;
    }

    public static void infect(char[][] board, int i, int j) {
        //二维数组的行和列的获取方式:
        //board.length -> 二维数组board中一维数组的个数,表示的是行数。
        //board[0].length -> 二维数组中一维数组的元素的个数,表示的是列数。
        if (i < 0 || i >= board.length || j < 0 || j >= board[0].length || board[i][j] != '1') {
            return;
        }
        board[i][j] = 2;
        //将其上下左右区域都感染。
        infect(board, i, j + 1);
        infect(board, i, j - 1);
        infect(board, i + 1, j);
        infect(board, i - 1, j);
    }

方法二:使用并查集的方法(map)

在这里插入图片描述

因为map特性的原因如果都是1字符,map只会放下一个。所以我们创建一个Dot表,来替换原broad表。

	/**
     * 方法二:采用并查集的方法
     * 注意是map形式的并查集,使用并查集合并,一次只能合并两个。
     */

    public static int numIslands1(char[][] board) {
        //board数组的行与列数。
        int H = board.length;
        int L = board[0].length;
        List<Dot> dotList = new ArrayList<>();
        Dot[][] dots = new Dot[H][L];
        //先遍历一遍board二维数组,将dots二维数组生成好。
        //之前board数组是1的位置,现在在dots数组中是一个地址,之前在board数组中是0的位置,现在在dots数组中是null.
        for (int i = 0; i < H; i++) {
            for (int j = 0; j < L; j++) {
                if (board[i][j] == '1') {
                    dots[i][j] = new Dot();
                    dotList.add(dots[i][j]);
                }
            }
        }
        //创建并查集并初始化并查集。其中每一个Dot都是一个小集合。
        UnionFind<Dot> unionFind = new UnionFind<>(dotList);
        //再遍历一遍board二维数组
        for (int i = 0; i < H; i++) {
            for (int j = 0; j < L; j++) {
                //如果遇到了'1'就判断当前节点的左侧与上侧是否也是‘1’,如果也是‘1’就合并所对应的dots数组的中dot.
                if (board[i][j] == '1') {
                    //加入防止左侧越界的条件。
                    if (j - 1 >= 0 && j - 1 < L && board[i][j - 1] == '1') {
                        unionFind.union(dots[i][j], dots[i][j - 1]);
                    }
                    加入防止上侧越界的条件。
                    if (i - 1 >= 0 && i - 1 < H && board[i - 1][j] == '1') {
                        unionFind.union(dots[i][j], dots[i - 1][j]);
                    }
                }
            }
        }
        return unionFind.sets();
    }

    public static class Dot {
    }

    public static class UnionFind<V> {

        /**
         * 属性
         */
        public HashMap<V, V> fatherMap;
        //father:<v1,v2>:指的是:v1的父亲是v2,注意这里的父亲是直系父亲.
        //HashMap<5, 2>:5->2
        //HashMap<2, 4>:2->4
        //HashMap<4, 6>:4->6
        public HashMap<V, Integer> sizeMap;
        //size:<V,Integer>:size里面装的是所有集合的头部节点,以及该头部节点下集合的元素个数。
        //注意不是头部节点不可以放入size中。

        /**
         * 构造器
         */
        public UnionFind(List<V> list) {
            //创建两个map。
            fatherMap = new HashMap<>();
            sizeMap = new HashMap<>();
            //遍历一遍链表,将链表中的数据放入两个map中。
            for (V l : list) {
                //当只有一个节点的时候,这个节点的头部节点是他自己,即自己指向自己。
                fatherMap.put(l, l);
                sizeMap.put(l, 1);
            }
        }

        /**
         * findAncestor方法
         * 传入一个节点,然后从这个节点开始一直往上找,直到直到最上边为止,返回最上面的节点。
         */
        public V findAncestor(V cur) {
            //创建一个容器,顺着当前的节点cur开始往上找,将途中经过的节点都放入这个容器中。
            Stack<V> path = new Stack<>();
            //循环的条件是:当当前节点的父亲就是当前节点时,说明来到了最顶部。
            while (cur != fatherMap.get(cur)) {
                //将cur讲过的节点放入容器中。
                path.push(cur);
                //cur来到其直系父亲节点。
                cur = fatherMap.get(cur);
            }
            //这是一个优化部分。
            //我们将途径的每个节点都指向祖先节点,这样就降低了路径的长度,使复杂度更低。
            //假设从cur开始到顶部的长度是n,第一次进行向上寻找的时候复杂度是O(n),但是以后寻找的时候复杂度就会变成O(1)。
            while (!path.isEmpty()) {
                fatherMap.put(path.pop(), cur);
            }
            return cur;
        }

        /**
         * isSameSet方法
         * 传入两个值,判断这两个值是否是在一个集合里。
         */
        public boolean isSameSet(V value1, V value2) {
            return findAncestor(value1) == findAncestor(value2);
        }

        /**
         * union方法
         * 给你两个值,将两个值所在的集合进行合并。
         */
        public void union(V a, V b) {
            //father1指向的是a元素所在集合的头部节点。
            //father2指向的是b元素所在集合的头部节点。
            V father1 = findAncestor(a);
            V father2 = findAncestor(b);
            if (father1 != father2) {
                //size1是头部节点father1所在集合的大小。
                //size2是头部节点father2所在集合的大小。
                int size1 = sizeMap.get(father1);
                int size2 = sizeMap.get(father2);
                //big指向的是集合元素多的头部节点。
                //small指向的是集合元素少的头部节点。
                V big = size1 > size2 ? father1 : father2;
                V small = big == father1 ? father2 : father1;
                //small指向big
                //这里使用map实现的指针的功能。
                //因为small合并到big中,所以big这个头部节点的sizemap中元素个数要增多。
                sizeMap.put(big, size1 + size2);
                //与此同时头部节点small应该指向big。
                fatherMap.put(small, big);
                //因为small指向big所以,small不再是头部节点,就将small从sizeMap中删除。
                sizeMap.remove(small);
            }
        }

        /**
         * sets方法
         * 返回的是一共有几个集合/几个头部节点。
         */
        public int sets() {
            return sizeMap.size();
        }
    }
}

方法三:使用并查集的方法(数组)

但其实用一维坐标是比较常见的写法。不同的1,希望用办法来区分。可以用一维坐标代表,也可用一个类的实例的不同内存地址来代表。都可以。更推荐一维坐标的方式。因为快。常数时间少。

采用并查集(数组)的方法进行操作,习惯上是采用一维数组,所以我们要将二维数组转化成一维数组。

在这里插入图片描述

虽然一维数组上有很多的空间是没用用上的,但其实无所谓,因为这样我们可以快速的锁定我们要找的位置。

而不需要进行复杂的转换。

/**
     * 方法三
     */
    public static int numIslands2(char[][] board) {
        UnionFind2 unionFind2 = new UnionFind2(board);
        int H = board.length;
        int L = board[0].length;
        for (int i = 0; i < H; i++) {
            for (int j = 0; j < L; j++) {
                //如果遇到了'1'就判断当前节点的左侧与上侧是否也是‘1’,如果也是‘1’就合并
                if (board[i][j] == '1') {
                    //加入防止左侧越界的条件。
                    if (j - 1 >= 0 && j - 1 < L && board[i][j - 1] == '1') {
                        unionFind2.union(i, j, i, j - 1);
                    }
                    加入防止上侧越界的条件。
                    if (i - 1 >= 0 && i - 1 < H && board[i - 1][j] == '1') {
                        unionFind2.union(i, j, i - 1, j);
                    }
                }
            }
        }
        return unionFind2.sets();
    }

    /**
     * 并查集内部类
     * 这里的并查集是一个二位数组改成一维数组的并查集。
     */
    public static class UnionFind2 {
        /**
         * 属性
         */

        public static int L;//列数
        public static int[] fatehr;
        public static int[] size;
        public static int[] help;
        public static int sets;

        /**
         * 构造器
         */
        public UnionFind2(char[][] board) {
            //行数
            int H = board.length;
            //列数
            int L = board[0].length;
            this.L = L;
            fatehr = new int[H * L];
            size = new int[H * L];
            help = new int[H * L];
            sets = 0;
            //遍历一遍board二维数组。
            for (int i = 0; i < H; i++) {
                for (int j = 0; j < L; j++) {
                    //只有二维数组board是’1‘的,一维数组才会放入数。
                    //这样一维数组就会有很多位置是空的,但是这不重要。
                    if (board[i][j] == '1') {
                        //将二维数组下标转化为一维数组的下标。
                        int k = index(i, j);
                        fatehr[k] = k;
                        size[k] = 1;
                        sets++;
                    }
                }
            }
        }

        /**
         * index方法
         * 将一个二维数组坐标[i][j]转化成一维数组坐标[k]。
         * 公式:i * L + j
         */
        public static int index(int i, int j) {
            return i * L + j;
        }

        /**
         * findAncestor方法
         */
        public static int findAncestor(int x) {
            int j = 0;
            while (fatehr[x] != x) {
                help[j++] = x;
                x = fatehr[x];
            }
            // father[x] == x
            //优化:将途径的节点直接连到祖先节点上,从而降低了下次查找的长度。
            j--;
            while (j > 0) {
                fatehr[help[j--]] = x;
            }
            return x;
        }

        /**
         * union方法
         * 将二维数组中board[i][j]与board[m][n]位置的集合进行合并
         */
        public static void union(int i, int j, int m, int n) {
            //将二维数组坐标[i][j]转化成一维数组坐标[k1]。
            int k1 = index(i, j);
            //将二维数组坐标[m][n]转化成一维数组坐标[k2]。
            int k2 = index(m, n);
            //找到k1的祖先fatherK1
            int fatherK1 = findAncestor(k1);
            //找到k2的祖先fatherK2
            int fatherK2 = findAncestor(k2);
            if (fatherK1 != fatherK2) {
                //找到头部节点fatherK1所在集合中的元素个数
                int sizeK1 = size[fatherK1];
                //找到头部节点fatherK2所在集合中的元素个数
                int sizeK2 = size[fatherK2];
                int big = sizeK1 > sizeK2 ? fatherK1 : fatherK2;
                int small = big == fatherK1 ? fatherK2 : fatherK1;
                //将集合元素小的头部节点挂到集合元素比较多的头部节点上。
                fatehr[small] = big;
                size[big] = sizeK1 + sizeK2;
                size[small] = 0;
                sets--;
            }
        }

        /**
         * 因为本题并不涉及到查找两个元素是否在同一个集合中,所以省略该方法。
         */

        /**
         * 返回集合元素的个数
         */
        public static int sets() {
            return sets;
        }
    }

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:/a/26710.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

C++ string类-2

at at 函数是在C还没有支持运算符重载的时候提供的。 他可以像 [] 重载运算符一样&#xff0c;找到某个位置的字符&#xff1a; string s1("hello world");s1.at(0) x;cout << s1 << endl; 输出&#xff1a; [] 重载运算符和 at&#xff08;&#x…

8自由度并联腿机器狗实现行走功能

1. 功能说明 本文示例将实现R309a样机8自由度并联腿机器狗行走的功能。 2. 并联仿生机器人结构设计 机器狗是一种典型的并联仿生四足机器人&#xff0c;其腿部结构主要模仿了四足哺乳动物的腿部结构&#xff0c;主要由腿部的节段和旋转关节组成。在设计机器狗的腿部结构时&…

echart实现地图展示

最近做的页面中需要展示省级地图精确到市级且悬浮到地区上时会显示一些信息 然后参考了网址&#xff1a; “绿色金融” - 江西省 - category-work,geo地理坐标,legend,series-map地图,series-scatter散点图,title标题,tooltip提示框,visualMap视觉映射 - makeapie echarts社区…

【玩转Linux操作】硬链接和软连接

&#x1f38a;专栏【玩转Linux操作】 &#x1f354;喜欢的诗句&#xff1a;更喜岷山千里雪 三军过后尽开颜。 &#x1f386;音乐分享【Counting Stars 】 欢迎并且感谢大家指出小吉的问题&#x1f970; 欢迎大家访问“在下小吉.”&#xff08;偷偷告诉你这个是我的大号哦&#…

yolov8seg模型转onnx转ncnn

yolov8是yolo的最新版本&#xff0c;可做图像分类&#xff0c;目标检测&#xff0c;实例分割&#xff0c;姿态估计。 主页地址 这里测试一个分割模型。 模型如下 选yolov8n-seg模型&#xff0c;转成onnx&#xff0c;再转ncnn测试。 yolov8s-seg的ncnn版可以直接用这个 如果用…

【Django 网页Web开发】07. 快捷的表单生成 Form与MoudleForm(保姆级图文)

目录 注意 正规写法是 ModelForm&#xff0c;下面文章我多实现效果url.py新建3个html文件数据库连接model.py 数据表1. 原始方法view.pytestOrgion.html 2. Form方法view.pytestForm.html 3. MoudleForm方法给字段设置样式面向对象的思路&#xff0c;批量添加样式错误信息的显示…

搜索算法(三) 回溯法

1.回溯法 回溯法可以理解成一种特殊的深度优先算法&#xff0c;比起普通的DFS&#xff0c;多了还原当前节点的一步。 修改当前节点、递归子节点、还原当前节点。 本质是一种试错的思想。 维基百科&#xff1a; 2.例题 1&#xff09; 力扣https://leetcode.cn/problems/pe…

17_Linux根文件简介与Busybox构建文件系统

目录 根文件系统简介 文件目录简介 BusyBox简介 编译BusyBox构建根文件系统 修改Makefile添加编译器 busybox中文字符支持 配置 busybox 编译busybox 向根文件系统添加lib库 向rootfs的“usr/lib”目录添加库文件 创建其他文件夹 根文件系统初步测试 根文件系统简介…

行业应用|立仪光谱共焦位移传感器在玻璃方面的检测

项目&#xff1a;玻璃管管壁单边测厚 行业应用|立仪光谱共焦位移传感器在玻璃方面的检测 行业应用|立仪光谱共焦位移传感器在玻璃方面的检测 检测方案 用D35A7镜头对玻璃管管壁进行单边测厚&#xff0c;取三个点静态测量厚度并记录重复性。 1、采用D35A7R2S35镜头对玻璃管管…

Android Input子系统 - kernel

目录 前言 数据结构 输入子系统流程 前言 上一节有展示Android Input子系统的架构图,这里我们关心Linux kernel层 可以看到kernel层分为三层: 输入子系统设备驱动:处理与硬件相关的信息,调用input API注册输入设备,并把数据往上报 输入子系统核心层:为事件处理层和设…

Python之并发多线程操作

一、threading模块介绍 multiprocess模块的完全模仿了threading模块的接口&#xff0c;二者在使用层面&#xff0c;有很大的相似性 二、开启线程的两种方式 方式一 #方式一 from threading import Thread import time def sayhi(name):time.sleep(2)print(%s say hello %na…

迷你版ChatGPT开源,教你怎么用nanoGPT训练一个写小说的AI机器人!

大家好,我是千与千寻,好久不见,最近太忙了,去医院拔了颗智齿,这不是刚休息一天,就立刻来给大家分享ChatGPT的新奇项目了。 ChatGPT的功能确实是好用,但是我觉得有一个小缺点,就是反应的时间比较慢,原因是GPT-3.5/GPT-4.0的模型体积较大,比较占用内存空间。 同时大模…

10.ES6模块化规范(关键字 import,from,as,export的用法)

导入其他模块成员要使用关键字 import &#xff0c;导出需要使用关键字 export 我们明确一个概念&#xff0c;只有js与js之间需要使用import与export&#xff0c;如果是在html中引入js是不需要用import的&#xff0c;你导入的方式是直接srcxxx.js 目录 1 默认导入导出 2 …

【高危】Apache Inlong 存在JDBC反序列化漏洞

漏洞描述 Apache InLong 是可用于构建基于流式的数据分析、建模等一站式的海量数据集成框架。 在Apache Inlong受影响版本&#xff0c;由于未对接收的jdbcUrl参数过滤空格字符&#xff0c;导致可以利用空格绕过jdbcUrl中autoDeserialize参数过滤限制&#xff0c;通过认证的攻…

尚硅谷JUC极速版笔记

尚硅谷JUC极速版笔记 1、JUC概述1.1 进程和线程1.2 线程的状态&#xff08;6个&#xff09;1.3 wait和sleep1.4 并发与并行1.5 管程&#xff08;锁&#xff09;1.6 用户线程和守护线程 2、Lock接口2.1 复习synchronized&#xff08;java内置同步锁&#xff09;2.2 什么是Lock接…

设计模式详解之策略模式

作者&#xff1a;刘文慧 策略模式是一种应用广泛的行为型模式&#xff0c;核心思想是对算法进行封装&#xff0c;委派给不同对象来管理&#xff0c;本文将着眼于策略模式进行分享。 一、概述 我们在进行软件开发时要想实现可维护、可扩展&#xff0c;就需要尽量复用代码&#x…

LayUI前框框架普及版

LayUI 一、课程目标 1. 【了解】LayUI框架 2. 【理解】LayUI基础使用 3. 【掌握】LayUI页面元素 4. 【掌握】LayUI内置模块二、LayUI基本使用 2.1 概念 layui&#xff08;谐音&#xff1a;类UI) 是一款采用自身模块规范编写的前端 UI 框架&#xff0…

阿里云nginx配置https踩坑(配置完后访问显示无法访问此网站)

本人小前端一枚&#xff0c;最近在玩服务器部署自己的东西时踩了个坑&#xff01;&#xff01;&#xff01; server {listen 443 ssl;server_name localhost;ssl_certificate 证书.com.pem;ssl_certificate_key 证书.com.key;#后台管理静态资源存放location / { #文件目…

springboot+vue新闻稿件java在线投稿管理系统

本文介绍了新闻稿件管理系统的开发全过程。通过分析新闻稿件管理系统管理的不足&#xff0c;创建了一个计算机管理新闻稿件管理系统的方案。文章介绍了新闻稿件管理系统的系统分析部分&#xff0c;包括可行性分析等&#xff0c;系统设计部分主要介绍了系统功能设计和数据库设计…

微信小程序开发实战 ②②(全局数据共享)

作者 : SYFStrive 博客首页 : HomePage &#x1f4dc;&#xff1a; 微信小程序 &#x1f4cc;&#xff1a;个人社区&#xff08;欢迎大佬们加入&#xff09; &#x1f449;&#xff1a;社区链接&#x1f517; &#x1f4cc;&#xff1a;觉得文章不错可以点点关注 &#x1f4…