图论 图论其实就是深度搜索和广度搜索两部分,基本是这两个算法思路在图上的应用。图一般是许多个相互关联的节点组成,相当于多点多连接的链表或者二叉树。因此,遍历图一般就是上述两种思路。
深度优先搜索理论 DFS 深度优先,顾名思义,先是一个方向探到底,再回头进行别的方向的探索。由于这种性质,深度优先dfs本质上就是回溯算法,回溯算法的终止条件,for剪枝等都可以运用在深度算法上。
给你一个有 n 个节点的 有向无环图(DAG) ,请你找出所有从节点 0 到节点 n-1 的路径并输出(不要求按特定顺序 )
graph[i] 是一个从节点 i 可以访问的所有节点的列表(即从节点 i 到节点 graph[i][j]存在一条有向边)。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 class Solution { List<List<Integer>> ans; List<Integer> cnt; public void dfs (int [][] graph, int node) { if (node == graph.length - 1 ) { ans.add(new ArrayList <>(cnt)); return ; } for (int index = 0 ; index < graph[node].length; index++) { int nextNode = graph[node][index]; cnt.add(nextNode); dfs(graph, nextNode); cnt.remove(cnt.size() - 1 ); } } public List<List<Integer>> allPathsSourceTarget (int [][] graph) { ans = new ArrayList <>(); cnt = new ArrayList <>(); cnt.add(0 ); dfs(graph, 0 ); return ans; } }
广度优先搜索理论 广度搜索和深度搜索形成了比较,深度是一探到底,再回头。广度是先探四周,再往底下深入去探,关键是广度探索的规则 ,比起深度探索是要复杂些。
给你一个由 '1'(陆地)和 '0'(水)组成的的二维网格,请你计算网格中岛屿的数量。
岛屿总是被水包围,并且每座岛屿只能由水平方向和/或竖直方向上相邻的陆地连接形成。
此外,你可以假设该网格的四条边均被水包围。
思路 :岛屿问题是经典的图论,一般是用dfs或者bfs解决。该题中,dfs的关键是如何处理索引越界问题,其实就是在索引越界之前,把越界的索引用if检测出来并抛开。另一个关键是使用淹没法,找到一个1就淹没一整片大陆;在bfs中,第一次接触还不熟悉怎么储存已经访问的数据,其实用一个同等大小的boolean数组储存就行。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 class Solution { public int numIslands (char [][] 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++; dfs(grid,i,j); } } } return res; } public void dfs (char [][] grid, int i, int j) { if (i < 0 || i >= grid.length || j < 0 || j >= grid[0 ].length || grid[i][j] == '0' ) return ; grid[i][j] = '0' ; dfs(grid,i - 1 ,j); dfs(grid,i + 1 ,j); dfs(grid,i,j + 1 ); dfs(grid,i,j - 1 ); } }class Solution { boolean [][] visited; int [][] move = {{0 , 1 }, {0 , -1 }, {1 , 0 }, {-1 , 0 }}; public int numIslands (char [][] 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] && grid[i][j] == '1' ) { bfs(grid, i, j); res++; } } } return res; } public void bfs (char [][] grid, int y, int x) { Deque<int []> queue = new ArrayDeque <>(); queue.offer(new int []{y, x}); visited[y][x] = true ; while (!queue.isEmpty()) { int [] cur = queue.poll(); int m = cur[0 ]; int n = cur[1 ]; for (int i = 0 ; i < 4 ; i++) { int nexty = m + move[i][0 ]; int nextx = n + move[i][1 ]; if (nextx < 0 || nexty == grid.length || nexty < 0 || nextx == grid[0 ].length) continue ; if (!visited[nexty][nextx] && grid[nexty][nextx] == '1' ) { queue.offer(new int []{nexty, nextx}); visited[nexty][nextx] = true ; } } } } }
给你一个大小为 m x n 的二进制矩阵 grid 。
岛屿 是由一些相邻的 1 (代表土地) 构成的组合,这里的「相邻」要求两个 1 必须在 水平或者竖直的四个方向上 相邻。你可以假设 grid 的四个边缘都被 0(代表水)包围着。
岛屿的面积是岛上值为 1 的单元格的数目。
计算并返回 grid 中最大的岛屿面积。如果没有岛屿,则返回面积为 0 。
思路: 这题和上一题相似,都是遍历到以前没访问的陆地时,跳进递归函数先走遍岛屿。还是需要用一个Boolean数组储存是否访问,唯一不同就是需要用max比较并保存最大的岛屿面积。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 class Solution { boolean [][] visited; int [][] move = {{0 , 1 }, {0 , -1 }, {1 , 0 }, {-1 , 0 }}; int tempRes = 1 ; 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] && grid[i][j] == 1 ){ bfs(grid, i, j); res = Math.max(res, tempRes); tempRes = 1 ; } } } return res; } public void bfs (int [][] grid, int y, int x) { Deque<int []> queue = new ArrayDeque <>(); queue.offer(new int []{y, x}); visited[y][x] = true ; while (!queue.isEmpty()){ int [] cur = queue.poll(); int m = cur[0 ]; int n = cur[1 ]; for (int i = 0 ; i < 4 ; i++){ int next_y = m + move[i][0 ]; int next_x = n + move[i][1 ]; if (next_x < 0 || next_y >= grid.length || next_y < 0 || next_x == grid[0 ].length) continue ; if (!visited[next_y][next_x] && grid[next_y][next_x] == 1 ){ queue.offer(new int []{next_y, next_x}); visited[next_y][next_x] = true ; tempRes++; } } } } }
给你一个大小为 m x n 的二进制矩阵 grid ,其中 0 表示一个海洋单元格、1 表示一个陆地单元格。
一次 移动 是指从一个陆地单元格走到另一个相邻(上、下、左、右 )的陆地单元格或跨过 grid 的边界。
返回网格中 无法 在任意次数的移动中离开网格边界的陆地单元格的数量。
思路: 这题也是寻岛屿的面积,不过要排除掉能联通边界的岛屿,有一个讨巧的思路,就是先遍历四个边界,这样一定先能找到所有联通边界的岛屿,然后用水淹掉就行。完了再遍历除去四条边的区域,只要有1就可以直接算到结果。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 class Solution { private static final int [][] position = {{-1 , 0 }, {0 , 1 }, {1 , 0 }, {0 , -1 }}; public void bfs (int [][] grid, Queue<int []> queue, boolean [][] visited) { while (!queue.isEmpty()) { int [] curPos = queue.poll(); for (int [] current: position) { int row = curPos[0 ] + current[0 ], col = curPos[1 ] + current[1 ]; if (row < 0 || row >= grid.length || col < 0 || col >= grid[0 ].length) continue ; if (visited[row][col] || grid[row][col] == 0 ) continue ; visited[row][col] = true ; queue.add(new int []{row, col}); } } } public int numEnclaves (int [][] grid) { int rowSize = grid.length, colSize = grid[0 ].length, ans = 0 ; boolean [][] visited = new boolean [rowSize][colSize]; Queue<int []> queue = new ArrayDeque <>(); for (int row = 0 ; row < rowSize; row++) { if (grid[row][0 ] == 1 ) { visited[row][0 ] = true ; queue.add(new int []{row, 0 }); } if (grid[row][colSize - 1 ] == 1 ) { visited[row][colSize - 1 ] = true ; queue.add(new int []{row, colSize - 1 }); } } for (int col = 1 ; col < colSize - 1 ; col++) { if (grid[0 ][col] == 1 ) { visited[0 ][col] = true ; queue.add(new int []{0 , col}); } if (grid[rowSize - 1 ][col] == 1 && !visited[rowSize - 1 ][col]) { visited[rowSize - 1 ][col] = true ; queue.add(new int []{rowSize - 1 , col}); } } bfs(grid, queue, visited); for (int row = 0 ; row < rowSize; row++) { for (int col = 0 ; col < colSize; col++) { if (grid[row][col] == 1 && !visited[row][col]) ++ans; } } return ans; } }
给你一个 m x n 的矩阵 board ,由若干字符 'X' 和 'O' ,找到所有被 'X' 围绕的区域,并将这些区域里所有的 'O' 用 'X' 填充。
思路: 这题就是飞地数量的翻版,还是先遍历四条边,然后用visited数组储存能联通的区域。而对于除去四条边的区域,没有visited又是陆地的,直接淹掉。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 class Solution { private static final int [][] position = {{-1 , 0 }, {0 , 1 }, {1 , 0 }, {0 , -1 }}; public void solve (char [][] board) { int rowSize = board.length, colSize = board[0 ].length; boolean [][] visited = new boolean [rowSize][colSize]; Queue<int []> queue = new ArrayDeque <>(); for (int row = 0 ; row < rowSize; row++) { if (board[row][0 ] == 'O' ) { visited[row][0 ] = true ; queue.add(new int []{row, 0 }); } if (board[row][colSize - 1 ] == 'O' ) { visited[row][colSize - 1 ] = true ; queue.add(new int []{row, colSize - 1 }); } } for (int col = 1 ; col < colSize - 1 ; col++) { if (board[0 ][col] == 'O' ) { visited[0 ][col] = true ; queue.add(new int []{0 , col}); } if (board[rowSize - 1 ][col] == 'O' ) { visited[rowSize - 1 ][col] = true ; queue.add(new int []{rowSize - 1 , col}); } } while (!queue.isEmpty()) { int [] current = queue.poll(); for (int [] pos: position) { int row = current[0 ] + pos[0 ], col = current[1 ] + pos[1 ]; if (row < 0 || row >= rowSize || col < 0 || col >= colSize) continue ; if (visited[row][col] || board[row][col] != 'O' ) continue ; visited[row][col] = true ; queue.add(new int []{row, col}); } } for (int row = 0 ; row < rowSize; row++) { for (int col = 0 ; col < colSize; col++) { if (board[row][col] == 'O' && !visited[row][col]) board[row][col] = 'X' ; } } } }
有一个 m × n 的矩形岛屿,与 太平洋 和 大西洋 相邻。 “太平洋” 处于大陆的左边界和上边界,而 “大西洋” 处于大陆的右边界和下边界。
这个岛被分割成一个由若干方形单元格组成的网格。给定一个 m x n 的整数矩阵 heights , heights[r][c] 表示坐标 (r, c) 上单元格 高于海平面的高度 。
岛上雨水较多,如果相邻单元格的高度 小于或等于 当前单元格的高度,雨水可以直接向北、南、东、西流向相邻单元格。水可以从海洋附近的任何单元格流入海洋。
返回网格坐标 result 的 2D 列表 ,其中 result[i] = [ri, ci] 表示雨水从单元格 (ri, ci) 流动 既可流向太平洋也可流向大西洋 。
思路: 这题使用bfs遍历每一个元素,如果可以同时到达左上和右下两个边界,那么就加入res。但是这种方法比较耗时,我们反向思维,从两个海洋的边界出发逆流而上 ,并使用标记数组visited,取两者的交叉点,就是答案数组 。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 class Solution { private static final int [][] position = {{-1 , 0 }, {0 , 1 }, {1 , 0 }, {0 , -1 }}; public void bfs (int [][] heights, Queue<int []> queue, boolean [][][] visited) { while (!queue.isEmpty()) { int [] curPos = queue.poll(); for (int [] current: position) { int row = curPos[0 ] + current[0 ], col = curPos[1 ] + current[1 ], sign = curPos[2 ]; if (row < 0 || row >= heights.length || col < 0 || col >= heights[0 ].length) continue ; if (heights[row][col] < heights[curPos[0 ]][curPos[1 ]] || visited[row][col][sign]) continue ; visited[row][col][sign] = true ; queue.add(new int []{row, col, sign}); } } } public List<List<Integer>> pacificAtlantic (int [][] heights) { int rowSize = heights.length, colSize = heights[0 ].length; List<List<Integer>> ans = new ArrayList <>(); boolean [][][] visited = new boolean [rowSize][colSize][2 ]; Queue<int []> queue = new ArrayDeque <>(); for (int row = 0 ; row < rowSize; row++) { visited[row][colSize - 1 ][0 ] = true ; visited[row][0 ][1 ] = true ; queue.add(new int []{row, colSize - 1 , 0 }); queue.add(new int []{row, 0 , 1 }); } for (int col = 0 ; col < colSize; col++) { visited[rowSize - 1 ][col][0 ] = true ; visited[0 ][col][1 ] = true ; queue.add(new int []{rowSize - 1 , col, 0 }); queue.add(new int []{0 , col, 1 }); } bfs(heights, queue, visited); for (int row = 0 ; row < rowSize; row++) { for (int col = 0 ; col < colSize; col++) { if (visited[row][col][0 ] && visited[row][col][1 ]) ans.add(List.of(row, col)); } } return ans; } }
给你一个大小为 n x n 二进制矩阵 grid 。最多 只能将一格 0 变成 1 。
返回执行此操作后,grid 中最大的岛屿面积是多少?
岛屿 由一组上、下、左、右四个方向相连的 1 形成。
思路 :这题的暴力解法很直观,就是遍历每一个0,然后在每一个0 dfs,判断新岛屿的面积大小。但这种解法非常慢,因为岛屿会在其周边的每一个0都遍历一次,其实这是没必要的。优化思路: 先遍历一遍所有的岛屿,遍历过程记录每个岛屿的面积,并且将每个岛屿的值改成岛屿的编号,用一个key-value的hashmap来维护岛屿编号和岛屿面积。之后遍历0就行,0只需要遍历上下左右四个方向临近有没有岛,加上相邻的岛值,然后取最大值。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 class Solution { private static final int [][] position = {{-1 , 0 }, {0 , 1 }, {1 , 0 }, {0 , -1 }}; public int dfs (int [][] grid, int row, int col, int mark) { int ans = 0 ; grid[row][col] = mark; for (int [] current: position) { int curRow = row + current[0 ], curCol = col + current[1 ]; if (curRow < 0 || curRow >= grid.length || curCol < 0 || curCol >= grid.length) continue ; if (grid[curRow][curCol] == 1 ) ans += 1 + dfs(grid, curRow, curCol, mark); } return ans; } public int largestIsland (int [][] grid) { int ans = Integer.MIN_VALUE, size = grid.length, mark = 2 ; Map<Integer, Integer> getSize = new HashMap <>(); for (int row = 0 ; row < size; row++) { for (int col = 0 ; col < size; col++) { if (grid[row][col] == 1 ) { int areaSize = 1 + dfs(grid, row, col, mark); getSize.put(mark++, areaSize); } } } for (int row = 0 ; row < size; row++) { for (int col = 0 ; col < size; col++) { if (grid[row][col] != 0 ) continue ; Set<Integer> hashSet = new HashSet <>(); int curSize = 1 ; for (int [] current: position) { int curRow = row + current[0 ], curCol = col + current[1 ]; if (curRow < 0 || curRow >= grid.length || curCol < 0 || curCol >= grid.length) continue ; int curMark = grid[curRow][curCol]; if (hashSet.contains(curMark) || !getSize.containsKey(curMark)) continue ; hashSet.add(curMark); curSize += getSize.get(curMark); } ans = Math.max(ans, curSize); } } return ans == Integer.MIN_VALUE ? size * size : ans; } }
字典 wordList 中从单词 beginWord 和 endWord 的 转换序列 是一个按下述规格形成的序列 beginWord -> s1 -> s2 -> ... -> sk:
每一对相邻的单词只差一个字母。
对于 1 <= i <= k 时,每个 si 都在 wordList 中。注意, beginWord 不需要在 wordList 中。
sk == endWord
给你两个单词 beginWord 和 endWord 和一个字典 wordList ,返回 从 beginWord 到 endWord 的 最短转换序列 中的 单词数目 。如果不存在这样的转换序列,返回 0 。
思路: 这道题明显是一个无向搜索 ,那么就一定要使用标记位来记录以访问元素,防止重复访问造成死循环;求的是最短序列,那么就要使用bfs来解,这样解出来就是最短的序列。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 class Solution { public int ladderLength (String beginWord, String endWord, List<String> wordList) { HashSet<String> wordSet = new HashSet <>(wordList); if (wordSet.size() == 0 || !wordSet.contains(endWord)) { return 0 ; } Queue<String> queue = new LinkedList <>(); queue.offer(beginWord); Map<String, Integer> map = new HashMap <>(); map.put(beginWord, 1 ); while (!queue.isEmpty()) { String word = queue.poll(); int path = map.get(word); for (int i = 0 ; i < word.length(); i++) { char [] chars = word.toCharArray(); for (char k = 'a' ; k <= 'z' ; k++) { chars[i] = k; String newWord = String.valueOf(chars); if (newWord.equals(endWord)) { return path + 1 ; } if (wordSet.contains(newWord) && !map.containsKey(newWord)) { map.put(newWord, path + 1 ); queue.offer(newWord); } } } } return 0 ; } }
有 n 个房间,房间按从 0 到 n - 1 编号。最初,除 0 号房间外的其余所有房间都被锁住。你的目标是进入所有的房间。然而,你不能在没有获得钥匙的时候进入锁住的房间。
当你进入一个房间,你可能会在里面找到一套不同的钥匙,每把钥匙上都有对应的房间号,即表示钥匙可以打开的房间。你可以拿上所有钥匙去解锁其他房间。
给你一个数组 rooms 其中 rooms[i] 是你进入 i 号房间可以获得的钥匙集合。如果能进入 所有 房间返回 true,否则返回 false。
思路 :这道题需要判断能不能进入,那么采用dfs会更好,bfs一般是用在最短路径的。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 class Solution { boolean [] visited; public boolean canVisitAllRooms (List<List<Integer>> rooms) { int [][] roomsArray = new int [rooms.size()][]; for (int i = 0 ; i < rooms.size(); i++) { List<Integer> room = rooms.get(i); roomsArray[i] = new int [room.size()]; for (int j = 0 ; j < room.size(); j++) { roomsArray[i][j] = room.get(j); } } visited = new boolean [roomsArray.length]; Deque<Integer> deque = new ArrayDeque <>(); for (int key : roomsArray[0 ]){ deque.add(key); visited[0 ] = true ; } while (!deque.isEmpty()){ int curKey = deque.poll(); visited[curKey] = true ; for (int nextKey : roomsArray[curKey]){ if (!visited[nextKey]){ deque.add(nextKey); visited[nextKey] = true ; } } } for (boolean temp : visited){ if (!temp){ return false ; } } return true ; } }
有一个具有 n 个顶点的 双向 图,其中每个顶点标记从 0 到 n - 1(包含 0 和 n - 1)。图中的边用一个二维整数数组 edges 表示,其中 edges[i] = [ui, vi] 表示顶点 ui 和顶点 vi 之间的双向边。 每个顶点对由 最多一条 边连接,并且没有顶点存在与自身相连的边。
请你确定是否存在从顶点 source 开始,到顶点 destination 结束的 有效路径 。
给你数组 edges 和整数 n、source 和 destination,如果从 source 到 destination 存在 有效路径 ,则返回 true,否则返回 false 。
思路: 这道题是经典的并查集,先初始化,然后join加入节点关系,通过find进行路径压缩。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 class Solution { int [] father; public boolean validPath (int n, int [][] edges, int source, int destination) { father = new int [n]; init(); for (int i = 0 ; i < edges.length; i++) { join(edges[i][0 ], edges[i][1 ]); } return isSame(source, destination); } public void init () { for (int i = 0 ; i < father.length; i++) { father[i] = i; } } public int find (int u) { if (u == father[u]) { return u; } else { father[u] = find(father[u]); return father[u]; } } public boolean isSame (int u, int v) { u = find(u); v = find(v); return u == v; } public void join (int u, int v) { u = find(u); v = find(v); if (u == v) return ; father[v] = u; } }
树可以看成是一个连通且 无环 的 无向 图。
给定往一棵 n 个节点 (节点值 1~n) 的树中添加一条边后的图。添加的边的两个顶点包含在 1 到 n 中间,且这条附加的边不属于树中已存在的边。图的信息记录于长度为 n 的二维数组 edges ,edges[i] = [ai, bi] 表示图中在 ai 和 bi 之间存在一条边。
请找出一条可以删去的边,删除后可使得剩余部分是一个有着 n 个节点的树。如果有多个答案,则返回数组 edges 中最后出现的那个。
思路: 经典并查集问题,删除后节点数量不变,也就是说明删除的这条边,一定在环上。返回最后出现那条边,则说明了连成环的最后一条边,那么正常从前到后遍历就行。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 class Solution { int [] father; public int [] findRedundantConnection(int [][] edges) { father = new int [edges.length + 1 ]; init(); for (int [] cur : edges){ if (find(cur[0 ]) == find(cur[1 ])){ return cur; }else { join(cur[0 ], cur[1 ]); } } return null ; } public void init () { for (int i = 0 ; i < father.length; i++){ father[i] = i; } } public void join (int cur1, int cur2) { cur1 = find(cur1); cur2 = find(cur2); if (cur1 == cur2) return ; father[cur2] = cur1; } public int find (int index) { if (index == father[index]){ return index; }else { father[index] = find(father[index]); return father[index]; } } }
在本问题中,有根树指满足以下条件的 有向 图。该树只有一个根节点,所有其他节点都是该根节点的后继。该树除了根节点之外的每一个节点都有且只有一个父节点,而根节点没有父节点。
输入一个有向图,该图由一个有着 n 个节点(节点值不重复,从 1 到 n)的树及一条附加的有向边构成。附加的边包含在 1 到 n 中的两个不同顶点间,这条附加的边不属于树中已存在的边。
结果图是一个以边组成的二维数组 edges 。 每个元素是一对 [ui, vi],用以表示 有向 图中连接顶点 ui 和顶点 vi 的边,其中 ui 是 vi 的一个父节点。
返回一条能删除的边,使得剩下的图是有 n 个节点的有根树。若有多个答案,返回最后出现在给定二维数组的答案。
思路: 这题是有向的图,如果都是单方向的还好,那么思路和上一题的就一样了。区别点在于,在有向的图中,当一个节点的入度大于1,那么这一定是一个环,就看剪掉哪条边。剪掉哪边取决于剩下的图是否构成树,如果是环,那么就一定不是树,反之就是环。
这题思路复杂,伪代码如下:
1 2 3 4 5 6 7 8 9 if (存在入度大于1 ){ if (剪1 ,剩下图构成有向环){ return 2 ; }else { return 1 ; } }else { 上一题并查集典型操作; }
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 class Solution { private static final int N = 1010 ; private int [] father; public Solution () { father = new int [N]; for (int i = 0 ; i < N; ++i) { father[i] = i; } } private int find (int u) { if (u == father[u]) { return u; } father[u] = find(father[u]); return father[u]; } private void join (int u, int v) { u = find(u); v = find(v); if (u == v) return ; father[v] = u; } private Boolean same (int u, int v) { u = find(u); v = find(v); return u == v; } private void initFather () { for (int i = 0 ; i < N; ++i) { father[i] = i; } } private int [] getRemoveEdge(int [][] edges) { initFather(); for (int i = 0 ; i < edges.length; i++) { if (same(edges[i][0 ], edges[i][1 ])) { return edges[i]; } join(edges[i][0 ], edges[i][1 ]); } return null ; } private Boolean isTreeAfterRemoveEdge (int [][] edges, int deleteEdge) { initFather(); for (int i = 0 ; i < edges.length; i++) { if (i == deleteEdge) continue ; if (same(edges[i][0 ], edges[i][1 ])) { return false ; } join(edges[i][0 ], edges[i][1 ]); } return true ; } public int [] findRedundantDirectedConnection(int [][] edges) { int [] inDegree = new int [N]; for (int i = 0 ; i < edges.length; i++) { inDegree[ edges[i][1 ] ] += 1 ; } ArrayList<Integer> twoDegree = new ArrayList <Integer>(); for (int i = edges.length - 1 ; i >= 0 ; i--) { if (inDegree[edges[i][1 ]] == 2 ) { twoDegree.add(i); } } if (!twoDegree.isEmpty()) { if (isTreeAfterRemoveEdge(edges, twoDegree.get(0 ))) { return edges[ twoDegree.get(0 )]; } return edges[ twoDegree.get(1 )]; } return getRemoveEdge(edges); } }