16.2:岛屿数量问题

news2025/1/9 8:06:26

文章目录

  • 岛屿数量问题
  • 方法一:采用递归的方法
  • 方法二:使用并查集的方法(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;
        }
    }

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

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

大数据:分布式计算,MapReduce,hadoop的计算组件,hive是sql分布式计算框架,底层就是基于MapReduce的

大数据&#xff1a;分布式计算&#xff0c;MapReduce&#xff0c;hadoop的计算组件&#xff0c;hive是sql分布式计算框架&#xff0c;底层就是基于MapReduce的 2022找工作是学历、能力和运气的超强结合体&#xff0c;遇到寒冬&#xff0c;大厂不招人&#xff0c;可能很多算法学…

【C++】一文带你入门 STL

一 STL 组成 graph LRA[STL] --- B[容器 container]A --- C[配接器 adapter]A --- D[迭代器 iterator]A --- E[仿函数 function]A --- F[算法 algorithm]A --- G[空间配置器 allocator]二 常用容器 容器简介 下面我们来简单看一下这些容器的常用接口的使用&#xff0c;并分析…

更新中-深度学习实战中遇到的一些概念+少量代码

onnx ONNX 是一种用于机器学习模型的开放式表示格式&#xff0c;它可以让不同的深度学习框架之间共享模型。 import onnxruntime # 加载模型 session onnxruntime.InferenceSession(model.onnx) # 运行模型。第一个参数是输出变量列表&#xff0c;不指定的话返回所有值 outp…

ESP8266使用MicroPython接入ThingsBoard

1、概述 我们老大当初叫我学习microPython,这个可以直接将代码发到板子上,然后就可以跑,就相当于设备业务代码由我们来写,不仅仅是让嵌入式来写,嵌入式做的就是封装函数,我们可以调用.最终这个还是实现了,但是没有推广. 2、设备 我自己购买的设备是ESP8266,某宝上购买的,mic…

智能仓储系统哪家公司做的比较好?求推荐排名不错的智能仓储公司?

什么是仓储服务信息平台&#xff1f;仓储服务信息平台可以为企业提供哪些便利&#xff1f; 随着电商和物流行业的快速发展&#xff0c;仓储服务越来越受到人们的关注。为了更好地管理仓储服务&#xff0c;提高效率&#xff0c;降低成本&#xff0c;仓储服务信息平台也应运而生…

CTF国赛2023 - ukfc(四道逆向已下班)

没啥好说的&#xff0c;惜败已复现&#xff1a;badkey1、国粹、ezbyte、moveAside、ezAndroid Notice&#xff1a;复现时候的一些题解来源于各大战队的wp&#xff0c;比如F61d&#xff0c;侵删 Re ezbyte 首先跟踪很容易分析到前后缀 至于里面的&#xff0c;得知道是dwarf…

哪个牌子的电容笔好用?Apple Pencil平替

随着时代的进步&#xff0c;数码产品在人们日常生活中的使用频率越来越高&#xff0c;一个iPad和一支电容笔似乎已然成为人们主要的学习工具了。电容笔的发展速度很快&#xff0c;在众多的电容笔牌子中&#xff0c;什么牌子好用又便宜&#xff1f;下面&#xff0c;我来给大家推…

深度学习进阶篇[8]:对抗神经网络GAN基本概念简介、纳什均衡、生成器判别器、解码编码器详解以及GAN应用场景

【深度学习入门到进阶】必看系列&#xff0c;含激活函数、优化策略、损失函数、模型调优、归一化算法、卷积模型、序列模型、预训练模型、对抗神经网络等 专栏详细介绍&#xff1a;【深度学习入门到进阶】必看系列&#xff0c;含激活函数、优化策略、损失函数、模型调优、归一化…

FastReport Business Graphics .NET 2023.1 Crack

FastReport 商业图形 .NET .NET WinForms 的数据可视化库 使用 FastReport 业务图形库&#xff0c;您可以可视化不同层次的数据&#xff0c;构建业务图表以供进一步分析和决策。 所有这些都可以直接在您的应用程序中工作&#xff01; 利用 .NET 7、.NET Core、Blazor、ASP.NE…

数据采集学习心得

