From 33eca1635693f64d0eeb6d7c477093665dded20b Mon Sep 17 00:00:00 2001 From: Terry Liu <102352821+lozakaka@users.noreply.github.com> Date: 2023年6月24日 20:18:36 -0400 Subject: [PATCH 1/5] =?UTF-8?q?=E6=96=B0=E5=A2=9Ejava=E8=A7=A3=E6=B3=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 1. 調整語言順序 2. 新增java - DFS, java - BFS解法 --- ...00345円244円247円351円235円242円347円247円257円.md" | 162 ++++++++++++++---- 1 file changed, 129 insertions(+), 33 deletions(-) diff --git "a/problems/0695.345円262円233円345円261円277円347円232円204円346円234円200円345円244円247円351円235円242円347円247円257円.md" "b/problems/0695.345円262円233円345円261円277円347円232円204円346円234円200円345円244円247円351円235円242円347円247円257円.md" index e5deb897d6..37a601bcf8 100644 --- "a/problems/0695.345円262円233円345円261円277円347円232円204円346円234円200円345円244円247円351円235円242円347円247円257円.md" +++ "b/problems/0695.345円262円233円345円261円277円347円232円204円346円234円200円345円244円247円351円235円242円347円247円257円.md" @@ -189,6 +189,135 @@ public: ``` # 其它语言版本 +## Java +### DFS +```java +// DFS +class Solution { + int[][] dir = { + {0, 1}, //right + {1, 0}, //down + {0, -1}, //left + {-1, 0} //up + }; + boolean visited[][]; + int count; + public int maxAreaOfIsland(int[][] grid) { + int res = 0; + visited = new boolean[grid.length][grid[0].length]; + for(int i = 0; i < grid.length; i++){ + for(int j = 0; j < grid[0].length; j++){ + if(visited[i][j] == false && grid[i][j] == 1){ + count = 0; + dfs(grid, i, j); + res = Math.max(res, count); + } + } + } + return res; + } + private void dfs(int[][] grid, int x, int y){ + if(visited[x][y] == true || grid[x][y] == 0) + return; + + visited[x][y] = true; + count++; + + for(int i = 0; i < 4; i++){ + int nextX = x + dir[i][0]; + int nextY = y + dir[i][1]; + + if(nextX < 0 || nextY < 0 || nextX>= grid.length || nextY>= grid[0].length) + continue; + dfs(grid, nextX, nextY); + } + } +} + + +``` +### BFS +```java +//BFS +class Solution { + int[][] dir = { + {0, 1}, {1, 0}, {0, -1}, {-1, 0} + }; + + int count; + boolean visited[][]; + + public int maxAreaOfIsland(int[][] grid) { + int res = 0; + visited = new boolean[grid.length][grid[0].length]; + + for(int i = 0; i < grid.length; i++){ + for(int j = 0; j < grid[0].length; j++){ + if(visited[i][j] == false && grid[i][j] == 1){ + count = 0; + bfs(grid, i, j); + res = Math.max(res, count); + } + } + } + return res; + } + private void bfs(int[][] grid, int x, int y){ + Queue que = new LinkedList(); + que.offer(x); + que.offer(y); + visited[x][y] = true; + count++; + + while(!que.isEmpty()){ + int currX = que.poll(); + int currY = que.poll(); + + for(int i = 0; i < 4; i++){ + int nextX = currX + dir[i][0]; + int nextY = currY + dir[i][1]; + + if(nextX < 0 || nextY < 0 || nextX>= grid.length || nextY>= grid[0].length) + continue; + if(visited[nextX][nextY] == false && grid[nextX][nextY] == 1){ + que.offer(nextX); + que.offer(nextY); + visited[nextX][nextY] = true; + count++; + } + } + } + } +} +``` +### DFS 優化(遇到島嶼後,就把他淹沒) +```java +//这里使用深度优先搜索 DFS 来完成本道题目。我们使用 DFS 计算一个岛屿的面积,同时维护计算过的最大的岛屿面积。同时,为了避免对岛屿重复计算,我们在 DFS 的时候对岛屿进行 "淹没" 操作,即将岛屿所占的地方置为 0。 +public int maxAreaOfIsland(int[][] grid) { + int res = 0; + for(int i = 0;i < grid.length;i++){ + for(int j = 0;j < grid[0].length;j++){ + //每遇到一个岛屿就计算这个岛屿的面积同时"淹没"这个岛屿 + if(grid[i][j] == 1){ + //每次计算一个岛屿的面积都要与res比较,维护最大的岛屿面积作为最后的答案 + res = Math.max(res,dfs(grid,i,j)); + } + } + } + return res; +} +public int dfs(int[][] grid,int i,int j){ + //搜索边界:i,j超过grid的范围或者当前元素为0,即当前所在的地方已经是海洋 + if(i < 0 || i>= grid.length || j < 0 || j>= grid[0].length || grid[i][j] == 0) return 0; + //淹没土地,防止后续被重复计算 + grid[i][j] = 0; + //递归的思路:要求当前土地(i,j)所在的岛屿的面积,则等于1加上下左右相邻的土地的总面积 + return 1 + dfs(grid,i - 1,j) + + dfs(grid,i + 1,j) + + dfs(grid,i,j + 1) + + dfs(grid,i,j - 1); +} +``` ## Python ### BFS @@ -261,39 +390,6 @@ class Solution: if 0 <= new_x < len(grid) and 0 <= new_y < len(grid[0]): self.dfs(grid, visited, new_x, new_y) ``` - - - -## Java - -这里使用深度优先搜索 DFS 来完成本道题目。我们使用 DFS 计算一个岛屿的面积,同时维护计算过的最大的岛屿面积。同时,为了避免对岛屿重复计算,我们在 DFS 的时候对岛屿进行 "淹没" 操作,即将岛屿所占的地方置为 0。 - -```java -public int maxAreaOfIsland(int[][] grid) { - int res = 0; - for(int i = 0;i < grid.length;i++){ - for(int j = 0;j < grid[0].length;j++){ - //每遇到一个岛屿就计算这个岛屿的面积同时"淹没"这个岛屿 - if(grid[i][j] == 1){ - //每次计算一个岛屿的面积都要与res比较,维护最大的岛屿面积作为最后的答案 - res = Math.max(res,dfs(grid,i,j)); - } - } - } - return res; -} -public int dfs(int[][] grid,int i,int j){ - //搜索边界:i,j超过grid的范围或者当前元素为0,即当前所在的地方已经是海洋 - if(i < 0 || i>= grid.length || j < 0 || j>= grid[0].length || grid[i][j] == 0) return 0; - //淹没土地,防止后续被重复计算 - grid[i][j] = 0; - //递归的思路:要求当前土地(i,j)所在的岛屿的面积,则等于1加上下左右相邻的土地的总面积 - return 1 + dfs(grid,i - 1,j) + - dfs(grid,i + 1,j) + - dfs(grid,i,j + 1) + - dfs(grid,i,j - 1); -} -```

