用javascript分类刷leetcode23.并查集(图文视频讲解)

news2024/12/26 21:02:43

并查集(union & find):用于处理一些元素的合并和查询问题

Find:确定元素属于哪一个子集,他可以被用来确定两个元素是否属于同一个子集,加入路径压缩,复杂度近乎O(1)

Union:将两个子集合并成同一个集合

ds_88

//                    0,1,2,3
//parent:        0,1,2,3
//size:         1,1,1,1
class UnionFind{
    constructor(n){ //构造一个大小为n的集合
        this.count = n
        this.parent = new Array(n)   
        this.size = new Array(n)  // size数组记录着每棵树的大小
        for (let i = 0; i < n; i++) {
            this.parent[i] = i; // 自己是自己的parent
            this.size[i] = 1;
        }
    }

    union(p,q){ //连通结点p和结点q, p和q都是索引
        let rootP = this.find(p);
        let rootQ = this.find(q);
        if(rootP === rootQ) return
        // 元素数量小的接到数量多的下面,这样比较平衡
        if (this.size[rootP] > this.size[rootQ]) {
            this.parent[rootQ] = rootP;
            this.size[rootP] += this.size[rootQ];
        } else {
            this.parent[rootP] = rootQ;
            this.size[rootQ] += this.size[rootP];
        }
        this.count--;
    }

    isConnected(p, q) { //判断p,q是否连通
        return this.find(p)=== this.find(q) 
    }

    find(x) { //找到x结点的root
        while (this.parent[x] != x) {
            // 进行路径压缩
            this.parent[x] = this.parent[this.parent[x]];
            x = this.parent[x];
        }
        return x;
    }

    getCount() { //返回子集个数
        return this.count;
    }
}

//                    0,1,2,3
//parent:        0,1,2,3
//rank:         1,1,1,1
//采用rank优化
class UnionFind {
    constructor(n) { //构造一个节点数为n的集合
        this.count = n //并查集总数
        this.parent = new Array(n)
        this.rank = new Array(n)  // rank数组记录着每棵树的重量
        for (let i = 0; i < n; i++) {
            this.parent[i] = i; // 自己是自己的parent
            this.rank[i] = 1;    //每个集合上节点的数量
        }
    }

    union(p, q) { //连通结点p和结点q, p和q都是索引
        let rootP = this.find(p);
        let rootQ = this.find(q);
        if (rootP === rootQ) return
        // 深度小的接在深度大元素下
        if (this.rank[rootP] > this.rank[rootQ]) {
            this.parent[rootQ] = rootP;
        } else if (this.rank[rootP] < this.rank[rootQ]) {
            this.parent[rootP] = rootQ;
        } else {
            this.parent[rootP] = rootQ;
            this.rank[rootQ]++
        }
        this.count--;
    }

    isConnected(p, q) { //判断p,q是否连通
        return this.find(p) === this.find(q)
    }

    find(x) { //找到x结点的root
        while (this.parent[x] != x) {
            // 进行路径压缩
            this.parent[x] = this.parent[this.parent[x]];
            x = this.parent[x];
        }
        return x;
    }

    getCount() { //返回子集个数
        return this.count;
    }
}

200. 岛屿数量 (medium)

给你一个由 ‘1’(陆地)和 ‘0’(水)组成的的二维网格,请你计算网格中岛屿的数量。

岛屿总是被水包围,并且每座岛屿只能由水平方向和/或竖直方向上相邻的陆地连接形成。

此外,你可以假设该网格的四条边均被水包围。

示例 1:

输入:grid = [
[“1”,“1”,“1”,“1”,“0”],
[“1”,“1”,“0”,“1”,“0”],
[“1”,“1”,“0”,“0”,“0”],
[“0”,“0”,“0”,“0”,“0”]
]
输出:1
示例 2:

输入:grid = [
[“1”,“1”,“0”,“0”,“0”],
[“1”,“1”,“0”,“0”,“0”],
[“0”,“0”,“1”,“0”,“0”],
[“0”,“0”,“0”,“1”,“1”]
]
输出:3

提示:

m == grid.length
n == grid[i].length
1 <= m, n <= 300
grid[i][j] 的值为 ‘0’ 或 ‘1’

动画过大,点击查看

方法1.dfs
  • 思路:循环网格,深度优先遍历每个坐标的四周,注意坐标不要越界,遇到陆地加1,并沉没四周的陆地,这样就不会重复计算
  • 复杂度:时间复杂度O(mn), m和n是行数和列数。空间复杂度是O(mn),最坏的情况下所有网格都需要递归,递归栈深度达到m * n

