16.3:岛屿数量问题2

news2024/12/29 10:05:26

岛屿数量问题2

https://leetcode.cn/problems/number-of-islands-ii/

给你一个大小为 m x n 的二进制网格 grid 。网格表示一个地图,其中,0 表示水,1 表示陆地。最初,grid 中的所有单元格都是水单元格(即,所有单元格都是 0)。

可以通过执行 addLand 操作,将某个位置的水转换成陆地。给你一个数组 positions ,其中 positions[i] = [ri, ci] 是要执行第 i 次操作的位置 (ri, ci)

返回一个整数数组 answer ,其中 answer[i] 是将单元格 (ri, ci) 转换为陆地后,地图中岛屿的数量。

岛屿 的定义是被「水」包围的「陆地」,通过水平方向或者垂直方向上相邻的陆地连接而成。你可以假设地图网格的四边均被无边无际的「水」所包围。

示例 1:

img
输入:m = 3, n = 3, positions = [[0,0],[0,1],[1,2],[2,1]]
输出:[1,1,2,3]
解释:
起初,二维网格 grid 被全部注入「水」。(0 代表「水」,1 代表「陆地」)
- 操作 #1:addLand(0, 0) 将 grid[0][0] 的水变为陆地。此时存在 1 个岛屿。
- 操作 #2:addLand(0, 1) 将 grid[0][1] 的水变为陆地。此时存在 1 个岛屿。
- 操作 #3:addLand(1, 2) 将 grid[1][2] 的水变为陆地。此时存在 2 个岛屿。
- 操作 #4:addLand(2, 1) 将 grid[2][1] 的水变为陆地。此时存在 3 个岛屿。

示例 2:

输入:m = 1, n = 1, positions = [[0,0]]
输出:[1]

提示:

  • 1 <= m, n, positions.length <= 104
  • 1 <= m * n <= 104
  • positions[i].length == 2
  • 0 <= ri < m
  • 0 <= ci < n

理解题目

根据 public static List numIslands21(int m, int n, int[] [] positions) {},其中m指的是二维数组的行,n指的是二维数组的列,二维数组positions指的是传入的1的节点坐标。

举个例子:m = 2,n=3,positions = {{0,0},{0,2},{1,1},{1,2}},指的是在两行三列的二维数组中的{0,0},{0,2},{1,1},{1,2}位置都是1,其他位置都是0。注意这里的坐标顺序是打乱的,出现的顺序也是打乱的。

最重要的一点是:当你遍历到{0,2}位置的时候,后面没便利到的{1,1},{1,2}位置就当是0,遍历到的{0,0}位置是1.所以你可以简单的想象为遍历到哪哪就出现一个1.

思路

1:我打算用并查集的方法,但是这里的1都是随机出现的,所以我们没法一上来就进行father数组与size数组的初始化(怎样的初始化如下)

	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++;
                    }
                }
            }
        }

我们需要动态的初始化,就是说遇到谁就初始化谁的father与size。

2:我知道合并的时候,如果两个数可以合并,那么一定会伴随着sets–,以及size[small] = 0。集合的数量一定会减少,并且small祖先的集合数清零。但在这里我们用size[k] != 0来表示节点是岛屿,size[k] == 0来表示节点是水。所以当集合合并后不可以直接清零,保持不动即可。方便新出节点判别当前节点是岛屿。

package algorithmbasic.class16;
// https://leetcode.cn/problems/number-of-islands-ii/

import java.util.ArrayList;
import java.util.List;

public class numIslands2 {

    public static List<Integer> numIslands21(int m, int n, int[][] positions) {
        //对并查集进行初始化操作,创建好一维数组father,size,以及辅助数组help。设置记录集合数量的变量sets。
        UnionFind1 unionFind1 = new UnionFind1(m, n);
        List<Integer> ans = new ArrayList<>();
        for (int[] p : positions) {// -------------------------- 二维数组遍历的新方法。
            ans.add(unionFind1.connect(p[0], p[1]));
        }
        return ans;
    }