From 2b3ce4453520a16bac6d65c2b3295cc65b90f00e Mon Sep 17 00:00:00 2001 From: Terry Liu <102352821+lozakaka@users.noreply.github.com> Date: 2023年6月24日 23:38:40 -0400 Subject: [PATCH 2/5] =?UTF-8?q?=E6=96=B0=E5=A2=9Ejava=E8=A7=A3=E6=B3=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- ...60347円232円204円346円225円260円351円207円217円.md" | 126 +++++++++++++++++- 1 file changed, 124 insertions(+), 2 deletions(-) diff --git "a/problems/1020.351円243円236円345円234円260円347円232円204円346円225円260円351円207円217円.md" "b/problems/1020.351円243円236円345円234円260円347円232円204円346円225円260円351円207円217円.md" index e92b2412f3..7538d774d2 100644 --- "a/problems/1020.351円243円236円345円234円260円347円232円204円346円225円260円351円207円217円.md" +++ "b/problems/1020.351円243円236円345円234円260円347円232円204円346円225円260円351円207円217円.md" @@ -149,7 +149,63 @@ public: ### Java -深度优先遍历版本: +深度优先遍历(没有终止条件 + 空間優化(淹沒島嶼,沒有使用visited數組)) +```java +//DFS +class Solution { + int count = 0; + int[][] dir ={ + {0, 1}, + {1, 0}, + {-1, 0}, + {0, -1} + }; + private void dfs(int[][] grid, int x, int y){ + if(grid[x][y] == 0) + return; + + grid[x][y] = 0; + count++; + + for(int i = 0; i < 4; i++){ + int nextX = x + dir[i][0]; + int nextY = y + dir[i][1]; + + if(nextX < 0 || nextY < 0 || nextX>= grid.length || nextY>= grid[0].length) + continue; + dfs(grid, nextX, nextY); + } + + } + + public int numEnclaves(int[][] grid) { + for(int i = 0; i < grid.length; i++){ + if(grid[i][0] == 1) + dfs(grid, i, 0); + if(grid[i][grid[0].length - 1] == 1) + dfs(grid, i, grid[0].length - 1); + } + //初始化的時候,j 的上下限有調整過,必免重複操作。 + for(int j = 1; j < grid[0].length - 1; j++){ + if(grid[0][j] == 1) + dfs(grid, 0, j); + if(grid[grid.length - 1][j] == 1) + dfs(grid, grid.length - 1, j); + } + count = 0; + + for(int i = 1; i < grid.length - 1; i++){ + for(int j = 1; j < grid[0].length - 1; j++){ + if(grid[i][j] == 1) + dfs(grid, i, j); + } + } + return count; + } +} +``` + +深度优先遍历(没有终止条件) ```java class Solution { @@ -206,7 +262,7 @@ class Solution { } ``` -广度优先遍历版本: +广度优先遍历(使用visited數組) ```java class Solution { @@ -269,6 +325,72 @@ class Solution { } ``` +廣度优先遍历(空間優化(淹沒島嶼,沒有使用visited數組)) +```java +//BFS +class Solution { + int count = 0; + int[][] dir ={ + {0, 1}, + {1, 0}, + {-1, 0}, + {0, -1} + }; + private void bfs(int[][] grid, int x, int y){ + Queue que = new LinkedList(); + que.offer(x); + que.offer(y); + count++; + grid[x][y] = 0; + + while(!que.isEmpty()){ + int currX = que.poll(); + int currY = que.poll(); + + for(int i = 0; i < 4; i++){ + int nextX = currX + dir[i][0]; + int nextY = currY + dir[i][1]; + + if(nextX < 0 || nextY < 0 || nextX>= grid.length || nextY>= grid[0].length) + continue; + + if(grid[nextX][nextY] == 1){ + que.offer(nextX); + que.offer(nextY); + count++; + grid[nextX][nextY] = 0; + } + } + } + } + + public int numEnclaves(int[][] grid) { + for(int i = 0; i < grid.length; i++){ + if(grid[i][0] == 1) + bfs(grid, i, 0); + if(grid[i][grid[0].length - 1] == 1) + bfs(grid, i, grid[0].length - 1); + } + for(int j = 1; j < grid[0].length; j++){ + if(grid[0][j] == 1) + bfs(grid, 0 , j); + if(grid[grid.length - 1][j] == 1) + bfs(grid, grid.length - 1, j); + } + count = 0; + for(int i = 1; i < grid.length - 1; i++){ + for(int j = 1; j < grid[0].length - 1; j++){ + if(grid[i][j] == 1) + bfs(grid,i ,j); + } + } + return count; + } +} + + +``` + ### Python 深度优先遍历 From cdc5a1e9e47690fee5c5f5b34d66f643d6e08907 Mon Sep 17 00:00:00 2001 From: Terry Liu <102352821+lozakaka@users.noreply.github.com> Date: 2023年6月26日 21:37:58 -0400 Subject: [PATCH 3/5] =?UTF-8?q?=E6=96=B0=E5=A2=9EJAVA=E8=A7=A3=E6=B3=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 1. java的BFS解法有寫一個helper function去呼叫 2. java's DFS with 終止條件 --- ...25347円232円204円345円214円272円345円237円237円.md" | 89 +++++++++++++++++++ 1 file changed, 89 insertions(+) diff --git "a/problems/0130.350円242円253円345円233円264円347円273円225円347円232円204円345円214円272円345円237円237円.md" "b/problems/0130.350円242円253円345円233円264円347円273円225円347円232円204円345円214円272円345円237円237円.md" index abb68e199c..e244873ba3 100644 --- "a/problems/0130.350円242円253円345円233円264円347円273円225円347円232円204円345円214円272円345円237円237円.md" +++ "b/problems/0130.350円242円253円345円233円264円347円273円225円347円232円204円345円214円272円345円237円237円.md" @@ -188,6 +188,54 @@ class Solution { } } ``` +```Java +//BFS(使用helper function) +class Solution { + int[][] dir ={{0, 1}, {1, 0}, {0, -1}, {-1, 0}}; + public void solve(char[][] board) { + for(int i = 0; i < board.length; i++){ + if(board[i][0] == 'O') bfs(board, i, 0); + if(board[i][board[0].length - 1] == 'O') bfs(board, i, board[0].length - 1); + } + + for(int j = 1 ; j < board[0].length - 1; j++){ + if(board[0][j] == 'O') bfs(board, 0, j); + if(board[board.length - 1][j] == 'O') bfs(board, board.length - 1, j); + } + + for(int i = 0; i < board.length; i++){ + for(int j = 0; j < board[0].length; j++){ + if(board[i][j] == 'O') board[i][j] = 'X'; + if(board[i][j] == 'A') board[i][j] = 'O'; + } + } + } + private void bfs(char[][] board, int x, int y){ + Queue que = new LinkedList(); + board[x][y] = 'A'; + que.offer(x); + que.offer(y); + + while(!que.isEmpty()){ + int currX = que.poll(); + int currY = que.poll(); + + for(int i = 0; i < 4; i++){ + int nextX = currX + dir[i][0]; + int nextY = currY + dir[i][1]; + + if(nextX < 0 || nextY < 0 || nextX>= board.length || nextY>= board[0].length) + continue; + if(board[nextX][nextY] == 'X'|| board[nextX][nextY] == 'A') + continue; + bfs(board, nextX, nextY); + } + } + } +} + +``` + ```Java // 深度优先遍历 // 使用 visited 数组进行标记 @@ -296,6 +344,47 @@ class Solution { } } ``` +```java +//DFS(有終止條件) +class Solution { + int[][] dir ={{0, 1}, {1, 0}, {0, -1}, {-1, 0}}; + public void solve(char[][] board) { + + for(int i = 0; i < board.length; i++){ + if(board[i][0] == 'O') dfs(board, i, 0); + if(board[i][board[0].length - 1] == 'O') dfs(board, i, board[0].length - 1); + } + + for(int j = 1 ; j < board[0].length - 1; j++){ + if(board[0][j] == 'O') dfs(board, 0, j); + if(board[board.length - 1][j] == 'O') dfs(board, board.length - 1, j); + } + + for(int i = 0; i < board.length; i++){ + for(int j = 0; j < board[0].length; j++){ + if(board[i][j] == 'O') board[i][j] = 'X'; + if(board[i][j] == 'A') board[i][j] = 'O'; + } + } + } + + private void dfs(char[][] board, int x, int y){ + if(board[x][y] == 'X'|| board[x][y] == 'A') + return; + board[x][y] = 'A'; + for(int i = 0; i < 4; i++){ + int nextX = x + dir[i][0]; + int nextY = y + dir[i][1]; + + if(nextX < 0 || nextY < 0 || nextX>= board.length || nextY>= board[0].length) + continue; + // if(board[nextX][nextY] == 'X'|| board[nextX][nextY] == 'A') + // continue; + dfs(board, nextX, nextY); + } + } +} +```