js:

const numIslands = (grid) => {
    let count = 0
    for (let i = 0; i < grid.length; i++) {
        for (let j = 0; j < grid[0].length; j++) {//循环网格
            if (grid[i][j] === '1') {//如果为陆地,count++,
                count++
                turnZero(i, j, grid)
            }
        }
    }
    return count
}
function turnZero(i, j, grid) {//沉没四周的陆地
    if (i < 0 || i >= grid.length || j < 0
        || j >= grid[0].length || grid[i][j] === '0') return //检查坐标的合法性
    grid[i][j] = '0'//让四周的陆地变为海水
    turnZero(i, j + 1, grid)
    turnZero(i, j - 1, grid)
    turnZero(i + 1, j, grid)
    turnZero(i - 1, j, grid)
}
方法2.bfs
  • 思路:循环网格,广度优先遍历坐标的四周,遇到陆地加1,沉没四周的陆地,不重复计算陆地数
  • 复杂度:时间复杂度O(mn),m和n是行数和列数。空间复杂度是O(min(m,n)),队列的长度最坏的情况下需要能容得下m和n中的较小者

js:

const numIslands = (grid) => {
    let count = 0
    let queue = []
    for (let i = 0; i < grid.length; i++) {
        for (let j = 0; j < grid[0].length; j++) {
            if (grid[i][j] === '1') {
                count++
                grid[i][j] = '0' // 做标记,避免重复遍历
                queue.push([i, j]) //加入队列
                turnZero(queue, grid)
            }
        }
    }
    return count
}
function turnZero(queue, grid) {
    const dirs = [[0, 1], [1, 0], [0, -1], [-1, 0]]
    while (queue.length) {//当队列中还有元素的时候 
        const cur = queue.shift() //取出队首元素
        for (const dir of dirs) {//四个方向广度优先扩散
            const x = cur[0] + dir[0]
            const y = cur[1] + dir[1]
            if (x < 0 || x >= grid.length || y < 0 || y >= grid[0].length || grid[x][y] !== '1') {
                continue
            }//检查坐标合法性
            grid[x][y] = '0' //沉没陆地
            queue.push([x, y]) //四周的节点加入队列
        }
    }
}
方法3.并查集
  • 思路:
  • 复杂度:时间复杂度O(mn),时间复杂度其实是O(mn * f(mn)),f是采用并查集路径压缩时的复杂度,为常数,所以可以忽略。 m和n是行数和列数。空间复杂度是O(mn),并查集的空间

js:

class UnionFind {
    constructor(n) { //构造一个节点数为n的集合
        this.count = n //并查集总数
        this.parent = new Array(n)
        this.size = new Array(n)  // size数组记录着每棵树的重量
        for (let i = 0; i < n; i++) {
            this.parent[i] = i; // 自己是自己的parent
            this.size[i] = 1;    //每个集合上节点的数量
        }
    }

    union(p, q) { //连通结点p和结点q, p和q都是索引
        let rootP = this.find(p);
        let rootQ = this.find(q);
        if (rootP === rootQ) return
        // 元素数量小的接到数量多的下面,这样比较平衡
        if (this.size[rootP] > this.size[rootQ]) {
            this.parent[rootQ] = rootP;
            this.size[rootP] += this.size[rootQ];
        } else {
            this.parent[rootP] = rootQ;
            this.size[rootQ] += this.size[rootP];
        }
        this.count--;
    }

    isConnected(p, q) { //判断p,q是否连通
        return this.find(p) === this.find(q)
    }

    find(x) { //找到x结点的root
        while (this.parent[x] != x) {
            // 进行路径压缩
            this.parent[x] = this.parent[this.parent[x]];
            x = this.parent[x];
        }
        return x;
    }

    getCount() { //返回子集个数
        return this.count;
    }

}