    /**
     * 并查集内部类。
     */
    public static class UnionFind1 {
        /**
         * 属性
         */
        static int[] father;
        static int[] size;
        static int[] help;
        static int sets;
        //列的长度大小
        static int L;
        //行的长度大小
        static int H;

        /**
         * 构造器
         * 这里我们没有一上来就直接进行初始化操作,我们需要走一步看一步。
         */
        public UnionFind1(int m, int n) {
            father = new int[m * n];
            size = new int[m * n];
            help = new int[m * n];
            sets = 0;
            L = n;
            H = m;
        }

        /**
         * index方法
         * 传入二维数组的行列参数:i,j 找到对应的一维数组的下标 k
         * 公式:K = i * positions[0].length + j
         */

        public static int index(int i, int j) {
            return i * L + j;
        }

        /**
         * findAncestor方法
         * 传入一个节点,然后从这个节点开始一直往上找,直到直到最上边为止,返回最上面的节点。
         */
        public static int findAncestor(int cur) {
            int j = 0;
            while (cur != father[cur]) {
                //进行优化,将途径的节点进行记录。
                help[j++] = cur;
                cur = father[cur];
            }
            //cur == father[cur]
            j--;
            while (j >= 0) {
                father[help[j--]] = cur;
            }
            return cur;
        }


        /**
         * union合并方法
         * 将 i,j   m,n两个位置所在的集合进行合并
         */

        public static void union(int i, int j, int m, int n) {
            //防止越界条件
            if (m < 0 || m >= H || n < 0 || n >= L) {
                return;
            }
            //根据二维数组的下标找到一维数组的下标。
            int i1 = index(i, j);
            int i2 = index(m, n);
            //只有都是岛屿时才可以合并。
            if (size[i1] != 0 && size[i2] != 0) {
                //找到各自节点的祖先节点。
                int fatherA = findAncestor(i1);
                int fatherB = findAncestor(i2);
                //只有祖先不同时才可以合并,防止祖先相同时反复合并,反复的sets--,导致数据不准确。
                if (fatherA != fatherB) {
                    //找到祖先节点fatherA所在集合大小sizeA。
                    int sizeA = size[fatherA];
                    //找到祖先节点fatherB所在集合大小sizeB。
                    int sizeB = size[fatherB];
                    //big指向集合数量较多的祖先节点。
                    int big = sizeA > sizeB ? fatherA : fatherB;
                    //small指向集合数量较少的祖先节点。
                    int small = big == fatherA ? fatherB : fatherA;
                    //进行合并
                    //注意这个地方size[small]不要被置为0.
                    father[small] = big;
                    size[big] = size[big] + size[small];
                    sets--;
                }
            }
        }


        /**
         * connect连接方法
         * 传入两个参数 i,j 说明二维数组positions的 i,j位置出现了1,然后对其进行相连,连接完之后,返回二维数组目前一共有多少集合。
         */
        public static int connect(int i, int j) {
            int k = index(i, j);
            //之前
            if (size[k] == 0) {
                //初始化当前位置的father与size数组。
                size[k] = 1;
                father[k] = k;
                sets++;
                //进行上下左右的合并。
                union(i, j, i + 1, j);
                union(i, j, i - 1, j);
                union(i, j, i, j + 1);
                union(i, j, i, j - 1);
            }
            return sets;
        }
    }
}

时间复杂度

时间复杂度是O(m * n + k),k指的是position数组中1的数量为k。

m*n是初始化并查集的复杂度,因为在构造并查集对象的时候,会为并查集对象的数据结构分配空间并进行初始化操作。

father = new int[m * n]; size = new int[m * n]; help = new int[m * n];时间复杂度都是m*n。

方法二:

方法一讲的如果m*n比较大,会经历很重的初始化,而k比较小,所以采取如下方法,其时间复杂度是O(k)

只需要初始化是1的节点,其他节点不需要初始化。

package algorithmbasic.class16;
// https://leetcode.cn/problems/number-of-islands-ii/

import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;

/**
 * 方法二:时间复杂度是O(K)
 * 课上讲的如果m*n比较大,会经历很重的初始化,而k比较小,怎么优化的方法
 */