From 886287eb27b94681d578248af3ac2b348ee7e94f Mon Sep 17 00:00:00 2001 From: programmercarl <826123027@qq.com> Date: 2023年7月14日 11:12:45 +0800 Subject: [PATCH 4/5] =?UTF-8?q?=E5=B9=B6=E6=9F=A5=E9=9B=86=E5=9B=BE?= =?UTF-8?q?=E8=AE=BA?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- ...06350円256円272円345円237円272円347円241円200円.md" | 433 ++++++++++++++++++ 1 file changed, 433 insertions(+) create mode 100644 "problems/345円233円276円350円256円272円345円271円266円346円237円245円351円233円206円347円220円206円350円256円272円345円237円272円347円241円200円.md" diff --git "a/problems/345円233円276円350円256円272円345円271円266円346円237円245円351円233円206円347円220円206円350円256円272円345円237円272円347円241円200円.md" "b/problems/345円233円276円350円256円272円345円271円266円346円237円245円351円233円206円347円220円206円350円256円272円345円237円272円347円241円200円.md" new file mode 100644 index 0000000000..5cd753e9ba --- /dev/null +++ "b/problems/345円233円276円350円256円272円345円271円266円346円237円245円351円233円206円347円220円206円350円256円272円345円237円272円347円241円200円.md" @@ -0,0 +1,433 @@ + +# 并查集理论基础 + +图论中,关于深搜和广搜我们在这里:[钥匙和房间](https://mp.weixin.qq.com/s/E9NlJy9PW1oJuD8N2EURoQ) 已经更新完毕了。 + +接下来我们来讲一下并查集,首先当然是并查集理论基础。 + +## 背景 + +首先要知道并查集可以解决什么问题呢? + +并查集常用来解决连通性问题。 + +大白话就是当我们需要判断两个元素是否在同一个集合里的时候,我们就要想到用并查集。 + +并查集主要有两个功能: + +* 将两个元素添加到一个集合中。 +* 判断两个元素在不在同一个集合 + +接下来围绕并查集的这两个功能来展开讲解。 + +## 原理讲解 + +从代码层面,我们如何将两个元素添加到同一个集合中呢。 + +此时有录友会想到:可以把他放到同一个数组里或者set 或者 map 中,这样就表述两个元素在同一个集合。 + +那么问题来了,对这些元素分门别类,可不止一个集合,可能是很多集合,成百上千,那么要定义这么多个数组吗? + +有录友想,那可以定义一个二维数组。 + +但如果我们要判断两个元素是否在同一个集合里的时候 我们又能怎么办? 只能把而二维数组都遍历一遍。 + +而且每当想添加一个元素到某集合的时候,依然需要把把二维数组组都遍历一遍,才知道要放在哪个集合里。 + +这仅仅是一个粗略的思路,如果沿着这个思路去实现代码,非常复杂,因为管理集合还需要很多逻辑。 + +那么我们来换一个思路来看看。 + +我们将三个元素A,B,C (分别是数字)放在同一个集合,其实就是将三个元素连通在一起,如何连通呢。 + +只需要用一个一维数组来表示,即:father[A] = B,father[B] = C 这样就表述 A 与 B 与 C连通了(有向连通图)。 + +代码如下: + +``` CPP +// 将v,u 这条边加入并查集 +void join(int u, int v) { + u = find(u); // 寻找u的根 + v = find(v); // 寻找v的根 + if (u == v) return; // 如果发现根相同,则说明在一个集合,不用两个节点相连直接返回 + father[v] = u; +} +``` + +可能有录友想,这样我可以知道 A 连通 B,因为 A 是索引下标,根据 father[A]的数值就知道 A 连通 B。那怎么知道 B 连通 A呢? + +我们的目的是判断这三个元素是否在同一个集合里,知道 A 连通 B 就已经足够了。 + +这里要讲到寻根思路,只要 A ,B,C 在同一个根下就是同一个集合。 + +给出A元素,就可以通过 father[A] = B,father[B] = C,找到根为 C。 + +给出B元素,就可以通过 father[B] = C,找到根也为为 C,说明 A 和 B 是在同一个集合里。 +大家会想第一段代码里find函数是如何实现的呢?其实就是通过数组下标找到数组元素,一层一层寻根过程,代码如下: + +```CPP +// 并查集里寻根的过程 +int find(int u) { + if (u == father[u]) return u; // 如果根就是自己,直接返回 + else return find(father[u]); // 如果根不是自己,就根据数组下标一层一层向下找 +} + +``` + + +如何表示 C 也在同一个元素里呢? 我们需要 father[C] = C,即C的根也为C,这样就方便表示 A,B,C 都在同一个集合里了。 + +所以father数组初始化的时候要 father[i] = i,默认自己指向自己。 + +代码如下: + +```CPP +// 并查集初始化 +void init() { + for (int i = 0; i < n; ++i) { + father[i] = i; + } +} +``` + +最后我们如何判断两个元素是否在同一个集合里,如果通过 find函数 找到 两个元素属于同一个根的话,那么这两个元素就是同一个集合,代码如下: + + +```CPP +// 判断 u 和 v是否找到同一个根 +bool isSame(int u, int v) { + u = find(u); + v = find(v); + return u == v; +} +``` + +## 路径压缩 + +在实现 find 函数的过程中,我们知道,通过递归的方式,不断获取father数组下标对应的数值,最终找到这个集合的根。 + +搜索过程像是一个多叉树中从叶子到根节点的过程,如图: + +![](https://code-thinking-1253855093.file.myqcloud.com/pics/20230602102619.png) + +如果这棵多叉树高度很深的话,每次find函数 去寻找跟的过程就要递归很多次。 + +我们的目的只需要知道这些节点在同一个根下就可以,所以对这棵多叉树的构造只需要这样就可以了,如图: + +![](https://code-thinking-1253855093.file.myqcloud.com/pics/20230602103040.png) + +除了根节点其他所有节点都挂载根节点下,这样我们在寻根的时候就很快,只需要一步, + +如果我们想达到这样的效果,就需要 **路径压缩**,将非根节点的所有节点直接指向根节点。 +那么在代码层面如何实现呢? + +我们只需要在递归的过程中,让 father[u] 接住 递归函数 find(father[u]) 的返回结果。 + +因为 find 函数向上寻找根节点,father[u] 表述 u 的父节点,那么让 father[u] 直接获取 find函数 返回的根节点,这样就让节点 u 的父节点 变成根节点。 + +代码如下,注意看注释,路径压缩就一行代码: + +```CPP +// 并查集里寻根的过程 +int find(int u) { + if (u == father[u]) return u; + else return father[u] = find(father[u]); // 路径压缩 +} +``` + +以上代码在C++中,可以用三元表达式来精简一下,代码如下: + +```CPP +int find(int u) { + return u == father[u] ? u : father[u] = find(father[u]); +} + +``` + +相信不少录友在学习并查集的时候,对上面这三行代码实现的 find函数 很熟悉,但理解上却不够深入,仅仅知道这行代码很好用,不知道这里藏着路径压缩的过程。 + +所以对于算法初学者来说,直接看精简代码学习是不太友好的,往往忽略了很多细节。 + +## 代码模板 + +那么此时并查集的模板就出来了, 整体模板C++代码如下: + +```CPP +int n = 1005; // n根据题目中节点数量而定,一般比节点数量大一点就好 +vector father = vector (n, 0); // C++里的一种数组结构 + +// 并查集初始化 +void init() { + for (int i = 0; i < n; ++i) { + father[i] = i; + } +} +// 并查集里寻根的过程 +int find(int u) { + return u == father[u] ? u : father[u] = find(father[u]); // 路径压缩 +} + +// 判断 u 和 v是否找到同一个根 +bool isSame(int u, int v) { + u = find(u); + v = find(v); + return u == v; +} + +// 将v->u 这条边加入并查集 +void join(int u, int v) { + u = find(u); // 寻找u的根 + v = find(v); // 寻找v的根 + if (u == v) return ; // 如果发现根相同,则说明在一个集合,不用两个节点相连直接返回 + father[v] = u; +} +``` +通过模板,我们可以知道,并查集主要有三个功能。 + +1. 寻找根节点,函数:find(int u),也就是判断这个节点的祖先节点是哪个 +2. 将两个节点接入到同一个集合,函数:join(int u, int v),将两个节点连在同一个根节点上 +3. 判断两个节点是否在同一个集合,函数:isSame(int u, int v),就是判断两个节点是不是同一个根节点 + +## 常见误区 + +这里估计有录友会想,模板中的 join 函数里的这段代码: + +```CPP +u = find(u); // 寻找u的根 +v = find(v); // 寻找v的根 +if (u == v) return ; // 如果发现根相同,则说明在一个集合,不用两个节点相连直接返回 + +``` + +与 isSame 函数的实现是不是重复了? 如果抽象一下呢,代码如下: + +```CPP +// 判断 u 和 v是否找到同一个根 +bool isSame(int u, int v) { + u = find(u); + v = find(v); + return u == v; +} + +// 将v->u 这条边加入并查集 +void join(int u, int v) { + if (isSame) return ; // 如果发现根相同,则说明在一个集合,不用两个节点相连直接返回 + father[v] = u; +} +``` + +这样写可以吗? 好像看出去没问题,而且代码更精简了。 + +**其实这么写是有问题的**,在join函数中 我们需要寻找 u 和 v 的根,然后再进行连线在一起,而不是直接 用 u 和 v 连线在一起。 + +举一个例子: + +``` +join(1, 2); +join(3, 2); +``` + +此时构成的图是这样的: + +![](https://code-thinking-1253855093.file.myqcloud.com/pics/20230525111307.png) + +此时问 1,3是否在同一个集合,我们调用 `join(1, 2); join(3, 2);` 很明显本意要表示 1,3是在同一个集合。 + +但我们来看一下代码逻辑,当我们调用 `isSame(1, 3)`的时候,find(1) 返回的是1,find(3)返回的是3。 `return 1 == 3` 返回的是false,代码告诉我们 1 和 3 不在同一个集合,这明显不符合我们的预期,所以问题出在哪里? + +问题出在我们精简的代码上,即 join 函数 一定要先 通过find函数寻根再进行关联。 + +如果find函数是这么实现,再来看一下逻辑过程。 + +```CPP +void join(int u, int v) { + u = find(u); // 寻找u的根 + v = find(v); // 寻找v的根 + if (u == v) return ; // 如果发现根相同,则说明在一个集合,不用两个节点相连直接返回 + father[v] = u; +} +``` + +分别将 这两对元素加入集合。 + +```CPP +join(1, 2); +join(3, 2); +``` + +当执行`join(3, 2)`的时候,会先通过find函数寻找 3的根为3,2的根为1 (第一个`join(1, 2)`,将2的根设置为1),所以最后是将1 指向 3。 + +构成的图是这样的: + +![](https://code-thinking-1253855093.file.myqcloud.com/pics/20230525112101.png) + +因为在join函数里,我们有find函数进行寻根的过程,这样就保证元素 1,2,3在这个有向图里是强连通的。 + +此时我们在调用 `isSame(1, 3)`的时候,find(1) 返回的是3,find(3) 返回的也是3,`return 3 == 3` 返回的是true,即告诉我们 元素 1 和 元素3 是 在同一个集合里的。 + + + + +## 模拟过程 + +(**凸显途径合并的过程,每一个join都要画图**) + +不少录友在接触并查集模板之后,用起来很娴熟,因为模板确实相对固定,但是对并查集内部数据组织方式以及如何判断是否是同一个集合的原理很模糊。 + +通过以上讲解之后,我在带大家一步一步去画一下,并查集内部数据连接方式。 + +注意:为了让录友们了解基础并查集的操作,不至于混乱,**以下模拟过程中不考虑路径压缩的过程**,了解基础并查集操作后,路径压缩也很容易理解。 + +我们先通过一些列的join操作,将两两元素分别放入同一个集合中。 + +```CPP +join(1, 8); +join(3, 8); +join(1, 7); +join(8, 5); +join(6, 2); +join(2, 9); + +``` + +此时我们生成的的有向图为: + +![](https://code-thinking-1253855093.file.myqcloud.com/pics/20230526122203.png) + +有录友可能想,`join(3, 8)` 在图中为什么 将 元素1 连向元素 3 而不是将 元素 8 连向 元素 3 呢? + +这一点 我在 「常见误区」标题下已经详细讲解了,因为在`join(int u, int v)`函数里 要分别对 u 和 v 寻根之后再进行关联。 + +大家看懂这个有向图后,相信应该知道如下函数的返回值了。 + +```CPP +cout << isSame(8, 7) << endl; +cout << isSame(7, 2) << endl; +``` + +返回值分别如下,表示,8 和 7 是同一个集合,而 7 和 2 不是同一个集合。 + +``` +true +false +``` + +## 拓展 + + +在「路径压缩」讲解中,我们知道如何靠压缩路径来缩短查询根节点的时间。 + +其实还有另一种方法:按秩(rank)合并。 + +rank表示树的高度,即树中结点层次的最大值。 + +例如两个集合(多叉树)需要合并,如图所示: + +![](https://code-thinking-1253855093.file.myqcloud.com/pics/20230602172250.png) + +树1 rank 为2,树2 rank 为 3。那么合并两个集合,是 树1 合入 树2,还是 树2 合入 树1呢? + +我们来看两个不同方式合入的效果。 + +![](https://code-thinking-1253855093.file.myqcloud.com/pics/20230602172933.png) + +这里可以看出,树2 合入 树1 会导致整棵树的高度变的更高,而 树1 合入 树2 整棵树的高度 和 树2 保持一致。 + +所以在 join函数中如何合并两棵树呢? + +一定是 rank 小的树合入 到 rank大 的树,这样可以保证最后合成的树rank 最小,降低在树上查询的路径长度。 + +按秩合并的代码如下: + +```CPP +int n = 1005; // n根据题目中节点数量而定,一般比节点数量大一点就好 +vector father = vector (n, 0); // C++里的一种数组结构 +vector rank = vector (n, 1); // 初始每棵树的高度都为1 + +// 并查集初始化 +void init() { + for (int i = 0; i < n; ++i) { + father[i] = i; + rank[i] = 1; // 也可以不写 + } +} +// 并查集里寻根的过程 +int find(int u) { + return u == father[u] ? u : find(father[u]);// 注意这里不做路径压缩 +} + +// 判断 u 和 v是否找到同一个根 +bool isSame(int u, int v) { + u = find(u); + v = find(v); + return u == v; +} + +// 将v->u 这条边加入并查集 +void join(int u, int v) { + u = find(u); // 寻找u的根 + v = find(v); // 寻找v的根 + + if (rank[u] <= rank[v]) father[u] = v; // rank小的树合入到rank大的树 + else father[v] = u; + + if (rank[u] == rank[v] && u != v) rank[v]++; // 如果两棵树高度相同,则v的高度+1因为,方面 if (rank[u] <= rank[v]) father[u] = v; 注意是 <= +} +``` + +可以注意到在上面的模板代码中,我是没有做路径压缩的,因为一旦做路径压缩,rank记录的高度就不准了,根据rank来判断如何合并就没有意义。 + +也可以在 路径压缩的时候,再去实时修生rank的数值,但这样在代码实现上麻烦了不少,关键是收益很小。 + +其实我们在优化并查集查询效率的时候,只用路径压缩的思路就够了,不仅代码实现精简,而且效率足够高。 + +按秩合并的思路并没有将树形结构尽可能的扁平化,所以在整理效率上是没有路径压缩高的。 + +说到这里可能有录友会想,那在路径压缩的代码中,只有查询的过程 即 find 函数的执行过程中会有路径压缩,如果一直没有使用find函数,是不是相当于这棵树就没有路径压缩,导致查询效率依然很低呢? + +大家可以再去回顾使用路径压缩的 并查集模板,在isSame函数 和 join函数中,我们都调用了 find 函数来进行寻根操作。 + +也就是说,无论使用并查集模板里哪一个函数(除了init函数),都会有路径压缩的过程,第二次访问相同节点的时候,这个节点就是直连根节点的,即 第一次访问的时候它的路径就被压缩了。 + +**所以这里推荐大家直接使用路径压缩的并查集模板就好**,但按秩合并的优化思路我依然给大家讲清楚,有助于更深一步理解并查集的优化过程。 + +## 复杂度分析 + + +这里对路径压缩版并查集来做分析。 + +空间复杂度: O(n) ,申请一个father数组。 + +关于时间复杂度,如果想精确表达出来需要繁琐的数学证明,就不在本篇讲解范围内了,大家感兴趣可以自己去深入去研究。 + +这里做一个简单的分析思路。 + +路径压缩后的并查集时间复杂度在O(logn)与O(1)之间,且随着查询或者合并操作的增加,时间复杂度会越来越趋于O(1)。 + +了解到这个程度对于求职面试来说就够了。 + +在第一次查询的时候,相当于是n叉树上从叶子节点到根节点的查询过程,时间复杂度是logn,但路径压缩后,后面的查询操作都是O(1),而 join 函数 和 isSame函数 里涉及的查询操作也是一样的过程。 + + +## 总结 + +本篇我们讲解了并查集的背景、原理、两种优化方式(路径压缩,按秩合并),代码模板,常见误区,以及模拟过程。 + +要知道并查集解决什么问题,在什么场景下我们要想到使用并查集。 + + +接下来进一步优化并查集的执行效率,重点介绍了路径压缩的方式,另一种方法:按秩合并,我们在 「拓展」中讲解。 + +通过一步一步的原理讲解,最后给出并查集的模板,所有的并查集题目都在这个模板的基础上进行操作或者适当修改。 + +但只给出模板还是不够的,针对大家学习并查集的常见误区,详细讲解了模板代码的细节。 + +为了让录友们进一步了解并查集的运行过程,我们再通过具体用例模拟一遍代码过程并画出对应的内部数据连接图(有向图)。 + +这里也建议大家去模拟一遍才能对并查集理解的更到位。 + +如果对模板代码还是有点陌生,不用担心,接下来我会讲解对应LeetCode上的并查集题目,通过一系列题目练习,大家就会感受到这套模板有多么的好用! + +敬请期待 并查集题目精讲系列。 + + From ba5ecfecaf7160911e4d464c5aecdce1f6973146 Mon Sep 17 00:00:00 2001 From: programmercarl <826123027@qq.com> Date: 2023年7月14日 11:13:00 +0800 Subject: [PATCH 5/5] Update --- ...273円351円231円244円345円205円203円347円264円240円.md" | 7 +++++-- ...72346円227円213円347円237円251円351円230円265円II.md" | 9 ++++++--- ...276円350円241円250円345円205円203円347円264円240円.md" | 4 ++++ ...273円350円275円254円351円223円276円350円241円250円.md" | 5 +++++ ...204円345円255円220円346円225円260円347円273円204円.md" | 13 ++++++++----- ...220円345円255円227円347円254円246円344円270円262円.md" | 16 ++++++++-------- ...227円344円275円231円350円277円236円346円216円245円.md" | 10 +++------- ...27344円275円231円350円277円236円346円216円245円II.md" | 4 ++-- ...214円345円210円206円346円237円245円346円211円276円.md" | 6 ++++-- ...276円350円256円241円351円223円276円350円241円250円.md" | 8 ++++++-- ...204円347円232円204円345円271円263円346円226円271円.md" | 17 ++++++++++------- ...230円345円234円250円350円267円257円345円276円204円.md" | 6 +++--- 12 files changed, 64 insertions(+), 41 deletions(-) diff --git "a/problems/0027.347円247円273円351円231円244円345円205円203円347円264円240円.md" "b/problems/0027.347円247円273円351円231円244円345円205円203円347円264円240円.md" index 908011532a..3d43a199b0 100644 --- "a/problems/0027.347円247円273円351円231円244円345円205円203円347円264円240円.md" +++ "b/problems/0027.347円247円273円351円231円244円345円205円203円347円264円240円.md" @@ -26,9 +26,12 @@ **你不需要考虑数组中超出新长度后面的元素。** -## 思路 -针对本题,我录制了视频讲解:[数组中移除元素并不容易!LeetCode:27. 移除元素](https://www.bilibili.com/video/BV12A4y1Z7LP),结合本题解一起看,事半功倍! +## 算法公开课 + +**[《代码随想录》算法视频公开课](https://programmercarl.com/other/gongkaike.html):[数组中移除元素并不容易!LeetCode:27. 移除元素](https://www.bilibili.com/video/BV12A4y1Z7LP),相信结合视频再看本篇题解,更有助于大家对本题的理解**。 + +## 思路 有的同学可能说了,多余的元素,删掉不就得了。 diff --git "a/problems/0059.350円236円272円346円227円213円347円237円251円351円230円265円II.md" "b/problems/0059.350円236円272円346円227円213円347円237円251円351円230円265円II.md" index b4dad9c3de..fd40f3fc52 100644 --- "a/problems/0059.350円236円272円346円227円213円347円237円251円351円230円265円II.md" +++ "b/problems/0059.350円236円272円346円227円213円347円237円251円351円230円265円II.md" @@ -6,7 +6,7 @@ -## 59.螺旋矩阵II +# 59.螺旋矩阵II [力扣题目链接](https://leetcode.cn/problems/spiral-matrix-ii/) @@ -22,9 +22,12 @@ [ 7, 6, 5 ] ] -## 思路 -为了利于录友们理解,我特意录制了视频,[拿下螺旋矩阵!LeetCode:59.螺旋矩阵II](https://www.bilibili.com/video/BV1SL4y1N7mV),结合视频一起看,事半功倍! +## 算法公开课 + +**[《代码随想录》算法视频公开课](https://programmercarl.com/other/gongkaike.html):[拿下螺旋矩阵!LeetCode:59.螺旋矩阵II](https://www.bilibili.com/video/BV1SL4y1N7mV),相信结合视频再看本篇题解,更有助于大家对本题的理解**。 + +## 思路 这道题目可以说在面试中出现频率较高的题目,**本题并不涉及到什么算法,就是模拟过程,但却十分考察对代码的掌控能力。** diff --git "a/problems/0203.347円247円273円351円231円244円351円223円276円350円241円250円345円205円203円347円264円240円.md" "b/problems/0203.347円247円273円351円231円244円351円223円276円350円241円250円345円205円203円347円264円240円.md" index f875165826..300f98e911 100644 --- "a/problems/0203.347円247円273円351円231円244円351円223円276円350円241円250円345円205円203円347円264円240円.md" +++ "b/problems/0203.347円247円273円351円231円244円351円223円276円350円241円250円345円205円203円347円264円240円.md" @@ -27,6 +27,10 @@ 输入:head = [7,7,7,7], val = 7 输出:[] +# 算法公开课 + +**[《代码随想录》算法视频公开课](https://programmercarl.com/other/gongkaike.html):[链表基础操作| LeetCode:203.移除链表元素](https://www.bilibili.com/video/BV18B4y1s7R9),相信结合视频再看本篇题解,更有助于大家对本题的理解**。 + # 思路 diff --git "a/problems/0206.347円277円273円350円275円254円351円223円276円350円241円250円.md" "b/problems/0206.347円277円273円350円275円254円351円223円276円350円241円250円.md" index 0425e18280..c63c998dc7 100644 --- "a/problems/0206.347円277円273円350円275円254円351円223円276円350円241円250円.md" +++ "b/problems/0206.347円277円273円350円275円254円351円223円276円350円241円250円.md" @@ -17,6 +17,11 @@ 输入: 1->2->3->4->5->NULL 输出: 5->4->3->2->1->NULL +# 算法公开课 + +**[《代码随想录》算法视频公开课](https://programmercarl.com/other/gongkaike.html):[帮你拿下反转链表 | LeetCode:206.反转链表](https://www.bilibili.com/video/BV1nB4y1i7eL),相信结合视频再看本篇题解,更有助于大家对本题的理解**。 + + # 思路 本题我录制了B站视频,[帮你拿下反转链表 | LeetCode:206.反转链表](https://www.bilibili.com/video/BV1nB4y1i7eL),相信结合视频在看本篇题解,更有助于大家对链表的理解。 diff --git "a/problems/0209.351円225円277円345円272円246円346円234円200円345円260円217円347円232円204円345円255円220円346円225円260円347円273円204円.md" "b/problems/0209.351円225円277円345円272円246円346円234円200円345円260円217円347円232円204円345円255円220円346円225円260円347円273円204円.md" index d7ae478033..82d551ea44 100644 --- "a/problems/0209.351円225円277円345円272円246円346円234円200円345円260円217円347円232円204円345円255円220円346円225円260円347円273円204円.md" +++ "b/problems/0209.351円225円277円345円272円246円346円234円200円345円260円217円347円232円204円345円255円220円346円225円260円347円273円204円.md" @@ -13,9 +13,9 @@ 示例: -输入:s = 7, nums = [2,3,1,2,4,3] -输出:2 -解释:子数组 [4,3] 是该条件下的长度最小的子数组。 +* 输入:s = 7, nums = [2,3,1,2,4,3] +* 输出:2 +* 解释:子数组 [4,3] 是该条件下的长度最小的子数组。 提示: @@ -23,9 +23,12 @@ * 1 <= nums.length <= 10^5 * 1 <= nums[i] <= 10^5 -# 思路 +# 算法公开课 + +**[《代码随想录》算法视频公开课](https://programmercarl.com/other/gongkaike.html):[拿下滑动窗口! | LeetCode 209 长度最小的子数组](https://www.bilibili.com/video/BV1tZ4y1q7XE),相信结合视频再看本篇题解,更有助于大家对本题的理解**。 -为了易于大家理解,我特意录制了B站视频[拿下滑动窗口! | LeetCode 209 长度最小的子数组](https://www.bilibili.com/video/BV1tZ4y1q7XE),结合视频看本题解,事半功倍! + +# 思路 ## 暴力解法 diff --git "a/problems/0459.351円207円215円345円244円215円347円232円204円345円255円220円345円255円227円347円254円246円344円270円262円.md" "b/problems/0459.351円207円215円345円244円215円347円232円204円345円255円220円345円255円227円347円254円246円344円270円262円.md" index 5d56ad18fb..e26d04ad87 100644 --- "a/problems/0459.351円207円215円345円244円215円347円232円204円345円255円220円345円255円227円347円254円246円344円270円262円.md" +++ "b/problems/0459.351円207円215円345円244円215円347円232円204円345円255円220円345円255円227円347円254円246円344円270円262円.md" @@ -16,18 +16,18 @@ 给定一个非空的字符串,判断它是否可以由它的一个子串重复多次构成。给定的字符串只含有小写英文字母,并且长度不超过10000。 示例 1: -输入: "abab" -输出: True -解释: 可由子字符串 "ab" 重复两次构成。 +* 输入: "abab" +* 输出: True +* 解释: 可由子字符串 "ab" 重复两次构成。 示例 2: -输入: "aba" -输出: False +* 输入: "aba" +* 输出: False 示例 3: -输入: "abcabcabcabc" -输出: True -解释: 可由子字符串 "abc" 重复四次构成。 (或者子字符串 "abcabc" 重复两次构成。) +* 输入: "abcabcabcabc" +* 输出: True +* 解释: 可由子字符串 "abc" 重复四次构成。 (或者子字符串 "abcabc" 重复两次构成。) # 思路 diff --git "a/problems/0684.345円206円227円344円275円231円350円277円236円346円216円245円.md" "b/problems/0684.345円206円227円344円275円231円350円277円236円346円216円245円.md" index 177338dd6d..c4d62d9b46 100644 --- "a/problems/0684.345円206円227円344円275円231円350円277円236円346円216円245円.md" +++ "b/problems/0684.345円206円227円344円275円231円350円277円236円346円216円245円.md" @@ -4,11 +4,8 @@