var numIslands = function (grid) {
    let m = grid.length
    if (m === 0) return 0
    let n = grid[0].length
    const dummy = -1
    const dirs = [[1, 0], [0, 1]]//方向数组 向右 向下
    const uf = new UnionFind(m * n)
    for (let x = 0; x < m; x++) {
        for (let y = 0; y < n; y++)
            if (grid[x][y] === '0') {//如果网格是0,则和dummy合并
                uf.union(n * x + y, dummy) 
            }
            else if (grid[x][y] === '1') {//如果网格是1,则向右 向下尝试
                for (let d of dirs) {
                    let r = x + d[0]
                    let c = y + d[1]
                    if (r >= m || c >= n) continue //坐标合法性
                    if (grid[r][c] === '1') { //当前网格的右边 下面如果是1,则和当前网格合并
                        uf.union(n * x + y, n * r + c)
                    }
                }
            }
    }
    return uf.getCount()  //返回并查集的个数减一就行
};

547. 省份数量(medium)

有 n 个城市,其中一些彼此相连,另一些没有相连。如果城市 a 与城市 b 直接相连,且城市 b 与城市 c 直接相连,那么城市 a 与城市 c 间接相连。

省份 是一组直接或间接相连的城市,组内不含其他没有相连的城市。

给你一个 n x n 的矩阵 isConnected ,其中 isConnected[i][j] = 1 表示第 i 个城市和第 j 个城市直接相连,而 isConnected[i][j] = 0 表示二者不直接相连。

返回矩阵中 省份 的数量。

示例 1:

输入:isConnected = [[1,1,0],[1,1,0],[0,0,1]]
输出:2

示例 2:

输入:isConnected = [[1,0,0],[0,1,0],[0,0,1]]
输出:3

提示:

1 <= n <= 200
n == isConnected.length
n == isConnected[i].length
isConnected[i][j] 为 1 或 0
isConnected[i][i] == 1
isConnected[i][j] == isConnected[j][i]

方法1.dfs
  • 思路:深度优先遍历,visited记录是否访问过,循环省份数组,递归寻找isConnected矩阵中相邻的城市。
  • 复杂度:时间复杂度O(n^2),n是城市的数量,遍历矩阵中的每个元素。空间复杂度O(n),递归深度不超过n

js

var findCircleNum = function(isConnected) {
  const rows = isConnected.length;
  const visited = new Set();//记录是否访问过
  let count = 0;//省份数量
  for (let i = 0; i < rows; i++) {
      if (!visited.has(i)) {//如果没访问过
          dfs(isConnected, visited, rows, i);//深度优先遍历
          count++;//省份数量+1
      }
  }
  return count;
};

const dfs = (isConnected, visited, rows, i) => {
  for (let j = 0; j < rows; j++) {
      if (isConnected[i][j] == 1 && !visited.has(j)) {//如果i,j相连接
          visited.add(j);
          dfs(isConnected, visited, rows, j);//递归遍历
      }
  }
};
方法2.bfs
  • 思路:广度优先遍历,循矩阵,然后寻找相邻城市加入队列,队列不为空就不断出队,继续遍历
  • 复杂度:时间复杂度O(n^2),n是城市的数量,遍历矩阵中的每个元素。空间复杂度O(n),队列和visited数组最长是n

js:

var findCircleNum = function(isConnected) {
  const rows = isConnected.length;
  const visited = new Set();//记录是否访问过
  let count = 0;
  const queue = new Array();
  for (let i = 0; i < rows; i++) {
      if (!visited.has(i)) {//没有访问过
          queue.push(i); //加入队列
          while (queue.length) {//队列不为空 继续循环
              const j = queue.shift();//出队
              visited.add(j);
              for (let k = 0; k < rows; k++) {//循环相邻的城市 加入队列
                  if (isConnected[j][k] === 1 && !visited.has(k)) {
                      queue.push(k);
                  }
              }
          }
          count++;
      }
  }
  return count;
};
方法3.并查集
  • 思路:循环矩阵,遇到相邻的城市就合并,最后返回并查集中集合的数量
  • 复杂度:时间复杂度O(n^2),n是城市的数量,需要遍历矩阵,经过路径压缩后的并查集中需找父节点复杂度是常数级。空间复杂度是O(n),即parent的空间

js:

class UnionFind{
    constructor(n){ //构造一个大小为n的集合
        this.count = n
        this.parent = new Array(n)   
        this.size = new Array(n)  // size数组记录着每棵树的大小
        for (let i = 0; i < n; i++) {
            this.parent[i] = i; // 自己是自己的parent
            this.size[i] = 1;
        }
    }

    union(p,q){ //连通结点p和结点q, p和q都是索引
        let rootP = this.find(p);
        let rootQ = this.find(q);
        if(rootP === rootQ) return
        // 元素数量小的接到数量多的下面,这样比较平衡
        if (this.size[rootP] > this.size[rootQ]) {
            this.parent[rootQ] = rootP;
            this.size[rootP] += this.size[rootQ];
        } else {
            this.parent[rootP] = rootQ;
            this.size[rootQ] += this.size[rootP];
        }
        this.count--;
    }