public class numIslands2_2 {

    public static List<Integer> numIslands22(int m, int n, int[][] positions) {
        List<Integer> ans = new ArrayList<>();
        UnionFind2 unionFind2 = new UnionFind2(m, n);
        for (int[] p : positions) {
            ans.add(unionFind2.connect(p[0], p[1]));
        }
        return ans;
    }


    /**
     * 并查集内部类
     */
    public static class UnionFind2 {
        /**
         * 属性
         */
        //之前写的并查集我们需要额外建立一个Dot类来区分相同的元素
        //其实可以直接用一个String来区分,比如说3行4列 写成 ->"3_4"
        static HashMap<String, String> fatherMap;
        static HashMap<String, Integer> sizeMap;
        static int sets;
        //行
        static int H;
        //列
        static int L;
        //辅助数组
        static ArrayList<String> list;

        /**
         * 构造器
         */
        public UnionFind2(int m, int n) {
            fatherMap = new HashMap<>();
            sizeMap = new HashMap<>();
            list = new ArrayList<>();
            sets = 0;
            this.H = m;
            this.L = n;
        }

        /**
         * findAncestor方法
         * 创建一个参数,直至找到他的祖先
         */
        public static String findAncestor(String k) {

            while (!fatherMap.get(k).equals(k)) {
                list.add(k);
                k = fatherMap.get(k);
            }
            // k == fatherMap.get(k).
            for (String s : list) {
                fatherMap.put(s, k);
            }
            list.clear();
            return k;
        }

        /**
         * union方法
         */
        public static void union(String key, String k) {
            //如果有传进来的k,就进行合并。
            if (fatherMap.containsKey(k)) {
                //找到key的祖先fatherA
                String fatherA = findAncestor(key);
                //找到k的祖先fatherB
                String fatherB = findAncestor(k);
                //如果两个祖先不一样的就合并。
                if (!fatherA.equals(fatherB)) {
                    //big指向集合比较大的祖先
                    String big = sizeMap.get(fatherA) > sizeMap.get(fatherB) ? fatherA : fatherB;
                    String small = big == fatherA ? fatherB : fatherA;
                    //进行合并
                    fatherMap.put(small, big);
                    sizeMap.put(big, sizeMap.get(big) + sizeMap.get(small));
                    //sizeMap.remove(small);
                    //这个地方还不能remove,因为有可能其他节点会连接他。
                    sets--;
                }
            }
        }

        /**
         * connect方法
         * 传入两个参数 i,j 说明二维数组positions的 i,j位置出现了1,然后对其进行相连,连接完之后,返回二维数组目前一共有多少集合。
         */

        public static int connect(int i, int j) {
            //先判断一下这个坐标之前出现过没,如果之前出现过,说明早已连接好,就跳过
            String key = String.valueOf(i) + "_" + String.valueOf(j);
            if (!fatherMap.containsKey(key)) {
                //进行动态的初始化。
                fatherMap.put(key, key);
                sizeMap.put(key, 1);
                sets++;
                //上并进行边界判断
                String up = (i - 1 < H && i >= 0) ? String.valueOf(i - 1) + "_" + String.valueOf(j) : null;
                if (up != null) union(key, up);
                //下
                String down = (i + 1 < H && i >= 0) ? String.valueOf(i + 1) + "_" + String.valueOf(j) : null;
                if (down != null) union(key, down);
                //左
                String left = (j - 1 < L && j >= 0) ? String.valueOf(i) + "_" + String.valueOf(j - 1) : null;
                if (left != null) union(key, left);
                //右
                String right = (j + 1 < L && j >= 0) ? String.valueOf(i) + "_" + String.valueOf(j + 1) : null;
                if (right != null) union(key, left);

                union(key, up);
                union(key, down);
                union(key, left);
                union(key, right);
            }
            return sets;
        }
    }

    public static void main(String[] args) {
        int[][] arr = {{0, 1}, {1, 2}, {2, 1}, {1, 0}, {0, 2}, {0, 0}, {1, 1}};
        List<Integer> list = numIslands2_2.numIslands22(3, 3, arr);
        System.out.println(list);
    }
}

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

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