参与本项目,贡献其他语言版本的代码,拥抱开源,让更多学习算法的小伙伴们收益!

- - # 684.冗余连接 - [力扣题目链接](https://leetcode.cn/problems/redundant-connection/) 树可以看成是一个连通且 无环 的 无向 图。 @@ -78,7 +75,9 @@ void join(int u, int v) { 2. 将两个节点接入到同一个集合,函数:join(int u, int v),将两个节点连在同一个根节点上 3. 判断两个节点是否在同一个集合,函数:isSame(int u, int v),就是判断两个节点是不是同一个根节点 -简单介绍并查集之后,我们再来看一下这道题目。 +如果还不了解并查集,可以看这里:[并查集理论基础](https://programmercarl.com/图论并查集理论基础.html) + +我们再来看一下这道题目。 题目说是无向图,返回一条可以删去的边,使得结果图是一个有着N个节点的树(即:只有一个根节点)。 @@ -92,7 +91,6 @@ void join(int u, int v) { 节点A 和节点 B 不在同一个集合,那么就可以将两个 节点连在一起。 - (如果题目中说:如果有多个答案,则返回二维数组中最前出现的边。 那我们就要 从后向前遍历每一条边了) 如果边的两个节点已经出现在同一个集合里,说明着边的两个节点已经连在一起了,再加入这条边一定就出现环了。 @@ -151,8 +149,6 @@ public: 可以看出,主函数的代码很少,就判断一下边的两个节点在不在同一个集合就可以了。 -这里对并查集就不展开过多的讲解了,翻到了自己十年前写过了一篇并查集的文章[并查集学习](https://blog.csdn.net/youngyangyang04/article/details/6447435),哈哈,那时候还太年轻,写不咋地,有空我会重写并查集基础篇! - # 其他语言版本 diff --git "a/problems/0685.345円206円227円344円275円231円350円277円236円346円216円245円II.md" "b/problems/0685.345円206円227円344円275円231円350円277円236円346円216円245円II.md" index 149eab0148..8c56afdc08 100644 --- "a/problems/0685.345円206円227円344円275円231円350円277円236円346円216円245円II.md" +++ "b/problems/0685.345円206円227円344円275円231円350円277円236円346円216円245円II.md" @@ -104,7 +104,7 @@ if (vec.size()> 0) { } ``` -在来看情况三,明确没有入度为2的情况,那么一定有有向环,找到构成环的边就是要删除的边。 +在来看情况三,明确没有入度为2的情况,那么一定有向环,找到构成环的边就是要删除的边。 可以定义一个函数,代码如下: @@ -122,7 +122,7 @@ vector getRemoveEdge(const vector>& edges) **因为如果两个点所在的边在添加图之前如果就可以在并查集里找到了相同的根,那么这条边添加上之后 这个图一定不是树了** -这里对并查集就不展开过多的讲解了,翻到了自己十年前写过了一篇并查集的文章[并查集学习](https://blog.csdn.net/youngyangyang04/article/details/6447435),哈哈,那时候还太年轻,写不咋地,有空我会重写一篇! +如果还不了解并查集,可以看这里:[并查集理论基础](https://programmercarl.com/图论并查集理论基础.html) 本题C++代码如下:(详细注释了) diff --git "a/problems/0704.344円272円214円345円210円206円346円237円245円346円211円276円.md" "b/problems/0704.344円272円214円345円210円206円346円237円245円346円211円276円.md" index 75135749bd..c59ae868ce 100644 --- "a/problems/0704.344円272円214円345円210円206円346円237円245円346円211円276円.md" +++ "b/problems/0704.344円272円214円345円210円206円346円237円245円346円211円276円.md" @@ -33,10 +33,12 @@ * n 将在 [1, 10000]之间。 * nums 的每个元素都将在 [-9999, 9999]之间。 +## 算法公开课 + +***[《代码随想录》算法视频公开课](https://programmercarl.com/other/gongkaike.html):[手把手带你撕出正确的二分法](https://www.bilibili.com/video/BV1fA4y1o715),相信结合视频再看本篇题解,更有助于大家对本题的理解**。 -## 思路 -为了易于大家理解,我还录制了视频,可以看这里:[B站:手把手带你撕出正确的二分法](https://www.bilibili.com/video/BV1fA4y1o715) +## 思路 **这道题目的前提是数组为有序数组**,同时题目还强调**数组中无重复元素**,因为一旦有重复元素,使用二分查找法返回的元素下标可能不是唯一的,这些都是使用二分法的前提条件,当大家看到题目描述满足如上条件的时候,可要想一想是不是可以用二分法了。 diff --git "a/problems/0707.350円256円276円350円256円241円351円223円276円350円241円250円.md" "b/problems/0707.350円256円276円350円256円241円351円223円276円350円241円250円.md" index aa04d0e1af..87e5f5a933 100644 --- "a/problems/0707.350円256円276円350円256円241円351円223円276円350円241円250円.md" +++ "b/problems/0707.350円256円276円350円256円241円351円223円276円350円241円250円.md" @@ -24,9 +24,13 @@ ![707示例](https://code-thinking-1253855093.file.myqcloud.com/pics/20200814200558953.png) -# 思路 -为了方便大家理解,我特意录制了视频:[帮你把链表操作学个通透!LeetCode:707.设计链表](https://www.bilibili.com/video/BV1FU4y1X7WD),结合视频在看本题解,事半功倍。 +# 算法公开课 + +**[《代码随想录》算法视频公开课](https://programmercarl.com/other/gongkaike.html):[帮你把链表操作学个通透!LeetCode:707.设计链表](https://www.bilibili.com/video/BV1FU4y1X7WD),相信结合视频再看本篇题解,更有助于大家对本题的理解**。 + + +# 思路 如果对链表的基础知识还不太懂,可以看这篇文章:[关于链表,你该了解这些!](https://programmercarl.com/链表理论基础.html) diff --git "a/problems/0977.346円234円211円345円272円217円346円225円260円347円273円204円347円232円204円345円271円263円346円226円271円.md" "b/problems/0977.346円234円211円345円272円217円346円225円260円347円273円204円347円232円204円345円271円263円346円226円271円.md" index a316096e20..de06c41925 100644 --- "a/problems/0977.346円234円211円345円272円217円346円225円260円347円273円204円347円232円204円345円271円263円346円226円271円.md" +++ "b/problems/0977.346円234円211円345円272円217円346円225円260円347円273円204円347円232円204円345円271円263円346円226円271円.md" @@ -13,17 +13,20 @@ 给你一个按 非递减顺序 排序的整数数组 nums,返回 每个数字的平方 组成的新数组,要求也按 非递减顺序 排序。 示例 1: -输入:nums = [-4,-1,0,3,10] -输出:[0,1,9,16,100] -解释:平方后,数组变为 [16,1,0,9,100],排序后,数组变为 [0,1,9,16,100] +* 输入:nums = [-4,-1,0,3,10] +* 输出:[0,1,9,16,100] +* 解释:平方后,数组变为 [16,1,0,9,100],排序后,数组变为 [0,1,9,16,100] 示例 2: -输入:nums = [-7,-3,2,3,11] -输出:[4,9,9,49,121] +* 输入:nums = [-7,-3,2,3,11] +* 输出:[4,9,9,49,121] -# 思路 +# 算法公开课 + +**[《代码随想录》算法视频公开课](https://programmercarl.com/other/gongkaike.html):[双指针法经典题目!LeetCode:977.有序数组的平方](https://www.bilibili.com/video/BV1QB4y1D7ep),相信结合视频再看本篇题解,更有助于大家对本题的理解**。 -针对本题,我录制了视频讲解:[双指针法经典题目!LeetCode:977.有序数组的平方](https://www.bilibili.com/video/BV1QB4y1D7ep),结合本题解一起看,事半功倍! + +# 思路 ## 暴力排序 diff --git "a/problems/1971.345円257円273円346円211円276円345円233円276円344円270円255円346円230円257円345円220円246円345円255円230円345円234円250円350円267円257円345円276円204円.md" "b/problems/1971.345円257円273円346円211円276円345円233円276円344円270円255円346円230円257円345円220円246円345円255円230円345円234円250円350円267円257円345円276円204円.md" index 16c7cb1e85..5f1d894333 100644 --- "a/problems/1971.345円257円273円346円211円276円345円233円276円344円270円255円346円230円257円345円220円246円345円255円230円345円234円250円350円267円257円345円276円204円.md" +++ "b/problems/1971.345円257円273円346円211円276円345円233円276円344円270円255円346円230円257円345円220円246円345円255円230円345円234円250円350円267円257円345円276円204円.md" @@ -31,9 +31,9 @@ ## 思路 -本题是并查集基础题目。 +本题是并查集基础题目。 如果还不了解并查集,可以看这里:[并查集理论基础](https://programmercarl.com/图论并查集理论基础.html) -首先要知道并查集可以解决什么问题呢? +并查集可以解决什么问题呢? 主要就是集合问题,两个节点在不在一个集合,也可以将两个节点添加到一个集合中。 @@ -70,7 +70,7 @@ void join(int u, int v) { } ``` -以上模板中,只要修改 n 大小就可以,本科n不会超过2 * 10^5。 +以上模板中,只要修改 n 大小就可以,本题n不会超过2 * 10^5。 并查集主要有三个功能。

AltStyle によって変換されたページ (->オリジナル) /