    isConnected(p, q) { //判断p,q是否连通
        return this.find(p)=== this.find(q) 
    }

    find(x) { //找到x结点的root
        while (this.parent[x] != x) {
            // 进行路径压缩
            this.parent[x] = this.parent[this.parent[x]];
            x = this.parent[x];
        }
        return x;
    }

    getCount() { //返回子集个数
        return this.count;
    }
}


var findCircleNum = function(isConnected) {
    const rows = isConnected.length;
    const uf = new UnionFind(rows)

    for (let i = 0; i < rows; i++) {
        for (let j = i + 1; j < rows; j++) {
            if (isConnected[i][j] == 1) {//相邻城市合并
                uf.union(i, j);
            }
        }
    }

    return uf.getCount();
};

视频讲解:传送门

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

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

相关文章

如何在 ESXi 7.x 上安装 Windows Server 2019

安装 Windows Server 2019 连接 ESXi 7.0上传 Windows Server 2019 镜像创建虚拟机安装 Windows Server 2019在本文中,我们将逐步在 ESXi 7.x 上安装 Windows Server 2019。 连接 ESXi 7.0 使用 VMware 主机或 vSphere Web 客户端连接到 VMware vSphere Hypervisor 7.0(ESX…

Unity-ROS与话题与服务(二)

0. 简介 对于ROS而言&#xff0c;其最常用的就是Topic话题以及Service两个了。之前我们在了解Unity Robotics Hub时候就了解到基本的Unity和ROS的通讯&#xff0c;下面我们来详细介绍一下Unity与ROS的话题与服务。 ROS和Unity之间的通信是通过Unity的“ROS-TCP-Connector”软…

Linux学习07-vim程序编辑器

1 vi与vim vi 是老式的文书处理器&#xff0c;不过功能已经很齐全了&#xff0c;但是还是有可以进步的地方。 vim 则可以说是程序开发者的一项很好用的工具&#xff0c;就连 vim 的官方网站 &#xff08;http://www.vim.org&#xff09; 自己也说 vim 是一个“程序开发工具”而…

服务器内存CPU负载监控

1&#xff0c;系统平均负载(Load average)&#xff1a; 系统平均负载被定义为在特定时间间隔内运行队列中的平均进程树&#xff0c;一般来说只要每个CPU的当前活动进程数不大于3那么系统的性能就是良好的&#xff0c;如果每个CPU的任务数大于5&#xff0c;那么就表示这台机器的…

SpringBoot+MDC实现链路调用日志

1.首先介绍什么是MDC MDC&#xff08;Mapped Diagnostic Context&#xff0c;映射调试上下文&#xff09;是 log4j 、logback及log4j2 提供的一种方便在多线程条件下记录日志的功能。MDC 可以看成是一个与当前线程绑定的哈希表&#xff0c;可以往其中添加键值对。MDC 中包含的…

毕业设计 STM32自行车智能无线防盗报警器 -物联网 单片机 嵌入式

0 前言 &#x1f525; 这两年开始毕业设计和毕业答辩的要求和难度不断提升&#xff0c;传统的毕设题目缺少创新和亮点&#xff0c;往往达不到毕业答辩的要求&#xff0c;这两年不断有学弟学妹告诉学长自己做的项目系统达不到老师的要求。 为了大家能够顺利以及最少的精力通过…

【C++进阶】C++11新特性下篇(万字详解)

&#x1f387;C学习历程&#xff1a;入门 博客主页&#xff1a;一起去看日落吗持续分享博主的C学习历程博主的能力有限&#xff0c;出现错误希望大家不吝赐教分享给大家一句我很喜欢的话&#xff1a; 也许你现在做的事情&#xff0c;暂时看不到成果&#xff0c;但不要忘记&…

京东前端高频vue面试题(边面边更)

Redux 和 Vuex 有什么区别&#xff0c;它们的共同思想 &#xff08;1&#xff09;Redux 和 Vuex区别 Vuex改进了Redux中的Action和Reducer函数&#xff0c;以mutations变化函数取代Reducer&#xff0c;无需switch&#xff0c;只需在对应的mutation函数里改变state值即可Vuex由…

【树莓派】擦灰重启行动

高中时候看大佬各种秀项目&#xff0c;于是乎兴致冲冲买了一块树莓派4B&#xff0c;400r&#xff0c;当时没想到光是开机&#xff0c;就折腾了两个星期~后来不出意外它在房间的角落很安逸地吃灰&#xff0c;但是&#xff0c;后来&#xff0c;我误打误撞学了CS&#xff0c;再误打…

Okhttp源码分析实践(五)【实践环节:Okhttp的基本框架搭建请求实现】

http的基础知识、okhttp的框架基本源码,我们通过之前课程都已学习总结过,接下来,就是关键的实践课程。 各位coder,需要紧跟小编脚步,要开始加速飙车了。 1.基本框架的搭建实现 既然不知道如何入手,我们不妨就以okhttp的基本使用代码为例,作为入手点,去开始编程实现。…

机器学习理论介绍

前言 图灵奖获得者、著名数据库专家James Gray 博士观察并总结人类自古以来&#xff0c;在科学研究上&#xff0c;先后历经了实验、理论、计算和数据四种范式。 科学研究第一种范式&#xff1a;实验 在古代&#xff0c;人们的认知水平较低&#xff0c;对事物的认识很大程度上…

React redux使用

1.redux是什么 redux是一个专门用于状态管理的JS库&#xff08;不是react插件库&#xff09; 它可以用在react,angular,vue等项目中&#xff0c;但基本与react配合使用 作用&#xff1a;集中式管理react应用中多个组件共享的状态 2.为什么要使用redux 某个组件的状态&#…

Windows命令行到底有多强大?

程序员宝藏库&#xff1a;https://gitee.com/sharetech_lee/CS-Books-Store DevWeekly收集整理每周优质开发者内容&#xff0c;包括开源项目、资源工具、技术文章等方面。 每周五定期发布&#xff0c;同步更新到 知乎&#xff1a;Jackpop。 欢迎大家投稿&#xff0c;提交issu…

高可用系列文章之三 - NGINX 高可用实施方案

前文链接 高可用系列文章之一 - 概述 - 东风微鸣技术博客 (ewhisper.cn)高可用系列文章之二 - 传统分层架构技术方案 - 东风微鸣技术博客 (ewhisper.cn) 四 NGINX 高可用实施方案 高可用的实施, 主要步骤概述如下: NGINX 的安装及基础配置负载均衡层高可用: NGINX Keepali…

在BSV上运行深度神经网络

我们已经实现了一个用于手写数字分类的深度神经网络。已经训练好的模型完全在链上运行。它使用手写数字的 MNIST 数据集进行离线训练。该模型采用 28x28 灰度像素的图像并输出 0 到 9 的数字。 深度神经网络简介 人工神经网络是受生物神经网络启发而构建的。网络通过接触大量带…

[附源码]计算机毕业设计Python的小说阅读系统(程序+源码+LW文档)

该项目含有源码、文档、程序、数据库、配套开发软件、软件安装教程 项目运行 环境配置&#xff1a; Pychram社区版 python3.7.7 Mysql5.7 HBuilderXlist pipNavicat11Djangonodejs。 项目技术&#xff1a; django python Vue 等等组成&#xff0c;B/S模式 pychram管理等…

智云通CRM:如何正确的提出报价?(一)

智云通CRM认为完成销售包括三个步骤&#xff1a;提出报价&#xff0c;解决最后问题&#xff0c;讨论下一步方案。 第一步是提出报价&#xff0c;首先我们讨论如何将提出报价。 在与客户讨论费用问题时&#xff0c;我们应当向客户提出两个不同报价&#xff0c;一个销售方案对应…

前缀和与差分算法

目录 一 前缀和 算法定义 算法分类 算法作用 一维前缀和 问题引入 暴力法&#xff1a; 前缀和法&#xff1a; 算法原理 问题解答 算法实践 江山白日梦 题目描述 题目解答 二维前缀和 问题引入 算法原理 问题解答 二 差分 算法定义 算法分类 算法作用 一…

torch.chunk与nn.Conv2d groups

torch.chunk 切分 假如特征x大小为&#xff1a;32x64x224x224 (BxCxHxW) q torch.chunk(x, 8, dim1) x是要切分的特征&#xff0c;8是要切分成几块&#xff0c;dim是指定切分的维度&#xff0c;这里等于1&#xff0c;就是按通道切分 就会将其按照通道&#xff0c;切分为8块&a…