数据采集是指从互联网或其他数据源中收集数据的过程。在当今信息时代&#xff0c;数据采集已经成为了一项非常重要的技能。无论是在商业领域还是学术领域&#xff0c;数据采集都是非常必要的。本文将分享我在学习数据采集过程中的心得体会。 一、数据采集的重要性 在当今信息…

高考即将到来,如何选择大学和专业?(2023版本)

同学们&#xff0c;高考又快到了&#xff0c;怎么选择大学和专业一直是同学们心烦的事情&#xff0c;正所谓“选择大于努力”&#xff0c;选择错了&#xff0c;努力方向自然就错了&#xff0c;事倍功半&#xff0c;甚至南辕北辙&#xff0c;所以对此我们必需慎之又慎&#xff0…

Mybatis自定义分页插件及PageHelper源码分析

文章目录 前言一、自定义一个简单的mybatis分页插件&#xff1f;1.判断当前传参是不是一个Page&#xff0c;如果是page就进行转换。2.分页查询总条数3.修改原有sql4.执行原有方法5.存在问题&#xff1a; 二、PageHelper分析1.PageHelper简介2.PageHelper源码分析 三&#xff1a…

概率论:假设检验

参考书目&#xff1a;《行为科学统计精要》&#xff08;第八版&#xff09;——弗雷德里克J格雷维特 1、假设检验预备知识 Z分位数样本均值的分布标准误 参考&#xff1a; 概率论&#xff1a;样本与总体分布&#xff0c;Z分数与概率_格勒王的博客-CSDN博客如何理解样本和整体…

Linux入门到进阶

文章目录 前言一、第一章-初识Linux1.初识Linux2.虚拟机介绍3.VMware Workstation安装4.在VMware上安装Linux5.远程连接Linux系统6.扩展&#xff1a;WSL&#xff08;Windows Subsystem for Linux&#xff09;7.扩展&#xff1a;虚拟机快照 二、第二章-Linux基础命令1.Linux的目…

AMEYA360:纳芯微推出车规级耐高压、三线霍尔开关及锁存器NSM101x系列

纳芯微推出全新三线制车规霍尔效应开关/锁存器NSM101x系列&#xff0c;为数字位置检测提供高精度的解决方案&#xff0c;可被广泛应用于汽车执行器等的位置检测。 NSM101x产品系列包含了3个产品型号&#xff0c;即NSM1011(单极霍尔开关)、NSM1012(全极霍尔开关)、NSM1013(霍尔锁…

oracle19c介绍和windows上安装

目录 一、版本 &#xff08;1&#xff09;历史 &#xff08;2&#xff09;11g和12c管理方式区别 11g 12C &#xff08;3&#xff09;各个版本对操作系统要求 二、分类 &#xff08;1&#xff09;分为桌面类和服务器类 &#xff08;2&#xff09;分为企业版和标准版 三…

后端(一):Tomcat

我们之前的前端是被我们一笔带过的&#xff0c;那不是我们要讲的重点&#xff0c;而这里的后端则是重点。本章先来认识认识后端的基础。 Tomcat 是什么 我们先来聊聊什么叫做tomcat&#xff0c;我们熟悉的那个是汤姆猫&#xff1a; 这和我们Java世界中的Tomcat 不是同一只猫&…

包含合并单元格的表格快速排序

实例需求&#xff1a;现需要将原料配方成分表按照“原料含量”从高到低排序&#xff0c;如下图所示。由于表格中包含合并单元格&#xff0c;因此Excel的排序功能无法正常。 示例代码如下。 Sub demo()Dim data(), i, idx, k, slstRow Cells(Rows.Count, 2).End(xlUp).RowReDi…

02-项目系统架构

1、为何选择xxxxx 1.1、高并发场景有哪些&#xff1f; 商品秒杀&#xff0c;双11 微信支付宝平台 微博突发热点 用户操作日志 购票平台 1.2、为何选择xxxxx 业务复杂度高于淘宝双11&#xff0c;考验个人程序设计能力 动态库存 选座功能 线上线下 持续高并发业务&…

5G宏基站的形态5G基站长什么样?

据说&#xff0c;全国建了约273.3万个5G基站。 真是春城无处不飞花&#xff0c;5G遍布千万家。 今天我们换个轻松的话题&#xff0c;来看看春光下的5G宏基站。 胜日寻芳泗水滨&#xff0c;无边光景一时新。 等闲识得东风面&#xff0c;万紫千红总是春。 古人在春游时寻芳&am…