相关文章

Dubbo源码解析一网络通信原理

Dubbo 网络通信原理 1. Dubbo高可用集群1.1 服务集群的概述1.1.1 服务集群的概述1.1.2 调用过程1.1.3 组件介绍 1.2 集群容错机制1.2.1 内置集群容错策略1.2.1.1 Failover(失败自动切换)1.2.1.2 Failsafe(失败安全)1.2.1.3 Failfast(快速失败)1.2.1.4 Failback(失败自动恢复)1.…

卡尔曼滤波 | Matlab实现利用卡尔曼滤波器估计电池充电状态(Kalman Filtering)

文章目录 效果一览文章概述研究内容程序设计参考资料效果一览 文章概述 卡尔曼滤波 | Matlab实现利用卡尔曼滤波器估计电池充电状态(Kalman Filtering) 研究内容

gyp verb `which` failed Error: not found: python2

安装node-sass居然需要python2,7环境&#xff0c;不能python3 我只能重新降版本&#xff1a; python2.7:https://www.python.org/ftp/python/2.7/python-2.7.amd64.msi npm ERR! code 1 npm ERR! path F:\idea2021work\music01 初始化\music-client\node_modules\node-sass np…

自然语言处理从入门到应用——自然语言处理的基础任务:词性标注(POS Tagging)和句法分析(Syntactic Parsing)

分类目录&#xff1a;《自然语言处理从入门到应用》总目录 词性标注 词性是词语在句子中扮演的语法角色&#xff0c;也被称为词类&#xff08;Part-Of-Speech&#xff0c;POS&#xff09;。例如&#xff0c;表示抽象或具体事物名字&#xff08;如“计算机”&#xff09;的词被…

【遗传算法简介】

遗传算法&#xff1a;原理与实战 简介 遗传算法是一种模拟达尔文生物进化论的自然选择以及遗传学机制的搜索算法&#xff0c;由 John Holland 在20世纪70年代提出。它们在各种搜索、优化和机器学习任务中已被广泛应用。 遗传算法原理 1. 编码 遗传算法的第一步是将问题的可…

Andriod开发 Room 数据库处理框架

1.Room框架 Room是Android Jetpack组件库中的一部分&#xff0c;它是一个SQLite数据库的抽象层&#xff0c;提供了更简单的API和更好的性能&#xff0c;适合于中大型应用程序。 2.Room的使用 使用Room和之前使用SQLite搭建数据库的过程类似&#xff0c;但是更加简单了。 1&…

JAVA网络编程(一)

一、什么是网络编程 定义&#xff1a;在网络通信协议下&#xff0c;不同计算机上运行的程序&#xff0c;进行的数据传输。 应用场景&#xff1a;即时通信&#xff0c;网游&#xff0c;邮件等 不管什么场景&#xff0c;都是计算机与计算机之间通过网络在进行数据传输 java提供一…

软件测试必会:cookie、session和token的区别

今天就来说说session、cookie、token这三者之间的关系&#xff01;最近这仨玩意搞得头有点大&#x1f923; 01、为什么会有它们三个 我们都知道 HTTP 协议是无状态的&#xff0c;所谓的无状态就是客户端每次想要与服务端通信&#xff0c;都必须重新与服务端链接&#xff0c;意…

穿针引线之 AsyncLocalStorage

在 Node.js 中&#xff0c;如何更优雅地获取请求上下文一直是一个问题&#xff0c;看一下下面的例子。 背景 const http require(http); function handler1(req, res) {console.log(req.url); }function handler2(req, res) {console.log(req.url); }http.createServer((req…

【react全家桶】react-Hook (下)

本人大二学生一枚&#xff0c;热爱前端&#xff0c;欢迎来交流学习哦&#xff0c;一起来学习吧。 <专栏推荐> &#x1f525;&#xff1a;js专栏 &#x1f525;&#xff1a;vue专栏 &#x1f525;&#xff1a;react专栏 文章目录 15【react-Hook &#xff08;下&#x…

