1,广度优先遍历
面试题13,机器人的运动范围
题目:地上有一个m行n列的方格,从坐标
[0,0]
到坐标[m-1,n-1]
。一个机器人从坐标[0, 0]
的格子开始移动,它每次可以向左、右、上、下移动一格(不能移动到方格外),也不能进入行坐标和列坐标的数位之和大于k的格子。例如,当k为18时,机器人能够进入方格 [35, 37] ,因为3+5+3+7=18。但它不能进入方格 [35, 38],因为3+5+3+8=19。请问该机器人能够到达多少个格子?class Solution { int[] dx = {1, 0, 0, -1}; int[] dy = {0, 1, -1, 0}; public int movingCount(int m, int n, int k) { ArrayList arrayList = new ArrayList(); Queue<int[]> queue = new LinkedList(); queue.add(new int[]{0, 0}); arrayList.add(0 + "," + 0); while (!queue.isEmpty()) { int[] middle = queue.poll(); for (int i = 0; i < 4; i++) { int newX = middle[0] + dx[i]; int newY = middle[1] + dy[i]; int[] newPoint = new int[]{newX, newY}; if (!arrayList.contains(newX + "," + newY)) { int t = subtractProductAndSum(newX) + subtractProductAndSum(newY); if (newX >= 0 && newX < m && newY >= 0 && newY < n && t <= k) { queue.add(newPoint); arrayList.add(newX + "," + newY); } } } } return arrayList.size(); } public int subtractProductAndSum(int n) { int sum = 0; while (n != 0) { sum += n % 10; n /= 10; } return sum; } }
79,单词搜索
题目:给定一个
m x n
二维字符网格board
和一个字符串单词word
。如果word
存在于网格中,返回true
;否则,返回false
。单词必须按照字母顺序,通过相邻的单元格内的字母构成,其中“相邻”单元格是那些水平相邻或垂直相邻的单元格。同一个单元格内的字母不允许被重复使用。class Solution { public boolean exist(char[][] board, String word) { int h = board.length, w = board[0].length; boolean[][] visited = new boolean[h][w]; for (int i = 0; i < h; i++) { for (int j = 0; j < w; j++) { boolean flag = check(board, visited, i, j, word, 0); if (flag) { return true; } } } return false; } public boolean check(char[][] board, boolean[][] visited, int i, int j, String s, int k) { if (board[i][j] != s.charAt(k)) { return false; } else if (k == s.length() - 1) { return true; } visited[i][j] = true; int[][] directions = {{0, 1}, {0, -1}, {1, 0}, {-1, 0}}; boolean result = false; for (int[] dir : directions) { int newi = i + dir[0], newj = j + dir[1]; if (newi >= 0 && newi < board.length && newj >= 0 && newj < board[0].length) { if (!visited[newi][newj]) { boolean flag = check(board, visited, newi, newj, s, k + 1); if (flag) { result = true; break; } } } } visited[i][j] = false; return result; } }
85,最大矩形/最大方阵/最大子矩阵
题目:给定一个仅包含
0
和1
、大小为rows x cols
的二维二进制矩阵,找出只包含1
的最大矩形,并返回其面积。思路1:双指针
class Solution { public int maximalRectangle(String[] matrix) { if (matrix == null || matrix.length == 0) return 0; int rows = matrix.length+1,cols = matrix[0].length(),max=0; int[][] m = new int[rows][cols]; for (int i = 1; i < rows; i++) { for (int j = 0; j < cols; j++) { if (matrix[i-1].charAt(j) != '0') { m[i][j] = m[i-1][j] + matrix[i-1].charAt(j) - '0'; } } max = Math.max(max,largestRectangleArea(m[i])); } return max; } //数据少用双指针,数据超多用单调栈,双指针,时间O(n*m),空间O(1) public int largestRectangleArea(int[] heights) { int max = 0, l = 0, r = 0, len = heights.length; for (int i = 0; i < len; i++) { l = i - 1; r = i + 1; if (len * heights[i] > max) { while ((l >= 0) && heights[l] >= heights[i]) l--; while ((r < len) && heights[r] >= heights[i]) r++; max = Math.max(max, (r - l - 1) * heights[i]); } } return max; } }
思路2:
- 每一层看作是柱状图,可以套用84题柱状图的最大面积。
- 第一层柱状图的高度["1","0","1","0","0"],最大面积为1;
- 第二层柱状图的高度["2","0","2","1","1"],最大面积为3;
- 第三层柱状图的高度["3","1","3","2","2"],最大面积为6;
- 第四层柱状图的高度["4","0","0","3","0"],最大面积为4;
class Solution { public int maximalRectangle(char[][] matrix) { if (matrix.length == 0 || matrix[0].length == 0) { return 0; } int col = matrix.length; int row = matrix[0].length; int[] heights = new int[row]; int ans = 0; for (int i = 0; i < col; i++) { for (int j = 0; j < row; j++) { if (matrix[i][j] == '1') { heights[j] += 1; } else { heights[j] = 0; } } ans = Math.max(ans, largestRectangleArea(heights)); } return ans; } public int largestRectangleArea(int[] heights) { int res = 0; Deque<Integer> stack = new ArrayDeque<>(); int[] new_heights = new int[heights.length + 2]; for (int i = 1; i < heights.length + 1; i++) { new_heights[i] = heights[i - 1]; } ; for (int i = 0; i < new_heights.length; i++) { while (!stack.isEmpty() && new_heights[stack.peek()] > new_heights[i]) { int cur = stack.pop(); res = Math.max(res, (i - stack.peek() - 1) * new_heights[cur]); } stack.push(i); } return res; } }
最大黑方阵:给定一个方阵,其中每个单元(像素)非黑即白。设计一个算法,找出 4 条边皆为黑色像素的最大子方阵。返回一个数组
[r, c, size]
,其中r
,c
分别代表子方阵左上角的行号和列号,size
是子方阵的边长。若有多个满足条件的子方阵,返回r
最小的,若r
相同,返回c
最小的子方阵。若无满足条件的子方阵,返回空数组。class Solution { public int[] findSquare(int[][] grid) { int m = grid.length, n = grid[0].length; // 遍历长度,左上角的位置 int limit = Math.min(m, n); for (int l = limit; l >= 1; l--) { for (int i = 0; i <= m - l; i++) { for (int j = 0; j <= n - l; j++) { if (check(i, j, l, grid)) return new int[]{i, j, l}; } } } return new int[]{}; } public boolean check(int r, int c, int l, int[][] grid) { // 查看边界是否全为1 for (int i = r; i < r + l; i++) { if (grid[i][c] == 1) return false; if (grid[i][c + l - 1] == 1) return false; } for (int j = c; j < c + l; j++) { if (grid[r][j] == 1) return false; if (grid[r + l - 1][j] == 1) return false; } return true; } }
LeetCode 1139:最大的以1为边界的正方形:给你一个由若干
0
和1
组成的二维网格grid
,请你找出边界全部由1
组成的最大 正方形 子网格,并返回该子网格中的元素数量。如果不存在,则返回0
。class Solution { // 可以暴力 public int largest1BorderedSquare(int[][] grid) { int m = grid.length, n = grid[0].length; // 遍历长度,左上角的位置 int limit = Math.min(m, n); for (int l = limit; l >= 1; l--) { for (int i = 0; i <= m - l; i++) { for (int j = 0; j <= n - l; j++) { if (check(i, j, l, grid)) return l * l; } } } return 0; } public boolean check(int r, int c, int l, int[][] grid) { // 查看边界是否全为1 for (int i = r; i < r + l; i++) { if (grid[i][c] == 0) return false; if (grid[i][c + l - 1] == 0) return false; } for (int j = c; j < c + l; j++) { if (grid[r][j] == 0) return false; if (grid[r + l - 1][j] == 0) return false; } return true; } }
最大子矩阵: 给定一个正整数、负整数和 0 组成的 N × M 矩阵,编写代码找出元素总和最大的子矩阵。返回一个数组
[r1, c1, r2, c2]
,其中r1
,c1
分别代表子矩阵左上角的行号和列号,r2
,c2
分别代表右下角的行号和列号。若有多个满足条件的子矩阵,返回任意一个均可。思路:将原问题转化为求解所有以每一行为底边的子矩阵的最大子矩阵和。然后,再在这些最大子矩阵中找出和最大的子矩阵。
- 创建一个长度为列数的数组
dp
,用于存储当前行的最大子矩阵和。- 遍历每一行,对于每一列,将当前列的值加到
dp
数组中对应的位置上。- 对
dp
数组进行求最大子数组和的操作,得到当前行的最大子矩阵和。- 如果当前行的最大子矩阵和大于全局的最大子矩阵和,则更新全局的最大子矩阵和,并记录当前行的行号和列号作为右下角的行号和列号。
- 重复步骤2-4,直到遍历完所有行。
- 返回记录的行号和列号作为结果。
class Solution { public int[] getMaxMatrix(int[][] matrix) { int rows = matrix.length; int cols = matrix[0].length; int maxSum = Integer.MIN_VALUE; int[] result = new int[4]; for (int i = 0; i < rows; i++) { int[] dp = new int[cols]; for (int j = i; j < rows; j++) { for (int k = 0; k < cols; k++) { dp[k] += matrix[j][k]; } int[] subArray = maxSubArray(dp); int sum = subArray[2]; if (sum > maxSum) { maxSum = sum; result[0] = i; result[1] = subArray[0]; result[2] = j; result[3] = subArray[1]; } } } return result; } private int[] maxSubArray(int[] nums) { int maxSum = Integer.MIN_VALUE; int sum = 0; int start = 0; int end = 0; int temp = 0; for (int i = 0; i < nums.length; i++) { if (sum < 0) { sum = nums[i]; temp = i; } else { sum += nums[i]; } if (sum > maxSum) { maxSum = sum; start = temp; end = i; } } return new int[]{start, end, maxSum}; } }
127,单词接龙
题目:字典
wordList
中从单词beginWord
和endWord
的 转换序列 是一个按下述规格形成的序列beginWord -> s1 -> s2 -> ... -> sk
:
- 每一对相邻的单词只差一个字母。
- 对于
1 <= i <= k
时,每个si
都在wordList
中。注意,beginWord
不需要在wordList
中。sk == endWord
给你两个单词
beginWord
和endWord
和一个字典wordList
,返回 从beginWord
到endWord
的 最短转换序列 中的 单词数目 。如果不存在这样的转换序列,返回0
。class Solution { public int ladderLength(String beginWord, String endWord, List<String> wordList) { int n = beginWord.length(); if(endWord.length() != n) return 0; if(!wordList.contains(endWord)) return 0; // 记录已经搜索过的状态 Set<String> searched = new HashSet<>(); searched.add(beginWord); Deque<String> queue = new LinkedList<>(); queue.offer(beginWord); int step = 1; while(!queue.isEmpty()){ int size = queue.size(); for(int i=0;i<size;i++){ String status = queue.poll(); if(status.equals(endWord)) return step; for(String next : getNextStatus(status, wordList)){ // 保证下一状态未被搜索过,且单词列表中包含该元素 if(!searched.contains(next) && wordList.contains(next)){ searched.add(next); queue.offer(next); } } } step++; } return 0; } public List<String> getNextStatus(String status, List<String> wordList){ int n = wordList.size(); List<String> list = new ArrayList<>(); int len = status.length(); for(String word : wordList){ if(word.length() != len) continue; // 比较两个单词是否只差一个字母 int diff = 0; for(int i=0;i<len;i++){ if(status.charAt(i) != word.charAt(i)) diff++; } if(diff == 1) list.add(word); } return list; } }
130,被围绕的区域
题目:给你一个
m x n
的矩阵board
,由若干字符'X'
和'O'
,找到所有被'X'
围绕的区域,并将这些区域里所有的'O'
用'X'
填充。思路:从边是'O'的字符处开始dfs, 先给标记成其他符号,代表着非包围字符, 那么剩下的'O'就都是被包围的字符了, 然后遍历矩阵, 将被包围的字符置为'X', 未被包围的字符置回'O'即可。如果直接顺序遍历,会出现全部O的情况下仍然修改。
class Solution { int[] dx = {1, -1, 0, 0}; int[] dy = {0, 0, 1, -1}; public void solve(char[][] board) { int n = board.length; if (n == 0) { return; } int m = board[0].length; Queue<int[]> queue = new LinkedList<int[]>(); for (int i = 0; i < n; i++) { if (board[i][0] == 'O') { queue.offer(new int[]{i, 0}); board[i][0] = 'A'; } if (board[i][m - 1] == 'O') { queue.offer(new int[]{i, m - 1}); board[i][m - 1] = 'A'; } } for (int i = 1; i < m - 1; i++) { if (board[0][i] == 'O') { queue.offer(new int[]{0, i}); board[0][i] = 'A'; } if (board[n - 1][i] == 'O') { queue.offer(new int[]{n - 1, i}); board[n - 1][i] = 'A'; } } while (!queue.isEmpty()) { int[] cell = queue.poll(); int x = cell[0], y = cell[1]; for (int i = 0; i < 4; i++) { int mx = x + dx[i], my = y + dy[i]; if (mx < 0 || my < 0 || mx >= n || my >= m || board[mx][my] != 'O') { continue; } queue.offer(new int[]{mx, my}); board[mx][my] = 'A'; } } for (int i = 0; i < n; i++) { for (int j = 0; j < m; j++) { if (board[i][j] == 'A') { board[i][j] = 'O'; } else if (board[i][j] == 'O') { board[i][j] = 'X'; } } } } }
200,岛屿的数量
题目:给你一个由 '1'(陆地)和 '0'(水)组成的的二维网格,请你计算网格中岛屿的数量。岛屿总是被水包围,并且每座岛屿只能由水平方向和/或竖直方向上相邻的陆地连接形成。此外,你可以假设该网格的四条边均被水包围。
输入:grid = [ ["1","1","1","1","0"], ["1","1","0","1","0"], ["1","1","0","0","0"], ["0","0","0","0","0"] ] 输出:1
class Solution { int[] dx = {1, 0, 0, -1}; int[] dy = {0, 1, -1, 0}; public int numIslands(char[][] grid) { ArrayList arrayList = new ArrayList(); Queue<int[]> queue = new LinkedList(); int heigh = grid.length; int width = grid[0].length; int max = 0; for (int i = 0; i <heigh; i++) { queue.clear(); for (int j = 0; j < width; j++) { if (grid[i][j] == '1') { if (!arrayList.contains(new int[]{i, j})) { max++; queue.add(new int[]{i, j}); arrayList.add(new int[]{i, j}); grid[i][j]=0; while (!queue.isEmpty()) { int middle[] = queue.poll(); for (int k = 0; k < 4; k++) { int newX = middle[0] + dx[k]; int newY = middle[1] + dy[k]; int[] newPoint = new int[]{newX, newY}; if (newX >= 0 && newX < heigh && newY >= 0 && newY < width && grid[newX][newY] == '1') { arrayList.add(newPoint); queue.add(newPoint); grid[newX][newY]='0'; } } } } } } } return max; } }
201,水域的数量
题目:你有一个用于表示一片土地的整数矩阵
land
,该矩阵中每个点的值代表对应地点的海拔高度。若值为0则表示水域。由垂直、水平或对角连接的水域为池塘。池塘的大小是指相连接的水域的个数。编写一个方法来计算矩阵中所有池塘的大小,返回值需要从小到大排序。public class Solution{ int[] dx = {1, -1, 0, 0, 1, 1, -1, -1}; int[] dy = {0, 0, 1, -1, 1, -1, 1, -1}; public int[] pondSizes(int[][] land) { ArrayList<Integer> result = new ArrayList(); ArrayList arrayList = new ArrayList(); Queue<int[]> queue = new LinkedList(); int heigh = land.length; int width = land[0].length; int max = 0; for (int i = 0; i < heigh; i++) { queue.clear(); for (int j = 0; j < width; j++) { if (land[i][j] == 0) { max = 0; if (!arrayList.contains(new int[]{i, j})) { max++; queue.add(new int[]{i, j}); arrayList.add(new int[]{i, j}); land[i][j] = 1; while (!queue.isEmpty()) { int middle[] = queue.poll(); for (int k = 0; k < 8; k++) { int newX = middle[0] + dx[k]; int newY = middle[1] + dy[k]; int[] newPoint = new int[]{newX, newY}; if (newX >= 0 && newX < heigh && newY >= 0 && newY < width && land[newX][newY] == 0) { arrayList.add(newPoint); queue.add(newPoint); land[newX][newY] = 1; max++; } } } result.add(max); } } } } int[] re = new int[result.size()]; for (int i = 0; i < re.length; i++) { re[i] = result.get(i); } Arrays.sort(re); return re; } }
210,课程表 II
题目:现在你总共有
numCourses
门课需要选,记为0
到numCourses - 1
。给你一个数组prerequisites
,其中prerequisites[i] = [ai, bi]
,表示在选修课程ai
前 必须 先选修bi
。
- 例如,想要学习课程
0
,你需要先完成课程1
,我们用一个匹配来表示:[0,1]
。返回你为了学完所有课程所安排的学习顺序。可能会有多个正确的顺序,你只要返回 任意一种 就可以了。如果不可能完成所有课程,返回 一个空数组 。
思路:先找到没有入度的结点,入队; 将队列中的队首元素出队,其出度对应元素入队,并取消掉度; 不断重复该过程,直到队列为空。
class Solution { public int[] findOrder(int numCourses, int[][] prerequisites) { Deque<Integer> queue=new ArrayDeque<>(); int [] indegree=new int [numCourses]; int [] res=new int [numCourses]; int cnt=0;//一个指针,表示res数组实际有的元素个数 int n=prerequisites.length; //某元素对应的入度数 for(int i=0;i<n;i++){ indegree[prerequisites[i][0]]++; } //先找到没有入度的结点,入队 for(int i=0;i<numCourses;i++){ if(indegree[i]==0) queue.offer(i); } while(!queue.isEmpty()){ //将队列中的队首元素出队 int peek=queue.poll(); res[cnt++]=peek; for(int []p:prerequisites){ //其出度对应元素入队,并取消掉度; if(p[1]==peek){ indegree[p[0]]--; if(indegree[p[0]]==0){ queue.offer(p[0]); } } } } return cnt==numCourses?res:new int [0]; } }
417,太平洋大西洋水流问题
题目:有一个
m × n
的矩形岛屿,与 太平洋 和 大西洋 相邻。 “太平洋” 处于大陆的左边界和上边界,而 “大西洋” 处于大陆的右边界和下边界。这个岛被分割成一个由若干方形单元格组成的网格。给定一个m x n
的整数矩阵heights
,heights[r][c]
表示坐标(r, c)
上单元格 高于海平面的高度 。岛上雨水较多,如果相邻单元格的高度 小于或等于 当前单元格的高度,雨水可以直接向北、南、东、西流向相邻单元格。水可以从海洋附近的任何单元格流入海洋。返回网格坐标
result
的 2D 列表 ,其中result[i] = [ri, ci]
表示雨水从单元格(ri, ci)
流动 既可流向太平洋也可流向大西洋 。思路:如果直接以每个单元格作为起点模拟雨水的流动,则会重复遍历每个单元格,导致时间复杂度过高。为了降低时间复杂度,可以从矩阵的边界开始反向搜索寻找雨水流向边界的单元格,反向搜索时,每次只能移动到高度相同或更大的单元格。
class Solution { static int[][] dirs = {{-1, 0}, {1, 0}, {0, -1}, {0, 1}}; int[][] heights; int m, n; public List<List<Integer>> pacificAtlantic(int[][] heights) { this.heights = heights; this.m = heights.length; this.n = heights[0].length; boolean[][] pacific = new boolean[m][n]; boolean[][] atlantic = new boolean[m][n]; for (int i = 0; i < m; i++) { bfs(i, 0, pacific); } for (int j = 1; j < n; j++) { bfs(0, j, pacific); } for (int i = 0; i < m; i++) { bfs(i, n - 1, atlantic); } for (int j = 0; j < n - 1; j++) { bfs(m - 1, j, atlantic); } List<List<Integer>> result = new ArrayList<List<Integer>>(); for (int i = 0; i < m; i++) { for (int j = 0; j < n; j++) { if (pacific[i][j] && atlantic[i][j]) { List<Integer> cell = new ArrayList<Integer>(); cell.add(i); cell.add(j); result.add(cell); } } } return result; } public void bfs(int row, int col, boolean[][] ocean) { if (ocean[row][col]) { return; } ocean[row][col] = true; Queue<int[]> queue = new ArrayDeque<int[]>(); queue.offer(new int[]{row, col}); while (!queue.isEmpty()) { int[] cell = queue.poll(); for (int[] dir : dirs) { int newRow = cell[0] + dir[0], newCol = cell[1] + dir[1]; if (newRow >= 0 && newRow < m && newCol >= 0 && newCol < n && heights[newRow][newCol] >= heights[cell[0]][cell[1]] && !ocean[newRow][newCol]) { ocean[newRow][newCol] = true; queue.offer(new int[]{newRow, newCol}); } } } } }
542,01矩阵
题目:给定一个由 0 和 1 组成的矩阵 mat ,请输出一个大小相同的矩阵,其中每一个格子是 mat 中对应位置元素到最近的 0 的距离。两个相邻元素间的距离为 1 。
输入:mat = [[0,0,0],[0,1,0],[1,1,1]] 输出:[[0,0,0],[0,1,0],[1,2,1]]
class Solution { static int[] dx = {1, 0, 0, -1}; static int[] dy = {0, 1, -1, 0}; public int[][] updateMatrix(int[][] matrix) { // 首先将 0 边上的 1 入队 int[] dx = new int[] {-1, 1, 0, 0}; int[] dy = new int[] {0, 0, -1, 1}; Queue<int[]> queue = new LinkedList<>(); int m = matrix.length, n = matrix[0].length; int[][] res = new int[m][n]; for (int i = 0; i < m; i++) { for (int j = 0; j < n; j++) { if (matrix[i][j] == 0) { for (int k = 0; k < 4; k++) { int x = i + dx[k]; int y = j + dy[k]; if (x >= 0 && x < m && y >= 0 && y < n && matrix[x][y] == 1 && res[x][y] == 0) { // 这是在 0 边上的1。需要加上 res[x][y] == 0 的判断防止重复入队 res[x][y] = 1; queue.offer(new int[] {x, y}); } } } } } while (!queue.isEmpty()) { int[] point = queue.poll(); int x = point[0], y = point[1]; for (int i = 0; i < 4; i++) { int newX = x + dx[i]; int newY = y + dy[i]; if (newX >= 0 && newX < m && newY >= 0 && newY < n && matrix[newX][newY] == 1 && res[newX][newY] == 0) { res[newX][newY] = res[x][y] + 1; queue.offer(new int[] {newX, newY}); } } } return res; } }
547,省份数量
题目:有 n 个城市,其中一些彼此相连,另一些没有相连。如果城市 a 与城市 b 直接相连,且城市 b 与城市 c 直接相连,那么城市 a 与城市 c 间接相连。省份 是一组直接或间接相连的城市,组内不含其他没有相连的城市。给你一个 n x n 的矩阵 isConnected ,其中 isConnected[i][j] = 1 表示第 i 个城市和第 j 个城市直接相连,而 isConnected[i][j] = 0 表示二者不直接相连。返回矩阵中 省份 的数量。
class Solution { int[] dx = {1, 0, 0, -1}; int[] dy = {0, 1, -1, 0}; public int findCircleNum(int[][] grid) { Queue<int[]> queue = new LinkedList(); int heigh = grid.length; int width = grid[0].length; int max = 0; for (int i = 0; i < heigh; i++) { queue.clear(); //没有被访问过 if (grid[i][i] == 1) { //System.out.println(i); max++; queue.add(new int[]{i, i}); grid[i][i] = 0; for (int j = i; j < width; j++) { if (grid[i][j] == 1) { queue.add(new int[]{j, j}); grid[i][j] = 0; grid[j][j] = 0; grid[j][i] = 0; } } while (!queue.isEmpty()) { int middle[] = queue.poll(); for (int j = 0; j < width; j++) { if (grid[middle[0]][j] == 1) { queue.add(new int[]{j, j}); grid[middle[0]][j] = 0; grid[j][j] = 0; grid[j][middle[0]] = 0; } } } } } return max; } }
684,多余的边(冗余连接)
题目:树可以看成是一个连通且 无环 的 无向 图。给定往一棵
n
个节点 (节点值1~n
) 的树中添加一条边后的图。添加的边的两个顶点包含在1
到n
中间,且这条附加的边不属于树中已存在的边。图的信息记录于长度为n
的二维数组edges
,edges[i] = [ai, bi]
表示图中在ai
和bi
之间存在一条边。请找出一条可以删去的边,删除后可使得剩余部分是一个有着n
个节点的树。如果有多个答案,则返回数组edges
中最后出现的边。class Solution { int[] parent; int find(int idx) { if (parent[idx] != idx) parent[idx] = find(parent[idx]); return parent[idx]; } void union(int x, int y) { parent[find(x)] = find(y); } public int[] findRedundantConnection(int[][] edges) { int n = edges.length; parent = new int[n + 1]; for (int i = 0; i < n; i++) { parent[i] = i; } for (int[] e : edges) { int x = e[0], y = e[1]; if (find(x) != find(y)) union(x, y); else return e; } return new int[0]; } }
695,岛屿最大面积
题目:给你一个大小为 m x n 的二进制矩阵 grid 。岛屿 是由一些相邻的 1 (代表土地) 构成的组合,这里的「相邻」要求两个 1 必须在 水平或者竖直的四个方向上 相邻。你可以假设 grid 的四个边缘都被 0(代表水)包围着。岛屿的面积是岛上值为 1 的单元格的数目。计算并返回 grid 中最大的岛屿面积。如果没有岛屿,则返回面积为 0 。
class Solution { static int[] dx = {1, 0, 0, -1}; static int[] dy = {0, 1, -1, 0}; public int maxAreaOfIsland(int[][] grid) { ArrayList arrayList = new ArrayList(); Queue<int[]> queue = new LinkedList(); int heigh = grid.length; int width = grid[0].length; int max = 0; for (int i = 0; i <heigh; i++) { queue.clear(); for (int j = 0; j < width; j++) { int temp = 0; if (grid[i][j] == 1) { if (!arrayList.contains(new int[]{i, j})) { queue.add(new int[]{i, j}); arrayList.add(new int[]{i, j}); grid[i][j]=0; while (!queue.isEmpty()) { int middle[] = queue.poll(); temp += 1; for (int k = 0; k < 4; k++) { int newX = middle[0] + dx[k]; int newY = middle[1] + dy[k]; int[] newPoint = new int[]{newX, newY}; if (newX >= 0 && newX < heigh && newY >= 0 && newY < width && grid[newX][newY] == 1) { arrayList.add(newPoint); queue.add(newPoint); grid[newX][newY]=0; } } } if (max<temp){ max = temp; } } } } } return max; } }
733,图像渲染
题目:有一幅以 m x n 的二维整数数组表示的图画 image ,其中 image[i][j] 表示该图画的像素值大小。你也被给予三个整数 sr , sc 和 newColor 。你应该从像素 image[sr][sc] 开始对图像进行 上色填充 。为了完成 上色工作 ,从初始像素开始,记录初始坐标的 上下左右四个方向上 像素值与初始坐标相同的相连像素点,接着再记录这四个方向上符合条件的像素点与他们对应 四个方向上 像素值与初始坐标相同的相连像素点,……,重复该过程。将所有有记录的像素点的颜色值改为 newColor 。最后返回 经过上色渲染后的图像 。
class Solution { int[] dx = {1, 0, 0, -1}; int[] dy = {0, 1, -1, 0}; public int[][] floodFill(int[][] image, int sr, int sc, int color) { //没有换色 int currColor = image[sr][sc]; if (currColor == color) return image; int oldColor = image[sr][sc]; int heigh = image.length; int width = image[0].length; Queue<int[]> queue = new LinkedList(); queue.add(new int[]{sr, sc}); image[sr][sc] = color; while (!queue.isEmpty()) { int middle[] = queue.poll(); for (int i = 0; i < 4; i++) { int newX = middle[0] + dx[i]; int newY = middle[1] + dy[i]; int[] newPoint = new int[]{newX, newY}; if (newX>=0&&newX<heigh&&newY>=0&&newY<width&&image[newX][newY]==oldColor){ queue.add(newPoint); image[newX][newY]=color; } } } return image; } }
752,打开转盘锁
题目:你有一个带有四个圆形拨轮的转盘锁。每个拨轮都有10个数字:
'0', '1', '2', '3', '4', '5', '6', '7', '8', '9'
。每个拨轮可以自由旋转:例如把'9'
变为'0'
,'0'
变为'9'
。每次旋转都只能旋转一个拨轮的一位数字。锁的初始数字为'0000'
,一个代表四个拨轮的数字的字符串。列表
deadends
包含了一组死亡数字,一旦拨轮的数字和列表里的任何一个元素相同,这个锁将会被永久锁定,无法再被旋转。字符串
target
代表可以解锁的数字,你需要给出解锁需要的最小旋转次数,如果无论如何不能解锁,返回-1
。class Solution { String[] section = new String[]{"1000","9000","0100","0900","0010","0090","0001","0009"}; //广度优先的八个方向 public int openLock(String[] deadends, String target) { if(target.equals("0000")) return 0; int[][][][] key = new int[10][10][10][10]; for (String deadend : deadends) { //假设deadends已经搜过了 key[deadend.charAt(0) - '0'][deadend.charAt(1) - '0'][deadend.charAt(2) - '0'][deadend.charAt(3) - '0'] = 1; } if(key[0][0][0][0] == 1) return -1; Queue<String> queue = new LinkedList<>(); queue.offer("0000"); while (!queue.isEmpty()) { String temp = queue.poll(); int a = temp.charAt(0) - '0', b = temp.charAt(1) - '0', c = temp.charAt(2) - '0', d = temp.charAt(3) - '0'; int step = key[a][b][c][d]; int aa, bb, cc, dd; String bfsString; for (String sec : section) { //遍历八个操作,正转9下等于倒转1下 aa = (sec.charAt(0) - '0' + a) % 10; bb = (sec.charAt(1) - '0' + b) % 10; cc = (sec.charAt(2) - '0' + c) % 10; dd = (sec.charAt(3) - '0' + d) % 10; if (key[aa][bb][cc][dd] == 0) { bfsString = "" + aa + bb + cc + dd; if(bfsString.equals(target)) return step+1; key[aa][bb][cc][dd] = step + 1; queue.offer(bfsString); } } } return -1; //到这一步,说明没搜到,就是-1 } }
815,公交路线
题目:给你一个数组
routes
,表示一系列公交线路,其中每个routes[i]
表示一条公交线路,第i
辆公交车将会在上面循环行驶。
- 例如,路线
routes[0] = [1, 5, 7]
表示第0
辆公交车会一直按序列1 -> 5 -> 7 -> 1 -> 5 -> 7 -> 1 -> ...
这样的车站路线行驶。现在从
source
车站出发(初始时不在公交车上),要前往target
车站。 期间仅可乘坐公交车。求出 最少乘坐的公交车数量 。如果不可能到达终点车站,返回-1
。思路:力扣
class Solution { public int numBusesToDestination(int[][] routes, int source, int target) { if (source == target) { return 0; } int n = routes.length; boolean[][] edge = new boolean[n][n]; Map<Integer, List<Integer>> rec = new HashMap<Integer, List<Integer>>(); for (int i = 0; i < n; i++) { for (int site : routes[i]) { List<Integer> list = rec.getOrDefault(site, new ArrayList<Integer>()); for (int j : list) { edge[i][j] = edge[j][i] = true; } list.add(i); rec.put(site, list); } } int[] dis = new int[n]; Arrays.fill(dis, -1); Queue<Integer> que = new LinkedList<Integer>(); for (int bus : rec.getOrDefault(source, new ArrayList<Integer>())) { dis[bus] = 1; que.offer(bus); } while (!que.isEmpty()) { int x = que.poll(); for (int y = 0; y < n; y++) { if (edge[x][y] && dis[y] == -1) { dis[y] = dis[x] + 1; que.offer(y); } } } int ret = Integer.MAX_VALUE; for (int bus : rec.getOrDefault(target, new ArrayList<Integer>())) { if (dis[bus] != -1) { ret = Math.min(ret, dis[bus]); } } return ret == Integer.MAX_VALUE ? -1 : ret; } }
994,腐烂的橘子
题目:在给定的 m x n 网格 grid 中,每个单元格可以有以下三个值之一:
- 值 0 代表空单元格;
- 值 1 代表新鲜橘子;
- 值 2 代表腐烂的橘子。
每分钟,腐烂的橘子 周围 4 个方向上相邻 的新鲜橘子都会腐烂。返回 直到单元格中没有新鲜橘子为止所必须经过的最小分钟数。如果不可能,返回 -1 。
class Solution { int[] dr = new int[]{-1, 0, 1, 0}; int[] dc = new int[]{0, -1, 0, 1}; public int orangesRotting(int[][] grid) { int R = grid.length, C = grid[0].length; Queue<Integer> queue = new ArrayDeque<Integer>(); Map<Integer, Integer> depth = new HashMap<Integer, Integer>(); for (int r = 0; r < R; ++r) { for (int c = 0; c < C; ++c) { if (grid[r][c] == 2) { int code = r * C + c; queue.add(code); depth.put(code, 0); } } } int ans = 0; while (!queue.isEmpty()) { int code = queue.remove(); int r = code / C, c = code % C; for (int k = 0; k < 4; ++k) { int nr = r + dr[k]; int nc = c + dc[k]; if (0 <= nr && nr < R && 0 <= nc && nc < C && grid[nr][nc] == 1) { grid[nr][nc] = 2; int ncode = nr * C + nc; queue.add(ncode); depth.put(ncode, depth.get(code) + 1); ans = depth.get(ncode); } } } for (int[] row: grid) { for (int v: row) { if (v == 1) { return -1; } } } return ans; } }
1901,寻找峰值 II
题目:一个 2D 网格中的 峰值 是指那些 严格大于 其相邻格子(上、下、左、右)的元素。
给你一个 从 0 开始编号 的 m x n 矩阵 mat ,其中任意两个相邻格子的值都 不相同 。找出 任意一个 峰值 mat[i][j] 并 返回其位置 [i,j] 。你可以假设整个矩阵周边环绕着一圈值为 -1 的格子。要求必须写出时间复杂度为 O(m log(n)) 或 O(n log(m)) 的算法。
思路:遍历,遇到比自己大的,切换到该值,重复,直至周围都比自己小。
class Solution { int[] dx = {1, 0, 0, -1}; int[] dy = {0, 1, -1, 0}; public int[] findPeakGrid(int[][] mat) { Queue<int[]> queue = new LinkedList(); int heigh = mat.length; int width = mat[0].length; int[] t = new int[]{0, 0}; queue.add(t); while (!queue.isEmpty()) { int middle[] = queue.poll(); int k = 0; for (; k < 4; k++) { int newX = middle[0] + dx[k]; int newY = middle[1] + dy[k]; int[] newPoint = new int[]{newX, newY}; if (newX >= 0 && newX < heigh && newY >= 0 && newY < width && mat[newX][newY] > mat[middle[0]][middle[1]]) { queue.add(newPoint); break; } } if (k == 4) { return middle; } } return null; } }
2,深度优先遍历
841,钥匙和房间
题目:有 n 个房间,房间按从 0 到 n - 1 编号。最初,除 0 号房间外的其余所有房间都被锁住。你的目标是进入所有的房间。然而,你不能在没有获得钥匙的时候进入锁住的房间。当你进入一个房间,你可能会在里面找到一套不同的钥匙,每把钥匙上都有对应的房间号,即表示钥匙可以打开的房间。你可以拿上所有钥匙去解锁其他房间。给你一个数组 rooms 其中 rooms[i] 是你进入 i 号房间可以获得的钥匙集合。如果能进入 所有 房间返回 true,否则返回 false。
class Solution { public boolean canVisitAllRooms(List<List<Integer>> rooms) { Queue<List> queue = new LinkedList(); boolean[] visited = new boolean[rooms.size()]; queue.add(rooms.get(0)); while (!queue.isEmpty()) { ArrayList<Integer> temp = (ArrayList) queue.poll(); for (int i = 0; i < temp.size(); i++) { if (!visited[temp.get(i)]) { queue.add(rooms.get(temp.get(i))); visited[temp.get(i)]=true; } } } for (int i = 1; i < visited.length; i++) { if (visited[i] == false) { return false; } } return true; } }
997,找到小镇的法官
题目:小镇里有 n 个人,按从 1 到 n 的顺序编号。传言称,这些人中有一个暗地里是小镇法官。如果小镇法官真的存在,那么:
- 小镇法官不会信任任何人。
- 每个人(除了小镇法官)都信任这位小镇法官。
- 只有一个人同时满足属性 1 和属性 2 。
给你一个数组 trust ,其中 trust[i] = [ai, bi] 表示编号为 ai 的人信任编号为 bi 的人。
如果小镇法官存在并且可以确定他的身份,请返回该法官的编号;否则,返回 -1 。
思路:在法官存在的情况下,法官不相信任何人,每个人(除了法官外)都信任法官,且只有一名法官。因此法官这个节点的入度是 n-1, 出度是 0。遍历每个节点的入度和出度,如果找到一个符合条件的节点,由于题目保证只有一个法官,我们可以直接返回结果;如果不存在符合条件的点,则返回 -1。
class Solution { public int findJudge(int n, int[][] trust) { int[] inDegrees = new int[n + 1]; int[] outDegrees = new int[n + 1]; for (int[] edge : trust) { int x = edge[0], y = edge[1]; ++inDegrees[y]; ++outDegrees[x]; } for (int i = 1; i <= n; ++i) { if (inDegrees[i] == n - 1 && outDegrees[i] == 0) { return i; } } return -1; } }
1557,可以到达所有点的最少点数
题目:给你一个 有向无环图 , n 个节点编号为 0 到 n-1 ,以及一个边数组 edges ,其中 edges[i] = [fromi, toi] 表示一条从点 fromi 到点 toi 的有向边。找到最小的点集使得从这些点出发能到达图中所有点。题目保证解存在且唯一。你可以以任意顺序返回这些节点编号。
思路:所有出节点和入节点的集合,如果一个出节点不在入节点集合中,说明出度为0,符合结果。相反,一个节点可以没有出节点,当叶子节点,但不能没有入节点,否则出度为0,这也是为啥用hash找hasSet的原因。
class Solution { public List<Integer> findSmallestSetOfVertices(int n, List<List<Integer>> edges) { List<Integer> res = new ArrayList<>(); HashSet<Integer> hashSet = new HashSet<>(); HashSet<Integer> hash = new HashSet<>(); for (int i = 0; i < edges.size(); i++) { hash.add(edges.get(i).get(0)); hashSet.add(edges.get(i).get(1)); } for (Integer a : hash) { if(!hashSet.contains(a)){ res.add(a); } } return res; } }
3,最短路径
24,节点间通路
题目:节点间通路。给定有向图,设计一个算法,找出两个节点之间是否存在一条路径。
思路:使用广度优先搜索判断是否存在从顶点 start 到顶点 target 的路径,需要从顶点 start 开始依次遍历每一层的顶点,判断可以到达顶点 target。广度优先搜索需要使用哈希表(或数组)记录每个顶点的访问状态,使用队列存储最近访问过的顶点。初始时将顶点 start 设为已访问,并将其入队列。每次将一个顶点 vertex 出队列,对于每个与 vertex 相邻且未访问的顶点 next,将 next 设为已访问,并将其入队列。当队列为空或访问到顶点 target 时,遍历结束,将顶点 target的访问状态返回。
class Solution { public boolean findWhetherExistsPath(int n, int[][] graph, int start, int target) { Set<Integer>[] adjacentArr = new Set[n]; for (int i = 0; i < n; i++) { adjacentArr[i] = new HashSet<Integer>(); } for (int[] edge : graph) { if (edge[0] != edge[1]) { adjacentArr[edge[0]].add(edge[1]); } } boolean[] visited = new boolean[n]; visited[start] = true; Queue<Integer> queue = new ArrayDeque<Integer>(); queue.offer(start); while (!queue.isEmpty() && !visited[target]) { int vertex = queue.poll(); Set<Integer> adjacent = adjacentArr[vertex]; for (int next : adjacent) { if (!visited[next]) { visited[next] = true; queue.offer(next); } } } return visited[target]; } }
329,矩阵中的最长递增路径
题目:给定一个
m x n
整数矩阵matrix
,找出其中 最长递增路径 的长度。对于每个单元格,你可以往上,下,左,右四个方向移动。 你 不能 在 对角线 方向上移动或移动到 边界外(即不允许环绕)。
class Solution { // 基本思路; // 深度优先遍历,遍历所有可能经过的地点 // 动态规划,记录各个地点的最长路径值 // dfs遍历每个地点(不重复经过)并累计路径长度,返回最长的路径值即可 int ans = Integer.MIN_VALUE; int[] dx = new int[]{0, 0, -1, 1}; int[] dy = new int[]{1, -1, 0, 0}; int[][] flag; public int longestIncreasingPath(int[][] matrix) { int m = matrix.length, n = matrix[0].length; flag = new int[m][n]; for(int i = 0; i < m; i++){ for(int j = 0; j < n; j++){ // 防止重复经过 if(flag[i][j] == 0){ flag[i][j] = dfs(matrix, m, n, i, j, -1); } ans = Math.max(ans, flag[i][j]); } } return ans; } private int dfs(int[][] matrix, int m, int n, int i, int j,int prev) { // 特判,防止下标越界 if (i < 0 || i >= m || j < 0 || j >= n || matrix[i][j] <= prev) { return 0; } // 当前地点已经过,返回经过该地点的路径长度 if (flag[i][j] != 0) { return flag[i][j]; } // 当前地点未经过,获取来自各个方向上满足条件的最长路径值 flag[i][j] = 1; int max = Integer.MIN_VALUE; for(int d = 0; d < 4; d++){ int di = i + dx[d]; int dj = j + dy[d]; int val = dfs(matrix, m, n, di, dj, matrix[i][j]); max = Math.max(val, max); } // 累计最长路径值 flag[i][j] += max; return flag[i][j]; } }
787,K 站中转内最便宜的航班
题目:有
n
个城市通过一些航班连接。给你一个数组flights
,其中flights[i] = [fromi, toi, pricei]
,表示该航班都从城市fromi
开始,以价格pricei
抵达toi
。现在给定所有的城市和航班,以及出发城市
src
和目的地dst
,你的任务是找到出一条最多经过k
站中转的路线,使得从src
到dst
的 价格最便宜 ,并返回该价格。 如果不存在这样的路线,则输出-1
。思路:贝尔曼方程
class Solution { int INF = 0x3f3f3f3f; public int findCheapestPrice(int n, int[][] flights, int src, int dst, int k) { int[] dist = new int[n]; Arrays.fill(dist, INF); dist[src] = 0; while (k-- >= 0) { int[] clone = dist.clone(); for (int[] flight : flights) { int from = flight[0]; int to = flight[1]; int price = flight[2]; // s->t = s->x + x->t dist[to] = Math.min(dist[to], clone[from] + price); } } return dist[dst] >= INF ? -1 : dist[dst]; } }
797,所有可能的路径
题目:给你一个有 n 个节点的 有向无环图(DAG),请你找出所有从节点 0 到节点 n-1 的路径并输出(不要求按特定顺序) graph[i] 是一个从节点 i 可以访问的所有节点的列表(即从节点 i 到节点 graph[i][j]存在一条有向边)。
class Solution { List<List<Integer>> ans = new ArrayList<List<Integer>>(); Deque<Integer> stack = new ArrayDeque<Integer>(); public List<List<Integer>> allPathsSourceTarget(int[][] graph) { stack.offerLast(0); dfs(graph, 0, graph.length - 1); return ans; } public void dfs(int[][] graph, int x, int n) { if (x == n) { ans.add(new ArrayList<Integer>(stack)); return; } for (int y : graph[x]) { stack.offerLast(y); dfs(graph, y, n); stack.pollLast(); } } }
1091,二进制矩阵中的最短路径
题目:给你一个 n x n 的二进制矩阵 grid 中,返回矩阵中最短 畅通路径 的长度。如果不存在这样的路径,返回 -1 。二进制矩阵中的 畅通路径 是一条从 左上角 单元格(即,(0, 0))到 右下角 单元格(即,(n - 1, n - 1))的路径,该路径同时满足下述要求:
路径途经的所有单元格都的值都是 0 。路径中所有相邻的单元格应当在 8 个方向之一 上连通(即,相邻两单元之间彼此不同且共享一条边或者一个角)。畅通路径的长度 是该路径途经的单元格总数。
思路:广义优先遍历,每层步数+1。
class Solution { int[] dx = {1, -1, 0, 0, 1, -1, -1, 1}; int[] dy = {0, 0, 1, -1, 1, -1, 1, -1}; public int shortestPathBinaryMatrix(int[][] grid) { Queue<int[]> queue = new LinkedList(); int heigh = grid.length; int width = grid[0].length; int start = grid[0][0]; if (heigh == 1) { if (grid[0][0]==1){ return -1; }else { return 1; } } else if (heigh == 0) { return -1; } else if (start == 1) { return -1; } else if (grid[heigh - 1][heigh - 1] == 1) { return -1; } else { queue.add(new int[]{0, 0}); } int min = 1; while (!queue.isEmpty()) { min++; int levelNum = queue.size();//获取当前层的节点数. for (int j = 0; j < levelNum; j++) { int[] cell = queue.poll(); for (int i = 0; i < dx.length; i++) { int newX = cell[0] + dx[i]; int newY = cell[1] + dy[i]; if (newX == heigh - 1 && newY == newX&&grid[newX][newY]==0) { return min; } if (newX >= 0 && newX < heigh && newY >= 0 && newY < width && grid[newX][newY] == 0) { queue.add(new int[]{newX, newY}); grid[newX][newY]=1; } } } } return -1; } }
4,染色问题
256,粉刷房子
题目:假如有一排房子,共 n 个,每个房子可以被粉刷成红色、蓝色或者绿色这三种颜色中的一种,你需要粉刷所有的房子并且使其相邻的两个房子颜色不能相同。
当然,因为市场上不同颜色油漆的价格不同,所以房子粉刷成不同颜色的花费成本也是不同的。每个房子粉刷成不同颜色的花费是以一个 n x 3 的正整数矩阵 costs 来表示的。
例如,costs[0][0] 表示第 0 号房子粉刷成红色的成本花费;costs[1][2] 表示第 1 号房子粉刷成绿色的花费,以此类推。请计算出粉刷完所有房子最少的花费成本。
思路1:前缀和
class Solution { public int minCost(int[][] costs) { int n=costs.length; int ans[][]=new int[n+1][3]; for(int i=1;i<=n;i++){ ans[i][0]=Math.min(ans[i-1][1],ans[i-1][2])+costs[i-1][0]; ans[i][1]=Math.min(ans[i-1][2],ans[i-1][0])+costs[i-1][1]; ans[i][2]=Math.min(ans[i-1][1],ans[i-1][0])+costs[i-1][2]; } return Math.min(Math.min(ans[n][0],ans[n][1]),ans[n][2]); } }
785,判断二分图
题目:存在一个 无向图 ,图中有
n
个节点。其中每个节点都有一个介于0
到n - 1
之间的唯一编号。给你一个二维数组graph
,其中graph[u]
是一个节点数组,由节点u
的邻接节点组成。形式上,对于graph[u]
中的每个v
,都存在一条位于节点u
和节点v
之间的无向边。该无向图同时具有以下属性:
- 不存在自环(
graph[u]
不包含u
)。- 不存在平行边(
graph[u]
不包含重复值)。- 如果
v
在graph[u]
内,那么u
也应该在graph[v]
内(该图是无向图)- 这个图可能不是连通图,也就是说两个节点
u
和v
之间可能不存在一条连通彼此的路径。二分图 定义:如果能将一个图的节点集合分割成两个独立的子集
A
和B
,并使图中的每一条边的两个节点一个来自A
集合,一个来自B
集合,就将这个图称为 二分图 。如果图是二分图,返回true
;否则,返回false
。class Solution { // 染色法判定二分图 // 0表示未访问,1表示红色,-1表示蓝色 public boolean isBipartite(int[][] graph) { int n = graph.length; int[] color = new int[n]; Deque<Integer> deque = new ArrayDeque<>(); // 可能存在多个连通分量 for (int i = 0; i < n; i++) { if (color[i] != 0) continue; // 染成红色并入队 deque.addLast(i); color[i] = 1; // 当队列不为空 while (!deque.isEmpty()) { int cur = deque.pollFirst(); for (int e : graph[cur]) { // 相同顶点颜色相同,不符合要求 if (color[e] == color[cur]) return false; // 当前结点未访问 if (color[e] == 0) { //染成相反的颜色 color[e] = -color[cur]; deque.addLast(e); } } } } return true; } }
1042,不邻接植花
题目:有
n
个花园,按从1
到n
标记。另有数组paths
,其中paths[i] = [xi, yi]
描述了花园xi
到花园yi
的双向路径。在每个花园中,你打算种下四种花之一。另外,所有花园 最多 有 3 条路径可以进入或离开。你需要为每个花园选择一种花,使得通过路径相连的任何两个花园中的花的种类互不相同。以数组形式返回 任一 可行的方案作为答案
answer
,其中answer[i]
为在第(i+1)
个花园中种植的花的种类。花的种类用 1、2、3、4 表示。保证存在答案。思路:同一条路径连接的两个花园为相邻花园,则任意两个相邻花园中的花的种类互不相同。由于有 4 种花,每个花园最多有 3 个相邻的花园,因此一定存在答案。
为了确保相邻花园中的花的种类互不相同,需要遍历所有的路径,得到每个花园的相邻花园,然后按照编号从 1 到 n 的顺序依次遍历每个花园并决定每个花园中的花的种类。由于遍历花园的顺序是按照编号递增顺序,因此记录每个花园的相邻花园时只需要记录比当前花园的编号小的相邻花园。当遍历到一个花园时,比当前花园的编号小的所有花园都已经决定了花园中的花的种类。
遍历花园的过程中,对于每个花园,得到与当前花园相邻且编号比当前花园的编号小的全部花园,这些花园中的花的种类都已知,称为相邻花的种类。默认当前花园中的花的种类是 1,如果花的种类在相邻花的种类中出现,则将花的种类加 1,直到花的种类不在相邻花的种类中出现,则可确定当前花园中的花的种类。
class Solution { public int[] gardenNoAdj(int n, int[][] paths) { List<Integer>[] adjacentGardens = new List[n]; for (int i = 0; i < n; i++) { adjacentGardens[i] = new ArrayList<Integer>(); } for (int[] path : paths) { int garden0 = path[0] - 1, garden1 = path[1] - 1; if (garden0 < garden1) { adjacentGardens[garden1].add(garden0); } else { adjacentGardens[garden0].add(garden1); } } int[] answer = new int[n]; for (int i = 0; i < n; i++) { List<Integer> adjacent = adjacentGardens[i]; boolean[] used = new boolean[5]; for (int garden : adjacent) { int adjacentType = answer[garden]; used[adjacentType] = true; } int type = 1; while (used[type]) { type++; } answer[i] = type; } return answer; } }
5,拓扑结构
269,外星文字典
题目:现有一种使用英语字母的外星文语言,这门语言的字母顺序与英语顺序不同。
给定一个字符串列表
words
,作为这门语言的词典,words
中的字符串已经 按这门新语言的字母顺序进行了排序 。请你根据该词典还原出此语言中已知的字母顺序,并 按字母递增顺序 排列。若不存在合法字母顺序,返回
""
。若存在多种可能的合法字母顺序,返回其中 任意一种 顺序即可。字符串
s
字典顺序小于 字符串t
有两种情况:
- 在第一个不同字母处,如果
s
中的字母在这门外星语言的字母顺序中位于t
中字母之前,那么s
的字典顺序小于t
。- 如果前面
min(s.length, t.length)
字母都相同,那么s.length < t.length
时,s
的字典顺序也小于t
。思路:
- 先标记所有出现过的字母,没出现过的字母不要在答案中出现;
- 正向建图(存储某个字母的儿子):用list存储,反向建图,用set存储;
- 把所有出现过的点(字母)并且没有父节点的(入度为0)点先拿出来组成字母表前缀,这些是字母表中最可能的最靠前的字母们;
- BFS取出每一个字母,同时把它从相应儿子的set中删除,假如此时set已经空,这说明这个点的祖先你们全都遍历过了,而且也加到字母表里面了,那么这个字母也能够假如字母表;
- 此时遍历所有set,假如还有不空的,说明拓扑排序存在环,则不存再答案;
- 对于有些出现了的,但是还没有跟其他字母存在先后关系的字母,也要拼在字母表后边,既然出现了,也不可以落下呀,
注意陷阱:
1、
建图不要重复加入边,出错案例:["ac","ab","zc","zb"]
2、
后者不能是前者的真前缀,出错案例:["abc","ab"]
class Solution { public String alienOrder(String[] words) { boolean has[]=new boolean[26];//记录字母是否出现过 char c[][]=new char[words.length][]; for(int i=0;i<c.length;i++){ c[i]=words[i].toCharArray(); for(int j=0;j<c[i].length;j++){has[c[i][j]-'a']=true;} } List<Integer> son[]=new List[26]; Set<Integer> father[]=new Set[26]; for(int i=0;i<26;i++){ son[i]=new ArrayList<>(); father[i]=new HashSet<>(); } //以下正反建图: for(int i=1;i<c.length;i++){ int p=0; while(p<Math.min(c[i-1].length,c[i].length)&&c[i-1][p]==c[i][p]){p++;} if(p==c[i].length&&p<c[i-1].length){return "";}//后者为前者的真前缀,报警了啊 if(p<c[i].length&&p<c[i-1].length){ //此时遇到的首个不同字母,有关字母表顺序,主注意去重 if(father[c[i][p]-'a'].add(c[i-1][p]-'a')){son[c[i-1][p]-'a'].add(c[i][p]-'a');} } } Queue<Integer> q=new LinkedList<>(); StringBuilder ans=new StringBuilder(); //先把没有父节点的字母收集起来,这些是字母中的前排 for(int i=0;i<26;i++){ if(has[i]&&father[i].size()==0){ ans.append((char)(i+'a')); has[i]=false; q.add(i); } } //下边开始BFS: while(q.size()>0){ int a=q.poll(); for(int i=0;i<son[a].size();i++){ int b=son[a].get(i); father[b].remove(a); if(father[b].size()==0){ ans.append((char)(b+'a')); has[b]=false; q.add(b); } } } //下边开始检查有无set不是空的,有的话说明存在环,顺便把无顺序关系的字母加进来 for(int i=0;i<26;i++){ if(father[i].size()>0){return "";} if(has[i]){ans.append((char)(i+'a'));} } return ans.toString(); } }
953,验证外星语词典
题目:某种外星语也使用英文小写字母,但可能顺序 order 不同。字母表的顺序(order)是一些小写字母的排列。给定一组用外星语书写的单词 words,以及其字母表的顺序 order,只有当给定的单词在这种外星语中按字典序排列时,返回 true;否则,返回 false。
class Solution { public boolean isAlienSorted(String[] words, String order) { for (int i = 0; i < words.length - 1; i++) { String o1 = words[i]; String o2 = words[i + 1]; int count = 0; for (int j = 0; j < o1.length() && j < o2.length(); j++) { char c1 = o1.charAt(j); char c2 = o2.charAt(j); if (c1 != c2) { if (order.indexOf(c1) >= order.indexOf(c2)) { return false; }else { break; } }else { count++; } } if (count == Math.min(o1.length(), o2.length())) { if (o1.length()>o2.length()){ return false; } } } return true; } }
444,重建序列
题目:给定一个长度为
n
的整数数组nums
,其中nums
是范围为[1,n]
的整数的排列。还提供了一个 2D 整数数组sequences
,其中sequences[i]
是nums
的子序列。
检查nums
是否是唯一的最短 超序列 。最短 超序列 是 长度最短 的序列,并且所有序列sequences[i]
都是它的子序列。对于给定的数组sequences
,可能存在多个有效的 超序列 。
- 例如,对于
sequences = [[1,2],[1,3]]
,有两个最短的 超序列 ,[1,2,3]
和[1,3,2]
。- 而对于
sequences = [[1,2],[1,3],[1,2,3]]
,唯一可能的最短 超序列 是[1,2,3]
。[1,2,3,4]
是可能的超序列,但不是最短的。如果
nums
是序列的唯一最短 超序列 ,则返回true
,否则返回false
。子序列 是一个可以通过从另一个序列中删除一些元素或不删除任何元素,而不改变其余元素的顺序的序列。class Solution { public boolean sequenceReconstruction(int[] nums, int[][] sequences) { int n = nums.length; Deque<Integer> deque = new ArrayDeque<>(); int[] v = new int[n + 1]; // 每个点的入度 List<Integer>[] g = new List[n + 1]; for (int i = 1; i <= n; ++i) g[i] = new ArrayList<>(); for (int[] s : sequences) { for (int i = 0; i < s.length - 1; ++i) { v[s[i + 1]]++; g[s[i]].add(s[i + 1]); } } int ptr = 0; for (int i = 1; i <= n; ++i) if (v[i] == 0) deque.offer(i); while (!deque.isEmpty()) { if (deque.size() > 1) return false; int cur = deque.poll(); if (nums[ptr++] != cur) return false; for (int nxt : g[cur]) { v[nxt]--; if (v[nxt] == 0) deque.offer(nxt); } } return ptr == n; } }
5,并查集
947,移除最多的同行或同列石头
题目:n
块石头放置在二维平面中的一些整数坐标点上。每个坐标点上最多只能有一块石头。如果一块石头的 同行或者同列 上有其他石头存在,那么就可以移除这块石头。给你一个长度为n
的数组stones
,其中stones[i] = [xi, yi]
表示第i
块石头的位置,返回 可以移除的石子 的最大数量。思路:如果一块石头的 同行或者同列 上有其他石头存在,那么就可以移除这块石头。可以发现:一定可以把一个连通图里的所有顶点根据这个规则删到只剩下一个顶点。最多可以移除的石头的个数 = 所有石头的个数 - 连通分量的个数。
import java.util.HashMap; import java.util.Map; public class Solution { public int removeStones(int[][] stones) { UnionFind unionFind = new UnionFind(); for (int[] stone : stones) { // 下面这三种写法任选其一 // unionFind.union(~stone[0], stone[1]); // unionFind.union(stone[0] - 10001, stone[1]); unionFind.union(stone[0] + 10001, stone[1]); } return stones.length - unionFind.getCount(); } private class UnionFind { private Map<Integer, Integer> parent; private int count; public UnionFind() { this.parent = new HashMap<>(); this.count = 0; } public int getCount() { return count; } public int find(int x) { if (!parent.containsKey(x)) { parent.put(x, x); // 并查集集中新加入一个结点,结点的父亲结点是它自己,所以连通分量的总数 +1 count++; } if (x != parent.get(x)) { parent.put(x, find(parent.get(x))); } return parent.get(x); } public void union(int x, int y) { int rootX = find(x); int rootY = find(y); if (rootX == rootY) { return; } parent.put(rootX, rootY); // 两个连通分量合并成为一个,连通分量的总数 -1 count--; } } }