进程控制(Linux)

进程控制 fork 在Linux中&#xff0c;fork函数是非常重要的函数&#xff0c;它从已存在进程中创建一个新进程。新进程为子进程&#xff0c;而原进程为父进程。 返回值&#xff1a; 在子进程中返回0&#xff0c;父进程中返回子进程的PID&#xff0c;子进程创建失败返回-1。 …

Spring - BeanFactory与ApplicationContext介绍

文章目录 Spring Bean一、BeanFactory 快速入门1.1 BeanFactory 开发步骤1.2 DI依赖注入 二、ApplicationContext快速入门2.1 入门2.2 BeanFactory 与 ApplicationContext关系2.3 BeanFactory 继承体系2.4 ApplicationContext 继承体系 Spring Bean 之前也了解过Spring Bean&a…

高斯过程回归 | Matlab实现高斯过程回归多输入单输出预测(Gaussian Process Regression)

文章目录 效果一览文章概述研究内容程序设计参考资料效果一览 文章概述 高斯过程回归 | Matlab实现高斯过程回归多输入单输出预测(Gaussian Process Regression) 研究内容 高斯过程回归(Gaussian Process Regression,GPR)是一种基于概率模型的非参数回归方法,可以用于

mybatisplus数据权限插件学习初探 动态表名更换插件

文章目录 学习链接 mybatisplus数据权限插件学习初探前言案例建表用户表订单表 环境准备UserUserMapperUserMapper.xmlOrdersOrdersMapperOrdersMapper.xml 配置UserTypeEnumUserContextHolderCustomizeDataPermissionHandlerMybatisPlusConfig 测试测试类bossdeptManagerclerk…

Zinx框架学习 - 消息封装

Zinx - V0.5 消息封装 之前我们使用Request来保存服务器的数据&#xff0c;很显然使用[]byte来接收数据&#xff0c;没有长度也没有消息类型&#xff0c;接下来就要针对这个消息进行封装 创建消息类型 定义一个基本的message包&#xff0c;会包含消息ID、数据、数据长度三个…

路径规划算法:基于探路者优化的路径规划算法- 附代码

路径规划算法&#xff1a;基于探路者优化的路径规划算法- 附代码 文章目录 路径规划算法&#xff1a;基于探路者优化的路径规划算法- 附代码1.算法原理1.1 环境设定1.2 约束条件1.3 适应度函数 2.算法结果3.MATLAB代码4.参考文献 摘要&#xff1a;本文主要介绍利用智能优化算法…

涉及float和double

文章目录 涉及float和double的问题&#xff1a;它们的存储方式&#xff1a;有效位&#xff1f; 链式结构 涉及float和double的问题&#xff1a; 它们的存储方式&#xff1a; 它们会分成小数部分和指数部分分别存储。小数部分的有效位数越多&#xff0c;精度就越高&#xff0c;…

NLP超详细新手快速入门上手篇(1)常用函数

前言 自然语言处理(NLP)是机器学习的应用之一&#xff0c;用于分析、理解和生成自然语言&#xff0c;以便人类与计算机&#xff0c;人类与人类更好的交流。自然语言处理按照任务类型可以分为分类、匹配、翻译、结构化预测、与序贯决策过程这五类。 本篇参考自TensorFlow官方文…

MyBatis 查询数据库

✏️作者&#xff1a;银河罐头 &#x1f4cb;系列专栏&#xff1a;JavaEE &#x1f332;“种一棵树最好的时间是十年前&#xff0c;其次是现在” 目录 MyBatis 是什么&#xff1f;第⼀个MyBatis查询创建数据库和表添加MyBatis框架支持设置 MyBatis 配置信息添加业务代码 查询操…

【VBA】实现批量生成二维码

系列文章 【C#】单号生成器&#xff08;编号规则、固定字符、流水号、产生业务单号&#xff09; 本文链接&#xff1a;https://blog.csdn.net/youcheng_ge/article/details/129129787 【C#】日期范围生成器&#xff08;开始日期、结束日期&#xff09; 本文链接&#xff1a;h…