一、深度优先搜索(DFS)
1、岛屿的最大面积
问题:
给你一个大小为 m x n 的二进制矩阵 grid 。
岛屿 是由一些相邻的 1 (代表土地) 构成的组合,这里的「相邻」要求两个 1 必须在 水平或者竖直的四个方向上 相邻。你可以假设 grid 的四个边缘都被 0(代表水)包围着。
岛屿的面积是岛上值为 1 的单元格的数目。
计算并返回 grid 中最大的岛屿面积。如果没有岛屿,则返回面积为 0 。
示例 1:
输入:grid = [[0,0,1,0,0,0,0,1,0,0,0,0,0],[0,0,0,0,0,0,0,1,1,1,0,0,0],[0,1,1,0,1,0,0,0,0,0,0,0,0],[0,1,0,0,1,1,0,0,1,0,1,0,0],[0,1,0,0,1,1,0,0,1,1,1,0,0],[0,0,0,0,0,0,0,0,0,0,1,0,0],[0,0,0,0,0,0,0,1,1,1,0,0,0],[0,0,0,0,0,0,0,1,1,0,0,0,0]]
输出:6
解释:答案不应该是 11 ,因为岛屿只能包含水平或垂直这四个方向上的 1 。
示例 2:
输入:grid = [[0,0,0,0,0,0,0,0]]
输出:0
1 package LeetCode.test5_sousuosuanfa; 2 3 public class ques_695_岛屿的最大面积 { 4 public static void main(String[] args) { 5 int[][] grid = {{0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0}, 6 {0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 0, 0, 0}, 7 {0, 1, 1, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0}, 8 {0, 1, 0, 0, 1, 1, 0, 0, 1, 0, 1, 0, 0}, 9 {0, 1, 0, 0, 1, 1, 0, 0, 1, 1, 1, 0, 0}, 10 {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0}, 11 {0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 0, 0, 0}, 12 {0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0}}; 13 System.out.println(maxAreaOfIsland(grid)); 14 } 15 16 public static int maxAreaOfIsland(int[][] grid) { 17 if (grid == null || grid.length == 0) { 18 return 0; 19 } 20 int[] X = {0, 0, 1, -1}; 21 int[] Y = {1, -1, 0, 0}; 22 int max_area = 0; 23 for (int i = 0; i < grid.length; i++) { 24 for (int j = 0; j < grid[0].length; j++) { 25 if (grid[i][j] == 1) { 26 System.out.println(DFS_maxAreaOfIsland(i, j, grid, X, Y)); 27 // max_area = Math.max(DFS_maxAreaOfIsland(i, j, grid, X, Y), max_area); 28 } 29 } 30 } 31 return max_area; 32 } 33 34 public static int DFS_maxAreaOfIsland(int x, int y, int[][] grid, int[] X, int[] Y) { 35 int area = 1; 36 grid[x][y] = 0; 37 for (int i = 0; i < 4; i++) { 38 int newX = x + X[i]; 39 int newY = y + Y[i]; 40 if (newX >= 0 && newY >= 0 && newX < grid.length && newY < grid[0].length && grid[newX][newY] == 1) { 41 area += DFS_maxAreaOfIsland(newX, newY, grid, X, Y); 42 } 43 } 44 return area; 45 } 46 }
2、省份数量
问题:
有 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 package LeetCode.test5_sousuosuanfa; 2 3 public class ques_547_省份数量 { 4 public static void main(String[] args) { 5 int[][] isConnected = {{1, 0, 0, 1}, 6 {0, 1, 1, 0}, 7 {0, 1, 1, 1}, 8 {1, 0, 1, 1}}; 9 System.out.println(findCircleNum(isConnected)); 10 } 11 12 public static int findCircleNum(int[][] isConnected) { 13 //新建一个visited数组表示当前城市是否被访问 14 boolean[] visited = new boolean[isConnected.length]; // 默认为false 15 int res = 0; //计数 16 for (int i = 0; i < isConnected.length; i++) { 17 if (!visited[i]) { 18 res++; //找到一个没有访问的城市时则视为找到一个省份,再递归将所有与其相连城市设置为访问状态 19 DFS_findCircleNum(isConnected, visited, i); 20 } 21 } 22 return res; 23 } 24 25 public static void DFS_findCircleNum(int[][] isConnected, boolean[] visited, int i) { 26 visited[i]=true; //将当前探索城市设置为已访问状态 27 for (int j = 0; j < isConnected.length; j++) { 28 if (isConnected[i][j]==1&& !visited[j]){ 29 //当当前城市A与其他城市B相连接且B没有被访问到时继续递归寻找城市 30 DFS_findCircleNum(isConnected,visited,j); 31 } 32 } 33 } 34 }
3、太平洋大西洋水流问题
问题:
给定一个 m x n 的非负整数矩阵来表示一片大陆上各个单元格的高度。“太平洋”处于大陆的左边界和上边界,而“大西洋”处于大陆的右边界和下边界。
规定水流只能按照上、下、左、右四个方向流动,且只能从高到低或者在同等高度上流动。
请找出那些水流既可以流动到“太平洋”,又能流动到“大西洋”的陆地单元的坐标。
示例:
给定下面的 5x5 矩阵:
heights = {{1, 2, 2, 3, 5},
{3, 2, 3, 4, 4},
{2, 4, 5, 3, 1},
{6, 7, 1, 4, 5},
{5, 1, 1, 2, 4}};
返回:
[[0, 4], [1, 3], [1, 4], [2, 2], [3, 0], [3, 1], [4, 0]]
1 package LeetCode.test5_sousuosuanfa; 2 3 import java.util.ArrayList; 4 import java.util.List; 5 6 public class ques_417_太平洋大西洋水流问题 { 7 public static void main(String[] args) { 8 int[][] heights = {{1, 2, 2, 3, 5}, 9 {3, 2, 3, 4, 4}, 10 {2, 4, 5, 3, 1}, 11 {6, 7, 1, 4, 5}, 12 {5, 1, 1, 2, 4}}; 13 System.out.println(pacificAtlantic(heights)); 14 } 15 16 public static List<List<Integer>> pacificAtlantic(int[][] heights) { 17 int[] X = {0, 0, 1, -1}; 18 int[] Y = {1, -1, 0, 0}; 19 int m = heights.length; 20 int n = heights[0].length; 21 List<List<Integer>> can_reach = new ArrayList<>(); 22 boolean[][] can_reach_p = new boolean[m][n]; 23 boolean[][] can_reach_a = new boolean[m][n]; 24 25 for (int i = 0; i < m; i++) { 26 DFS_pacificAtlantic(heights, can_reach_a, i, 0, X, Y); // 左边 27 DFS_pacificAtlantic(heights, can_reach_p, i, n - 1, X, Y); // 右边 28 } 29 for (int i = 0; i < n; i++) { 30 DFS_pacificAtlantic(heights, can_reach_a, 0, i, X, Y); // 上边 31 DFS_pacificAtlantic(heights, can_reach_p, m - 1, i, X, Y); // 下边 32 } 33 for (int i = 0; i < m; i++) { 34 for (int j = 0; j < n; j++) { 35 if (can_reach_a[i][j] && can_reach_p[i][j]) { 36 List<Integer> can = new ArrayList<>(); 37 can.add(i); 38 can.add(j); 39 can_reach.add(can); 40 } 41 } 42 } 43 return can_reach; 44 } 45 46 public static void DFS_pacificAtlantic(int[][] heights, boolean[][] can_reach, int r, int c, int[] X, int[] Y) { 47 if (can_reach[r][c]) { 48 return; 49 } 50 can_reach[r][c] = true; 51 for (int i = 0; i < 4; i++) { 52 int newX = r + X[i]; 53 int newY = c + Y[i]; 54 if (newX >= 0 && newY >= 0 && newX < heights.length && newY < heights[0].length && heights[r][c] <= heights[newX][newY]) { 55 DFS_pacificAtlantic(heights, can_reach, newX, newY, X, Y); 56 } 57 } 58 } 59 }
二、回溯法
4、全排列
问题:
给定一个不含重复数字的数组 nums ,返回其所有可能的全排列 。你可以按任意顺序返回答案。
示例 1:
输入:nums = [1,2,3]
输出:[[1,2,3],[1,3,2],[2,1,3],[2,3,1],[3,1,2],[3,2,1]]
示例 2:
输入:nums = [0,1]
输出:[[0,1],[1,0]]
示例 3:
输入:nums = [1]
输出:[[1]]
1 package LeetCode.test5_sousuosuanfa; 2 3 import java.util.ArrayList; 4 import java.util.Collections; 5 import java.util.List; 6 7 /** 8 思路:怎样输出所有的排列方式呢?对于每一个当前位置 i,可以将其于之后的任意位置交换, 9 然后继续处理位置 i+1,直到处理到最后一位。为了防止每此遍历时都要新建一个子数组储 10 存位置 i 之前已经交换好的数字,可以利用回溯法,只对原数组进行修改,在递归完成后再 11 修改回来。 12 */ 13 public class ques_46_全排列 { 14 public static void main(String[] args) { 15 int[] nums = {1, 2, 3}; 16 System.out.println(permute(nums)); 17 } 18 19 public static List<List<Integer>> permute(int[] nums) { 20 List<Integer> perm = new ArrayList<>(); 21 for (Integer num: nums) { 22 perm.add(num); 23 } 24 List<List<Integer>> res = new ArrayList<>(); 25 backtracking(perm, 0, res); 26 return res; 27 } 28 /* 29 为什么在往结果集中添加排列结果的时候,需要进行new ArrayList添加?????? 30 假设这段代码,「正确」res.add(new ArrayList<>(output)); 变为「错误」res.add(output);关键在于res存放的是list引用。 31 那么回溯过程中,将数字使用状态重置撤销的时候,会将list的元素移除掉,也会影响到res里面的list情况。 32 因为它们是同一个引用。全部为空,是因为回溯结束的同时,会将全部数字重置撤销,这样list里面的元素就会为空了,同样的,也会影响到res的存放情况。 33 */ 34 public static void backtracking(List<Integer> perm, int first, List<List<Integer>> res) { 35 if (first == perm.size() - 1) { 36 res.add(new ArrayList<>(perm)); 37 return; 38 } 39 for (int i = first; i < perm.size(); i++) { 40 Collections.swap(perm,i,first); //修改当前节点状态 41 backtracking(perm,first+1,res); //递归子节点 42 Collections.swap(perm,i,first); //回改当前节点状态 43 } 44 } 45 }
1 package LeetCode.test5_sousuosuanfa; 2 3 import java.util.ArrayList; 4 import java.util.List; 5 6 public class ques_46_全排列 { 7 public static void main(String[] args) { 8 int[] nums = {1, 2, 3}; 9 // int[] nums = {1, 1, 2}; 10 System.out.println(permuteUnique(nums)); 11 } 12 13 public static List<List<Integer>> permuteUnique(int[] nums) { 14 List<Integer> perm = new ArrayList<>(); // 每种排列的容器 15 List<List<Integer>> res = new ArrayList<>(); // 所有排列的容器 16 boolean[] visited = new boolean[nums.length]; 17 backtracking(visited, 0, nums, perm, res); 18 return res; 19 } 20 21 public static void backtracking(boolean[] visited, int index, int[] nums, List<Integer> perm, List<List<Integer>> res) { 22 if (index == nums.length) { 23 res.add(new ArrayList<>(perm)); 24 return; 25 } 26 for (int j = 0; j < nums.length; j++) { 27 if (visited[j]) { 28 continue; 29 } 30 perm.add(nums[j]); 31 visited[j] = true; 32 backtracking(visited, index + 1, nums, perm, res); 33 visited[j] = false; 34 perm.remove(index); 35 } 36 } 37 }
5、组合
问题:
给定两个整数 n 和 k,返回范围 [1, n] 中所有可能的 k 个数的组合。
你可以按 任何顺序 返回答案。
示例 1:
输入:n = 4, k = 2
输出:
[
[2,4],
[3,4],
[2,3],
[1,2],
[1,3],
[1,4],
]
示例 2:
输入:n = 1, k = 1
输出:[[1]]
1 package LeetCode.test5_sousuosuanfa; 2 3 import java.util.ArrayList; 4 import java.util.Arrays; 5 import java.util.List; 6 7 /** 8 思路:类似于排列问题,也可以进行回溯。排列回溯的是交换的位置,而组合回溯的是否把当前的数字加入结果中。 9 */ 10 public class ques_77_组合 { 11 public static void main(String[] args) { 12 int n = 4; 13 int k = 2; 14 System.out.println(combine(n, k)); 15 } 16 17 public static List<List<Integer>> combine(int n, int k) { 18 List<List<Integer>> res = new ArrayList<>(); 19 Integer[] perm = new Integer[k]; 20 int count = 0; 21 backtracking(res, n, k, count, 1, perm); 22 return res; 23 } 24 25 public static void backtracking(List<List<Integer>> res, int n, int k, int count, int pos, Integer[] perm) { 26 if (count == k) { 27 res.add(new ArrayList<>(Arrays.asList(perm))); 28 return; 29 } 30 for (int i = pos; i <= n; i++) { 31 perm[count++] = i; 32 backtracking(res, n, k, count, i + 1, perm); 33 count--; 34 } 35 } 36 }
6、单词搜索
问题:
给定一个 m x n 二维字符网格 board 和一个字符串单词 word 。如果 word 存在于网格中,返回 true ;否则,返回 false 。
单词必须按照字母顺序,通过相邻的单元格内的字母构成,其中“相邻”单元格是那些水平相邻或垂直相邻的单元格。同一个单元格内的字母不允许被重复使用。
示例 1:
输入:board = [["A","B","C","E"],["S","F","C","S"],["A","D","E","E"]], word = "ABCCED"
输出:true
示例 2:
输入:board = [["A","B","C","E"],["S","F","C","S"],["A","D","E","E"]], word = "SEE"
输出:true
示例 3:
输入:board = [["A","B","C","E"],["S","F","C","S"],["A","D","E","E"]], word = "ABCB"
输出:false
1 package LeetCode.test5_sousuosuanfa; 2 3 /** 4 * 思路:不同于排列组合问题,本题采用的并不是修改输出方式,而是修改访问标记。在对任意 5 * 位置进行深度优先搜索时,先标记当前位置为已访问,以避免重复遍历(如防止向右搜索后 6 * 又向左返回);在所有的可能都搜索完成后,再回改当前位置为未访问,防止干扰其它位置搜索 7 * 到当前位置。使用回溯法,可以只对一个二维的访问矩阵进行修改,而不用把每次的搜索状 8 * 态作为一个新对象传入递归函数中。 9 */ 10 public class ques_79_单词搜索 { 11 public static void main(String[] args) { 12 char[][] board = {{'A', 'B', 'C', 'E'}, 13 {'S', 'F', 'C', 'S'}, 14 {'A', 'D', 'E', 'E'}}; 15 String word = "ABCCED"; 16 System.out.println(exist(board, word)); 17 } 18 19 public static boolean exist(char[][] board, String word) { 20 int m = board.length; 21 int n = board[0].length; 22 boolean[][] visited = new boolean[m][n]; 23 for (int i = 0; i < m; i++) { 24 for (int j = 0; j < n; j++) { 25 if (backtracking(i, j, board, word, visited, 0)){ 26 return true; 27 } 28 } 29 } 30 return false; 31 } 32 33 public static boolean backtracking(int i, int j, char[][] board, String word, boolean[][] visited, int pos) { 34 if (i < 0 || j < 0 || i >= board.length || j >= board[0].length) { 35 return false; 36 } 37 if (visited[i][j] || board[i][j] != word.charAt(pos)) { 38 return false; 39 } 40 if (pos == word.length() - 1) { 41 return true; 42 } 43 visited[i][j] = true; 44 boolean res = (backtracking(i + 1, j, board, word, visited, pos + 1)|| 45 backtracking(i - 1, j, board, word, visited, pos + 1)|| 46 backtracking(i, j + 1, board, word, visited, pos + 1)|| 47 backtracking(i, j - 1, board, word, visited, pos + 1)); 48 visited[i][j] = false; 49 return res; 50 } 51 }
7、N皇后
问题:
n 皇后问题 研究的是如何将 n 个皇后放置在 n×n 的棋盘上,并且使皇后彼此之间不能相互攻击。
给你一个整数 n ,返回所有不同的 n 皇后问题 的解决方案。
每一种解法包含一个不同的 n 皇后问题 的棋子放置方案,该方案中 'Q' 和 '.' 分别代表了皇后和空位。
示例 1:
输入:n = 4
输出:[[".Q..","...Q","Q...","..Q."],["..Q.","Q...","...Q",".Q.."]]
解释:如上图所示,4 皇后问题存在两个不同的解法。
示例 2:
输入:n = 1
输出:[["Q"]]
1 package LeetCode.test5_sousuosuanfa; 2 3 import java.util.ArrayList; 4 import java.util.Arrays; 5 import java.util.List; 6 7 public class ques_51_N皇后 { 8 public static void main(String[] args) { 9 int n = 4; 10 System.out.println(solveNQueens(n)); 11 } 12 13 public static List<List<String>> solveNQueens(int n) { 14 List<List<String>> ans = new ArrayList<>(); 15 char[][] board = new char[n][n]; 16 for (int i = 0; i < n; i++) { 17 Arrays.fill(board[i], '.'); 18 } 19 //列数组长度为n 20 boolean[] column = new boolean[n]; 21 //假设n=4:[0,1,2,3,4,5,6] 在同一45度线上的元素,row+col刚好都相等,数组长度为2*n-1 数组下标:row+col 22 boolean[] diag_45 = new boolean[2 * n - 1]; 23 //假设n=4:[-3,-2,-1,0,1,2,3] 在同一45度线上的元素,row-col刚好都相等,数组长度为2*n-1 数组下标:n-1-(row-col) 24 boolean[] diag_135 = new boolean[2 * n - 1]; 25 backtracking(ans, board, column, diag_45, diag_135, 0, n); 26 return ans; 27 } 28 29 public static void backtracking(List<List<String>> ans, char[][] board, boolean[] column, 30 boolean[] diag_45, boolean[] diag_135, int row, int n) { 31 if (row == n) { 32 List<String> list = new ArrayList<>(); 33 for (int i = 0; i < n; i++) { 34 list.add(String.valueOf(board[i])); 35 } 36 ans.add(list); 37 } 38 for (int col = 0; col < n; col++) { 39 if (column[col] || diag_45[row + col] || diag_135[n - 1 - (row - col)]) { 40 continue; 41 } 42 board[row][col] = 'Q'; // 修改当前节点状态 43 column[col] = diag_45[row + col] = diag_135[n - 1 - (row - col)] = true; 44 backtracking(ans, board, column, diag_45, diag_135, row + 1, n); // 递归子节点 45 board[row][col] = '.'; // 回改当前节点状态 46 column[col] = diag_45[row + col] = diag_135[n - 1 - (row - col)] = false; 47 } 48 } 49 }
三、广度优先搜索(BFS)
8、最短的桥
问题:
在给定的二维二进制数组 A 中,存在两座岛。(岛是由四面相连的 1 形成的一个最大组。)
现在,我们可以将 0 变为 1,以使两座岛连接起来,变成一座岛。
返回必须翻转的 0 的最小数目。(可以保证答案至少是 1 。)
示例 1:
输入:A = [[0,1],[1,0]]
输出:1
示例 2:
输入:A = [[0,1,0],[0,0,0],[0,0,1]]
输出:2
示例 3:
输入:A = [[1,1,1,1,1],[1,0,0,0,1],[1,0,1,0,1],[1,0,0,0,1],[1,1,1,1,1]]
输出:1
1 package LeetCode.test5_sousuosuanfa; 2 3 import java.util.*; 4 5 /** 6 * 最短!!! 7 */ 8 public class ques_934_最短的桥 { 9 public static void main(String[] args) { 10 int[][] grid = {{0, 1, 0}, 11 {0, 0, 0}, 12 {0, 0, 1}}; 13 // int[][] grid = {{1, 1, 1, 1, 1}, 14 // {1, 0, 0, 0, 1}, 15 // {1, 0, 1, 0, 1}, 16 // {1, 0, 0, 0, 1}, 17 // {1, 1, 1, 1, 1}}; 18 System.out.println(shortestBridge(grid)); 19 } 20 21 public static int shortestBridge(int[][] grid) { 22 int[] X = {0, 0, 1, -1}; 23 int[] Y = {1, -1, 0, 0}; 24 25 // (1) dfs搜索第一个岛屿,并把1全部赋值为2 26 boolean flag = false; //保证只搜索第一个岛屿 27 for (int i = 0; i < grid.length; i++) { 28 if (flag) break; //内层循环结束,外层循环也必须结束 29 for (int j = 0; j < grid[0].length; j++) { 30 if (grid[i][j] == 1) { 31 dfs(grid, i, j, X, Y); 32 flag = true; 33 break; 34 } 35 } 36 } 37 // (2) bfs:首先将所有的2的节点放入队列,搜索第二个岛屿,并把过程中经过的0赋值为2 38 Queue<int[]> points = new LinkedList<>(); 39 for (int i = 0; i < grid.length; i++) { 40 for (int j = 0; j < grid[0].length; j++) { 41 if (grid[i][j] == 2) { 42 points.offer(new int[]{i, j}); 43 } 44 } 45 } 46 int count = 0; 47 while (!points.isEmpty()) { 48 int size = points.size(); 49 for (int j = 0; j < size; j++) { 50 int[] top = points.poll(); 51 for (int i = 0; i < 4; i++) { 52 assert top != null; 53 int newX = top[0] + X[i]; 54 int newY = top[1] + Y[i]; 55 if (newX < 0 || newX >= grid.length || newY < 0 || newY >= grid[0].length) { 56 continue; 57 } 58 if (grid[newX][newY] == 2) { 59 continue; 60 } 61 if (grid[newX][newY] == 1) { 62 return count; 63 } 64 grid[newX][newY] = 2; 65 points.offer(new int[]{newX, newY}); 66 } 67 } 68 count++; 69 } 70 return count; 71 } 72 73 public static void dfs(int[][] grid, int i, int j, int[] X, int[] Y) { 74 if (grid[i][j] == 1) { 75 grid[i][j] = 2; 76 } 77 for (int k = 0; k < 4; k++) { 78 int newX = i + X[k]; 79 int newY = j + Y[k]; 80 if (newX < 0 || newX >= grid.length || newY < 0 || newY >= grid[0].length) { 81 continue; 82 } 83 if (grid[newX][newY] == 0 || grid[newX][newY] == 2) { 84 continue; 85 } 86 dfs(grid, newX, newY, X, Y); 87 } 88 } 89 }
9、单词接龙II
问题:(https://blog.csdn.net/zzzzz_zzz___rss/article/details/121286731)
按字典 wordList 完成从单词 beginWord 到单词 endWord 转化,一个表示此过程的 转换序列 是形式上像 beginWord -> s1 -> s2 -> ... -> sk 这样的单词序列,并满足:
每对相邻的单词之间仅有单个字母不同。
转换过程中的每个单词 si(1 <= i <= k)必须是字典 wordList 中的单词。注意,beginWord 不必是字典 wordList 中的单词。
sk == endWord
给你两个单词 beginWord 和 endWord ,以及一个字典 wordList 。请你找出并返回所有从 beginWord 到 endWord 的 最短转换序列 ,如果不存在这样的转换序列,返回一个空列表。
每个序列都应该以单词列表 [beginWord, s1, s2, ..., sk] 的形式返回。
示例 1:
输入:beginWord = "hit", endWord = "cog", wordList = ["hot","dot","dog","lot","log","cog"]
输出:[["hit","hot","dot","dog","cog"],["hit","hot","lot","log","cog"]]
解释:存在 2 种最短的转换序列:
"hit" -> "hot" -> "dot" -> "dog" -> "cog"
"hit" -> "hot" -> "lot" -> "log" -> "cog"
示例 2:
输入:beginWord = "hit", endWord = "cog", wordList = ["hot","dot","dog","lot","log"]
输出:[]
解释:endWord "cog" 不在字典 wordList 中,所以不存在符合要求的转换序列。
思路:
(1)使用双向BFS的方法,每次从队列长度小的一端开始搜索。
(2)在对某一层BFS开始之前,先将队列中的全部元素进行标记。
(3)然后开始BFS,如果遍历到的节点未被标记,那么将该节点放入队列中,如果该节点存在于另一个队列中,说明已经找到最短的路径了。
(4)此时将该层节点遍历结束后,方可进行回溯记录路径。
(5)此外,需要在遍历节点的时候,通过判断这次遍历是从后向前还是从前向后,以确定构建的图的起始节点和结束节点。
1 package LeetCode.test5_sousuosuanfa; 2 3 import java.util.*; 4 5 /** 6 * 最短!!! 7 */ 8 public class ques_126_单词接龙II { 9 public static void main(String[] args) { 10 String beginWord = "hit"; 11 String endWord = "cog"; 12 String[] word = {"hot", "dot", "dog", "lot", "log", "cog"}; 13 List<String> wordList = new ArrayList<>(Arrays.asList(word)); 14 System.out.println((findLadders(beginWord, endWord, wordList))); 15 } 16 17 public static List<List<String>> findLadders(String beginWord, String endWord, List<String> wordList) { 18 wordList.add(beginWord); 19 // 构建一个标记集合,开始每个word都在集合中。后边每次将元素放到队列中,便将该元素从集合中删除. 20 Set<String> wordListSet = new HashSet<>(wordList); 21 if (!wordList.contains(endWord)) { 22 return new ArrayList<>(); 23 } 24 // 标记是不是已经找到了最短的路径,并用res来记录这些路径 25 boolean found = false; 26 List<List<String>> res = new ArrayList<>(); 27 // 构建map, 用来记录单词之间的关系 28 Map<String, Set<String>> map = new HashMap<>(); 29 // 使用双向的BFS来检索最短的变换路径 30 Queue<String> queueBegin = new LinkedList<>(); 31 Queue<String> queueEnd = new LinkedList<>(); 32 queueBegin.add(beginWord); 33 queueEnd.add(endWord); 34 // 每次只对比较短的BFS进行搜索,使用reverse标记两个方向的队列是不是交换过。 35 boolean reverse = false; 36 while (!queueBegin.isEmpty() && !queueEnd.isEmpty()) { 37 if (queueBegin.size() > queueEnd.size()) { 38 Queue<String> queue = queueBegin; 39 queueBegin = queueEnd; 40 queueEnd = queue; 41 // 标记是不是转换过,如果转换过的话,需要在构建图的时候将两个节点逆转 42 reverse = !reverse; 43 } 44 // 将上一层访问过的节点从集合中删除 45 wordListSet.removeAll(queueBegin); 46 // 根据queueEnd创建一个集合,用来判断遍历到的单词是不是出现在另一端遍历的队列中 47 Set<String> queueEndToSet = new HashSet<>(queueEnd); 48 int count = queueBegin.size(); 49 while (count-- > 0) { 50 String top = queueBegin.poll(); 51 assert top != null; 52 char[] tops = top.toCharArray(); 53 for (int i = 0; i < tops.length; i++) { 54 char c = tops[i]; 55 for (char j = 'a'; j <= 'z'; j++) { 56 tops[i] = j; 57 String newStr = new String(tops); 58 // 如果当前元素存在于另一个队列中,说明找到了最短的路径 59 // 只需要将当前的两个队列遍历一遍,找到全部的结果集,而不需要再次向下遍历。 60 if (queueEndToSet.contains(newStr)) { 61 found = true; 62 } 63 // 如果未被访问过,那么将该元素放到queueBegin中。 64 if (wordListSet.contains(newStr)) { 65 queueBegin.add(newStr); 66 } 67 // 通过判断是不是reverse过,来构建节点关系图(保证其正向) 68 if (wordListSet.contains(newStr) || queueEndToSet.contains(newStr)) { 69 Set<String> subList; 70 if (reverse) { 71 subList = map.get(newStr); 72 if (subList == null) { 73 subList = new HashSet<>(); 74 } 75 subList.add(top); 76 // {dot=[dog], lot=[log], hit=[hot], hot=[lot, dot], dog=[cog], log=[cog]} 77 map.put(newStr, subList); 78 } else { 79 subList = map.get(top); 80 if (subList == null) { 81 subList = new HashSet<>(); 82 } 83 subList.add(newStr); 84 // {hit=[hot], hot=[lot, dot]} 85 map.put(top, subList); 86 } 87 } 88 } 89 tops[i] = c; 90 } 91 } 92 // 将其放到最后进行遍历,原因在于: 93 // 在构建图的时候,需要考虑到,此时两个遍历方向的队列已经到了中间层。如果得到结果就直接返回的话,肯定会导致缺少路径。 94 // 因此在while(count--)之后再回溯记录路径,便会找到全部的路径 95 if (found){ 96 List<String> path = new ArrayList<>(); 97 path.add(beginWord); 98 dfs(map, path, res, beginWord, endWord); 99 return res; 100 } 101 } 102 return res; 103 } 104 105 // 回溯法记录路径 106 public static void dfs(Map<String, Set<String>> map, List<String> path, List<List<String>> res, String beginWord, String endWord) { 107 if (beginWord.equals(endWord)){ 108 res.add(new ArrayList<>(path)); 109 return; 110 } 111 if (map.get(beginWord)!=null){ 112 for (String str:map.get(beginWord)) { 113 path.add(str); 114 dfs(map, path, res, str, endWord); 115 path.remove(str); 116 } 117 } 118 } 119 }
10、单词接龙
问题:(https://blog.csdn.net/zzzzz_zzz___rss/article/details/121256294)
字典 wordList 中从单词 beginWord 和 endWord 的 转换序列 是一个按下述规格形成的序列:
序列中第一个单词是 beginWord 。
序列中最后一个单词是 endWord 。
每次转换只能改变一个字母。
转换过程中的中间单词必须是字典 wordList 中的单词。
给你两个单词 beginWord 和 endWord 和一个字典 wordList ,找到从 beginWord 到 endWord 的 最短转换序列 中的 单词数目 。如果不存在这样的转换序列,返回 0。
示例 1:
输入:beginWord = "hit", endWord = "cog", wordList = ["hot","dot","dog","lot","log","cog"]
输出:5
解释:一个最短转换序列是 "hit" -> "hot" -> "dot" -> "dog" -> "cog", 返回它的长度 5。
示例 2:
输入:beginWord = "hit", endWord = "cog", wordList = ["hot","dot","dog","lot","log"]
输出:0
解释:endWord "cog" 不在字典中,所以无法进行转换。
方法1:单向BFS
1 package LeetCode.test5_sousuosuanfa; 2 3 import java.util.*; 4 5 /** 6 * 最短!!! 7 * 方法1:单向BFS 8 */ 9 public class ques_127_单词接龙 { 10 public static void main(String[] args) { 11 String beginWord = "hit"; 12 String endWord = "cog"; 13 String[] word = {"hot","dot","dog","lot","log","cog"}; 14 List<String> wordList = new ArrayList<>(Arrays.asList(word)); 15 System.out.println((findLadders(beginWord, endWord, wordList))); 16 } 17 18 public static int findLadders(String beginWord, String endWord, List<String> wordList) { 19 // bfs的深度 20 int level = 0; 21 wordList.add(beginWord); 22 if (!wordList.contains(endWord)) { 23 return 0; 24 } 25 // 构建map, 用来记录单词之间的关系 26 Map<String, Set<String>> wordMap = new HashMap<>(); 27 for (String s: wordList) { 28 Set<String> set = new HashSet<>(); 29 for (String ss : wordList) { 30 if (checkDiff(ss, s)) { //检查ss和s是不是只有一个字符不相同 31 set.add(ss); 32 } 33 } 34 wordMap.put(s, set); 35 } 36 // 标记单词是不是已经使用过 37 Set<String> map = new HashSet<>(); 38 Set<String> tmpMap = new HashSet<>(); 39 40 // 定义一个Queue来实现bfs 41 Queue<String> queue = new ArrayDeque<>(); 42 queue.add(beginWord); 43 while (!queue.isEmpty()) { 44 int count = queue.size(); 45 level++; 46 // 将上一层bfs使用过的单词,在该层统一进行标记 47 map.addAll(tmpMap); 48 tmpMap.clear(); 49 while (count-- > 0) { 50 // 拿出queue中的元素,并在list中查找与其仅有一个字符不同的单词 51 String key = queue.remove(); 52 // 将key临时记录,并在当前循环结束之后放入map中记录,之后不再访问之 53 tmpMap.add(key); 54 // 在Map中获取与当前单词key直接相连的单词 55 Set<String> list = wordMap.get(key); 56 for (String s : list) { 57 // 找到list中未曾访问过的某个元素 58 if (!map.contains(s)) { 59 queue.add(s); 60 if (s.equals(endWord)) { 61 return level + 1; 62 } 63 } 64 } 65 } 66 } 67 return 0; 68 } 69 70 public static boolean checkDiff(String key, String str) { 71 if (key == null || str == null) { 72 return false; 73 } 74 int keyLen = key.length(); 75 int strLen = str.length(); 76 if (keyLen != strLen) { 77 return false; 78 } 79 int count = 0; 80 for (int i = 0; i < keyLen; i++) { 81 if (key.charAt(i) != str.charAt(i)) { 82 count++; 83 if (count > 1) { 84 return false; 85 } 86 } 87 } 88 return count == 1; 89 } 90 }
方法2:双向BFS
1 package LeetCode.test5_sousuosuanfa; 2 3 import java.util.*; 4 5 /** 6 * 最短!!! 7 * 方法2:双向BFS 8 */ 9 public class ques_127_单词接龙_2 { 10 public static void main(String[] args) { 11 String beginWord = "hit"; 12 String endWord = "cog"; 13 String[] word = {"hot", "dot", "dog", "lot", "log", "cog"}; 14 List<String> wordList = new ArrayList<>(Arrays.asList(word)); 15 System.out.println((findLadders(beginWord, endWord, wordList))); 16 } 17 18 public static int findLadders(String beginWord, String endWord, List<String> wordList) { 19 // 两个bfs的深度 20 int level = 0; 21 wordList.add(beginWord); 22 // 构建一个标记集合,开始每个word都在集合中。后边每次将元素放到队列中,便将该元素从集合中删除. 23 Set<String> wordListSet = new HashSet<>(wordList); 24 if (!wordList.contains(endWord)) { 25 return 0; 26 } 27 28 // 标记单词是不是已经使用过 - 从begin处向后 29 Set<String> usedBegin = new HashSet<>(); 30 // 标记单词是不是已经使用过 - 从end处向前 31 Set<String> usedEnd = new HashSet<>(); 32 33 // 定义一个Queue来实现bfs 34 Queue<String> queueBegin = new ArrayDeque<>(); 35 Queue<String> queueEnd = new ArrayDeque<>(); 36 queueEnd.add(endWord); 37 queueBegin.add(beginWord); 38 39 usedBegin.add(beginWord); 40 usedEnd.add(endWord); 41 42 while (!queueBegin.isEmpty() && !queueEnd.isEmpty()) { 43 level++; 44 if (queueBegin.size() > queueEnd.size()) { 45 // 交换 46 Queue<String> queue = queueBegin; 47 queueBegin = queueEnd; 48 queueEnd = queue; 49 // 交换 50 Set<String> set = usedBegin; 51 usedBegin = usedEnd; 52 usedEnd = set; 53 } 54 int count = queueBegin.size(); 55 while (count-- > 0) { 56 String top = queueBegin.poll(); 57 assert top != null; 58 char[] tops = top.toCharArray(); 59 for (int i = 0; i < tops.length; i++) { 60 char c = tops[i]; 61 for (char j = 'a'; j <= 'z'; j++) { 62 tops[i] = j; 63 String newStr = new String(tops); 64 if (usedBegin.contains(newStr)) { 65 continue; 66 } 67 if (usedEnd.contains(newStr)) { 68 return level + 1; 69 } 70 if (wordListSet.contains(newStr)) { 71 queueBegin.add(newStr); 72 usedBegin.add(newStr); 73 } 74 } 75 tops[i] = c; 76 } 77 } 78 } 79 return 0; 80 } 81 }
1 package LeetCode.test5_sousuosuanfa; 2 3 import java.util.*; 4 5 /** 6 * 最短!!! 7 * 方法2:双向BFS 8 */ 9 public class ques_127_单词接龙_3 { 10 public static void main(String[] args) { 11 String beginWord = "hit"; 12 String endWord = "cog"; 13 String[] word = {"hot", "dot", "dog", "lot", "log", "cog"}; 14 List<String> wordList = new ArrayList<>(Arrays.asList(word)); 15 System.out.println((findLadders(beginWord, endWord, wordList))); 16 } 17 18 public static int findLadders(String beginWord, String endWord, List<String> wordList) { 19 wordList.add(beginWord); 20 // 构建一个标记集合,开始每个word都在集合中。后边每次将元素放到队列中,便将该元素从集合中删除. 21 Set<String> wordListSet = new HashSet<>(wordList); 22 if (!wordList.contains(endWord)) { 23 return 0; 24 } 25 // 两个bfs的深度 26 int level = 0; 27 // 构建map, 用来记录单词之间的关系 28 Map<String, Set<String>> map = new HashMap<>(); 29 // 使用双向的BFS来检索最短的变换路径 30 Queue<String> queueBegin = new LinkedList<>(); 31 Queue<String> queueEnd = new LinkedList<>(); 32 queueBegin.add(beginWord); 33 queueEnd.add(endWord); 34 // 每次只对比较短的BFS进行搜索,使用reverse标记两个方向的队列是不是交换过。 35 boolean reverse = false; 36 while (!queueBegin.isEmpty() && !queueEnd.isEmpty()) { 37 level++; 38 if (queueBegin.size() > queueEnd.size()) { 39 Queue<String> queue = queueBegin; 40 queueBegin = queueEnd; 41 queueEnd = queue; 42 // 标记是不是转换过,如果转换过的话,需要在构建图的时候将两个节点逆转 43 reverse = !reverse; 44 } 45 // 将上一层访问过的节点从集合中删除 46 wordListSet.removeAll(queueBegin); 47 // 根据queueEnd创建一个集合,用来判断遍历到的单词是不是出现在另一端遍历的队列中 48 Set<String> queueEndToSet = new HashSet<>(queueEnd); 49 int count = queueBegin.size(); 50 while (count-- > 0) { 51 String top = queueBegin.poll(); 52 assert top != null; 53 char[] tops = top.toCharArray(); 54 for (int i = 0; i < tops.length; i++) { 55 char c = tops[i]; 56 for (char j = 'a'; j <= 'z'; j++) { 57 tops[i] = j; 58 String newStr = new String(tops); 59 // 如果当前元素存在于另一个队列中,说明找到了最短的路径 60 if (queueEndToSet.contains(newStr)) { 61 return level + 1; 62 } 63 // 如果未被访问过,那么将该元素放到queueBegin中。 64 if (wordListSet.contains(newStr)) { 65 queueBegin.add(newStr); 66 } 67 // 通过判断是不是reverse过,来构建节点关系图(保证其正向) 68 if (wordListSet.contains(newStr) || queueEndToSet.contains(newStr)) { 69 Set<String> subList; 70 if (reverse) { 71 subList = map.get(newStr); 72 if (subList == null) { 73 subList = new HashSet<>(); 74 } 75 subList.add(top); 76 // {dot=[dog], lot=[log], hit=[hot], hot=[lot, dot], dog=[cog], log=[cog]} 77 map.put(newStr, subList); 78 } else { 79 subList = map.get(top); 80 if (subList == null) { 81 subList = new HashSet<>(); 82 } 83 subList.add(newStr); 84 // {hit=[hot], hot=[lot, dot]} 85 map.put(top, subList); 86 } 87 } 88 } 89 tops[i] = c; 90 } 91 } 92 } 93 return 0; 94 } 95 }
练习(6道)
1、被围绕的区域
问题:
给你一个 m x n 的矩阵 board ,由若干字符 'X' 和 'O' ,找到所有被 'X' 围绕的区域,并将这些区域里所有的 'O' 用 'X' 填充。
示例 1:
输入:board = [["X","X","X","X"],["X","O","O","X"],["X","X","O","X"],["X","O","X","X"]]
输出:[["X","X","X","X"],["X","X","X","X"],["X","X","X","X"],["X","O","X","X"]]
解释:被围绕的区间不会存在于边界上,换句话说,任何边界上的 'O' 都不会被填充为 'X'。 任何不在边界上,或不与边界上的 'O' 相连的 'O' 最终都会被填充为 'X'。如果两个元素在水平或垂直方向相邻,则称它们是“相连”的。
示例 2:
输入:board = [["X"]]
输出:[["X"]]
1 package LeetCode.test5_sousuosuanfa; 2 3 import java.util.Arrays; 4 5 public class ques_130_被围绕的区域 { 6 public static void main(String[] args) { 7 char[][] board = {{'X', 'X', 'X', 'X'}, 8 {'X', 'O', 'O', 'X'}, 9 {'X', 'X', 'O', 'X'}, 10 {'X', 'O', 'X', 'X'}}; 11 // char[][] board = {{'O', 'O', 'O'}, 12 // {'O', 'O', 'O'}, 13 // {'O', 'O', 'O'}}; 14 solve(board); 15 System.out.println(Arrays.deepToString(board)); 16 } 17 18 public static void solve(char[][] board) { 19 int m = board.length; 20 int n = board[0].length; 21 for (int i = 0; i < m; i++) { 22 dfs(board, i, 0); 23 dfs(board, i, n - 1); 24 } 25 for (int i = 0; i < n; i++) { 26 dfs(board, 0, i); 27 dfs(board, m - 1, i); 28 } 29 for (int i = 0; i < m; i++) { 30 for (int j = 0; j < n; j++) { 31 if (board[i][j] == 'C') { 32 board[i][j] = 'O'; 33 } else if (board[i][j] == 'O') { 34 board[i][j] = 'X'; 35 } 36 } 37 } 38 } 39 40 public static void dfs(char[][] board, int i, int j) { 41 if (i < 0 || i >= board.length || j < 0 || j >= board[0].length || board[i][j] != 'O') { 42 return; 43 } 44 board[i][j] = 'C'; 45 dfs(board, i, j + 1); 46 dfs(board, i, j - 1); 47 dfs(board, i + 1, j); 48 dfs(board, i - 1, j); 49 } 50 }
2、二叉树的所有路径
问题:
给你一个二叉树的根节点 root ,按 任意顺序 ,返回所有从根节点到叶子节点的路径。叶子节点是指没有子节点的节点。
示例 1:
输入:root = [1,2,3,null,5]
输出:["1->2->5","1->3"]
示例 2:
输入:root = [1]
输出:["1"]
1 package LeetCode.test5_sousuosuanfa; 2 3 import java.util.ArrayList; 4 import java.util.List; 5 6 public class ques_257_二叉树的所有路径 { 7 public static void main(String[] args) { 8 // String[] root = {"1", "2", "3", null, "5"}; 9 String[] root = {"1"}; 10 int index = 0; 11 TreeNode bt = createBTree(root, index); 12 System.out.println(binaryTreePaths(bt)); 13 } 14 15 public static class TreeNode { 16 int val; 17 TreeNode left; 18 TreeNode right; 19 20 TreeNode() { 21 } 22 23 TreeNode(int val) { 24 this.val = val; 25 } 26 27 TreeNode(int val, TreeNode left, TreeNode right) { 28 this.val = val; 29 this.left = left; 30 this.right = right; 31 } 32 } 33 34 35 public static List<String> binaryTreePaths(TreeNode root) { 36 List<String> paths = new ArrayList<>(); 37 String s = ""; 38 getPath(root, s, paths); 39 return paths; 40 } 41 42 public static void getPath(TreeNode root, String s, List<String> paths) { 43 if (root != null) { 44 StringBuilder sb = new StringBuilder(s); 45 sb.append(root.val); 46 if (root.left == null && root.right == null) { 47 paths.add(sb.toString()); 48 } else { 49 sb.append("->"); 50 getPath(root.left, sb.toString(), paths); 51 getPath(root.right, sb.toString(), paths); 52 } 53 } 54 } 55 56 public static TreeNode createBTree(String[] root, int index) { 57 TreeNode rootNode; 58 if (index >= root.length || root[index] == null) { 59 return null; 60 } 61 rootNode = new TreeNode(Integer.parseInt(root[index])); 62 rootNode.left = createBTree(root, 2 * index + 1); 63 rootNode.right = createBTree(root, 2 * index + 2); 64 return rootNode; 65 } 66 }
3、全排列II
问题:
给定一个可包含重复数字的序列 nums ,按任意顺序 返回所有不重复的全排列。
示例 1:
输入:nums = [1,1,2]
输出:
[[1,1,2],
[1,2,1],
[2,1,1]]
示例 2:
输入:nums = [1,2,3]
输出:[[1,2,3],[1,3,2],[2,1,3],[2,3,1],[3,1,2],[3,2,1]]
1 package LeetCode.test5_sousuosuanfa; 2 3 import java.util.ArrayList; 4 import java.util.Arrays; 5 import java.util.List; 6 7 public class ques_47_全排列II { 8 public static void main(String[] args) { 9 int[] nums = {1, 1, 2}; 10 System.out.println(permuteUnique(nums)); 11 } 12 13 public static List<List<Integer>> permuteUnique(int[] nums) { 14 List<Integer> perm = new ArrayList<>(); // 每种排列的容器 15 List<List<Integer>> res = new ArrayList<>(); // 所有排列的容器 16 boolean[] visited = new boolean[nums.length]; 17 Arrays.sort(nums); 18 backtracking(visited, 0, nums, perm, res); 19 return res; 20 } 21 22 public static void backtracking(boolean[] visited, int index, int[] nums, List<Integer> perm, List<List<Integer>> res) { 23 if (index == nums.length) { 24 res.add(new ArrayList<>(perm)); 25 return; 26 } 27 for (int j = 0; j < nums.length; j++) { 28 // 重复问题:保证在填第index个数的时候重复数字只会被填入一次即可。 29 // 选择对原数组排序,保证相同的数字都相邻,然后每次填入的数一定是这个数所在重复数集合中(从左往右第一个未被填过的数字) 30 // eg({1, 1, 2}): index=0时,填第一个1,visited[0]=true 31 // 回退回来(如何避免index=0时填入第二个1) 此时visited[0]=false 32 // 保证nums[1] == nums[0]且visited[0]=false即可 33 if (visited[j] || (j > 0 && nums[j] == nums[j - 1] && !visited[j - 1])) { 34 continue; 35 } 36 perm.add(nums[j]); 37 visited[j] = true; 38 backtracking(visited, index + 1, nums, perm, res); 39 visited[j] = false; 40 perm.remove(index); 41 } 42 } 43 }
补:不包含重复数字
1 class Solution { 2 public List<List<Integer>> permute(int[] nums) { 3 List<List<Integer>> result = new ArrayList<>(); 4 List<Integer> res = new ArrayList<>(); 5 boolean[] flag = new boolean[nums.length]; 6 helper(nums,0,flag, result,res); 7 return result; 8 } 9 10 public void helper(int[] nums,int index,boolean[] flag,List<List<Integer>> result,List<Integer> res){ 11 if(index == nums.length){ 12 result.add(new ArrayList<>(res)); 13 return; 14 } 15 for(int i=0;i<nums.length;i++){ 16 if(!flag[i]){ 17 res.add(nums[i]); 18 flag[i]=true; 19 helper(nums,index+1,flag,result,res); 20 flag[i]=false; 21 res.remove(index); 22 } 23 } 24 } 25 }
4、组合总和II
问题:
给定一个数组 candidates 和一个目标数 target ,找出 candidates 中所有可以使数字和为 target 的组合。
candidates 中的每个数字在每个组合中只能使用一次。
注意:解集不能包含重复的组合。
示例 1:
输入: candidates = [10,1,2,7,6,1,5], target = 8,
输出:
[
[1,1,6],
[1,2,5],
[1,7],
[2,6]
]
示例 2:
输入: candidates = [2,5,2,1,2], target = 5,
输出:
[
[1,2,2],
[5]
]
1 package LeetCode.test5_sousuosuanfa; 2 3 import java.util.ArrayList; 4 import java.util.Arrays; 5 import java.util.List; 6 7 public class ques_40_组合总和II { 8 public static void main(String[] args) { 9 int[] candidates = {10, 1, 2, 7, 6, 1, 5}; 10 int target = 8; 11 System.out.println(combinationSum2(candidates, target)); 12 } 13 14 public static List<List<Integer>> combinationSum2(int[] candidates, int target) { 15 Arrays.sort(candidates); 16 List<List<Integer>> res = new ArrayList<>(); 17 List<Integer> perm = new ArrayList<>(); 18 backtrack(candidates, 0, target, perm, res); 19 return res; 20 } 21 22 public static void backtrack(int[] candidates, int begin, int target, List<Integer> perm, List<List<Integer>> res) { 23 int sum = perm.stream().reduce(0, Integer::sum); 24 if (sum == target) { 25 res.add(new ArrayList<>(perm)); 26 return; 27 } 28 for (int i = begin; i < candidates.length; i++) { 29 if (i > begin && candidates[i] == candidates[i - 1]) { // 避免重复 30 continue; 31 } 32 if (sum < target) { 33 perm.add(candidates[i]); 34 backtrack(candidates, i + 1, target, perm, res); 35 perm.remove(perm.size() - 1); 36 }else { 37 break; 38 } 39 } 40 } 41 }
5、解数独
问题:
编写一个程序,通过填充空格来解决数独问题。
数独的解法需 遵循如下规则:
数字 1-9 在每一行只能出现一次。
数字 1-9 在每一列只能出现一次。
数字 1-9 在每一个以粗实线分隔的 3x3 宫内只能出现一次。(请参考示例图)
数独部分空格内已填入了数字,空白格用 '.' 表示。
示例:
输入:board = [["5","3",".",".","7",".",".",".","."],["6",".",".","1","9","5",".",".","."],[".","9","8",".",".",".",".","6","."],["8",".",".",".","6",".",".",".","3"],["4",".",".","8",".","3",".",".","1"],["7",".",".",".","2",".",".",".","6"],[".","6",".",".",".",".","2","8","."],[".",".",".","4","1","9",".",".","5"],[".",".",".",".","8",".",".","7","9"]]
输出:[["5","3","4","6","7","8","9","1","2"],["6","7","2","1","9","5","3","4","8"],["1","9","8","3","4","2","5","6","7"],["8","5","9","7","6","1","4","2","3"],["4","2","6","8","5","3","7","9","1"],["7","1","3","9","2","4","8","5","6"],["9","6","1","5","3","7","2","8","4"],["2","8","7","4","1","9","6","3","5"],["3","4","5","2","8","6","1","7","9"]]
解释:输入的数独如上图所示,唯一有效的解决方案如下所示:
1 package LeetCode.test5_sousuosuanfa; 2 3 import java.util.ArrayList; 4 import java.util.Arrays; 5 import java.util.List; 6 7 /** 8 * 十分经典的数独题,可以利用回溯法求解。事实上对于数独类型的题, 9 * 有很多进阶的搜索方法和剪枝策略可以提高速度,如启发式搜索。 10 */ 11 public class ques_37_解数独 { 12 private boolean valid = false; 13 // row[2][3]=True表示数字4在第2行已经出现过,当遍历到第2行的空白格时,就不能填入数字4。 14 private boolean[][] row = new boolean[9][9]; // 行 15 private boolean[][] col = new boolean[9][9]; 16 private boolean[][][] block = new boolean[3][3][9]; 17 // 存储空格所在位置 18 private List<int[]> spaces = new ArrayList<>(); 19 20 public void solveSudoku(char[][] board) { 21 22 for (int i = 0; i < 9; i++) { 23 for (int j = 0; j < 9; j++) { 24 if (board[i][j] == '.') { 25 spaces.add(new int[]{i, j}); 26 } else { 27 int index = board[i][j] - '0' - 1; 28 row[i][index] = col[j][index] = block[i / 3][j / 3][index] = true; 29 } 30 } 31 } 32 backtracking(board, 0); 33 } 34 35 public void backtracking(char[][] board, int pos) { 36 if (pos == spaces.size()) { 37 valid = true; 38 return; 39 } 40 int[] res = spaces.get(pos); 41 int i = res[0]; 42 int j = res[1]; 43 for (int index = 0; index < 9 && !valid; index++) { 44 if (!row[i][index] && !col[j][index] && !block[i / 3][j / 3][index]) { 45 row[i][index] = col[j][index] = block[i / 3][j / 3][index] = true; 46 board[i][j] = (char) (index + '0' + 1); 47 backtracking(board, pos + 1); 48 row[i][index] = col[j][index] = block[i / 3][j / 3][index] = false; 49 } 50 } 51 } 52 53 public static void main(String[] args) { 54 char[][] board = {{'5', '3', '.', '.', '7', '.', '.', '.', '.'}, 55 {'6', '.', '.', '1', '9', '5', '.', '.', '.'}, 56 {'.', '9', '8', '.', '.', '.', '.', '6', '.'}, 57 {'8', '.', '.', '.', '6', '.', '.', '.', '3'}, 58 {'4', '.', '.', '8', '.', '3', '.', '.', '1'}, 59 {'7', '.', '.', '.', '2', '.', '.', '.', '6'}, 60 {'.', '6', '.', '.', '.', '.', '2', '8', '.'}, 61 {'.', '.', '.', '4', '1', '9', '.', '.', '5'}, 62 {'.', '.', '.', '.', '8', '.', '.', '7', '9'}}; 63 ques_37_解数独 s = new ques_37_解数独(); 64 s.solveSudoku(board); 65 System.out.println(Arrays.deepToString(board)); 66 } 67 }
6、最小高度树
问题:
树是一个无向图,其中任何两个顶点只通过一条路径连接。 换句话说,一个任何没有简单环路的连通图都是一棵树。
给你一棵包含 n 个节点的树,标记为 0 到 n - 1 。给定数字 n 和一个有 n - 1 条无向边的 edges 列表(每一个边都是一对标签),其中 edges[i] = [ai, bi] 表示树中节点 ai 和 bi 之间存在一条无向边。
可选择树中任何一个节点作为根。当选择节点 x 作为根节点时,设结果树的高度为 h 。在所有可能的树中,具有最小高度的树(即,min(h))被称为 最小高度树 。
请你找到所有的 最小高度树 并按 任意顺序 返回它们的根节点标签列表。
树的 高度 是指根节点和叶子节点之间最长向下路径上边的数量。
示例 1:
输入:n = 4, edges = [[1,0],[1,2],[1,3]]
输出:[1]
解释:如图所示,当根是标签为 1 的节点时,树的高度是 1 ,这是唯一的最小高度树。
示例 2:
输入:n = 6, edges = [[3,0],[3,1],[3,2],[3,4],[5,4]]
输出:[3,4]
示例 3:
输入:n = 1, edges = []
输出:[0]
示例 4:
输入:n = 2, edges = [[0,1]]
输出:[0,1]
1 package LeetCode.test5_sousuosuanfa; 2 3 import java.util.*; 4 5 /** 6 * 思路:从边缘开始,先找到所有出度为1的节点,然后把所有出度为1的节点进队列,然后不断地bfs, 7 * 最后找到的就是两边同时向中间靠近的节点,那么这个中间节点就相当于把整个距离二分了, 8 * 那么它当然就是到两边距离最小的点啦,也就是到其他叶子节点最近的节点了。 9 * 出现的问题:超出内存限制 10 */ 11 public class ques_310_最小高度树 { 12 public static void main(String[] args) { 13 int[][] edges = {{3, 0}, {3, 1}, {3, 2}, {3, 4}, {5, 4}}; 14 int n = 6; 15 System.out.println(findMinHeightTrees(n, edges)); 16 } 17 18 public static List<Integer> findMinHeightTrees(int n, int[][] edges) { 19 List<Integer> res = new ArrayList<>(); 20 // 如果只有一个节点,那么他就是最小高度树 21 if (n == 1) { 22 res.add(0); 23 return res; 24 } 25 boolean[][] graph = new boolean[n][n]; 26 int[] degree = new int[n]; 27 for (int[] edge : edges) { 28 graph[edge[0]][edge[1]] = true; 29 graph[edge[1]][edge[0]] = true; 30 degree[edge[0]]++; 31 degree[edge[1]]++; 32 } 33 Queue<Integer> queue = new LinkedList<>(); 34 for (int i = 0; i < n; i++) { // 把所有出度为1的节点,也就是叶子节点入队 35 if (degree[i] == 1) { 36 queue.offer(i); 37 } 38 } 39 while (!queue.isEmpty()) { 40 // 每层循环都要new一个新的结果集合,这样最后保存的就是最终的最小高度树了 41 res = new ArrayList<>(); 42 int size = queue.size(); 43 for (int i = 0; i < size; i++) { // eg:假设最后只剩1或者2个节点 44 int top = queue.poll(); 45 res.add(top); 46 //经典bfs,把当前节点的相邻接点都拿出来 47 List<Integer> neighbors = new ArrayList<>(); 48 for (int j = 0; j < graph.length; j++) { 49 if (graph[j][top]) { 50 neighbors.add(j); 51 } 52 } 53 //把它们的出度都减1(当前节点已经不存在了),而它的相邻节点们就有可能变成叶子节点 54 for (Integer neighbor : neighbors) { 55 degree[neighbor]--; 56 if (degree[neighbor] == 1) { 57 queue.offer(neighbor); 58 } 59 } 60 } 61 } 62 return res; 63 } 64 }
1 package LeetCode.test5_sousuosuanfa; 2 3 import java.util.ArrayList; 4 import java.util.LinkedList; 5 import java.util.List; 6 import java.util.Queue; 7 8 /** 9 * 思路:从边缘开始,先找到所有出度为1的节点,然后把所有出度为1的节点进队列,然后不断地bfs, 10 * 最后找到的就是两边同时向中间靠近的节点,那么这个中间节点就相当于把整个距离二分了, 11 * 那么它当然就是到两边距离最小的点啦,也就是到其他叶子节点最近的节点了。 12 * 出现的问题:超出内存限制 13 */ 14 public class ques_310_最小高度树_2 { 15 public static void main(String[] args) { 16 int[][] edges = {{3, 0}, {3, 1}, {3, 2}, {3, 4}, {5, 4}}; 17 int n = 6; 18 System.out.println(findMinHeightTrees(n, edges)); 19 } 20 21 public static List<Integer> findMinHeightTrees(int n, int[][] edges) { 22 List<Integer> res = new ArrayList<>(); 23 // 如果只有一个节点,那么他就是最小高度树 24 if (n == 1) { 25 res.add(0); 26 return res; 27 } 28 // 建立图关系,在每个节点的list中存储相连节点 29 List<List<Integer>> map = new ArrayList<>(); // 索引为当前节点,索引所在list中的元素与索引为相邻节点 30 for (int i = 0; i < n; i++) { 31 map.add(new ArrayList<>()); 32 } 33 // 建立各个节点的出度表 34 int[] degree = new int[n]; 35 36 for (int[] edge : edges) { 37 map.get(edge[0]).add(edge[1]); 38 map.get(edge[1]).add(edge[0]); 39 degree[edge[0]]++; 40 degree[edge[1]]++; 41 } 42 Queue<Integer> queue = new LinkedList<>(); 43 for (int i = 0; i < n; i++) { // 把所有出度为1的节点,也就是叶子节点入队 44 if (degree[i] == 1) { 45 queue.offer(i); 46 } 47 } 48 while (!queue.isEmpty()) { 49 // 每层循环都要new一个新的结果集合,这样最后保存的就是最终的最小高度树了 50 res = new ArrayList<>(); 51 int size = queue.size(); 52 for (int i = 0; i < size; i++) { // eg:假设最后只剩1或者2个节点 53 int top = queue.poll(); 54 res.add(top); 55 //经典bfs,把当前节点的相邻接点都拿出来 56 List<Integer> neighbors = map.get(top); 57 //把它们的出度都减1(当前节点已经不存在了),而它的相邻节点们就有可能变成叶子节点 58 for (Integer neighbor : neighbors) { 59 degree[neighbor]--; 60 if (degree[neighbor] == 1) { 61 queue.offer(neighbor); 62 } 63 } 64 } 65 } 66 return res; 67 } 68 }
7、迷宫找礼物
问题:给定一个二维数组,0代表能走,1代表不能走,8代表宝藏的位置,入口是二维数组的边界位置的任一个不为0的位置,求从人口到宝藏位置的最短路径,并输出路径
1 package Interview; 2 3 import java.util.*; 4 5 class Node { 6 int x; 7 int y; 8 Node pre; 9 10 public Node(int x, int y) { 11 this.x = x; 12 this.y = y; 13 } 14 } 15 16 // 问题:起点为maze四周为0的点(多个),终点为8,列出所有起点到终点的最短路径中最短的路径。 17 public class test1 { 18 int[] X = {0, 0, 1, -1}; 19 int[] Y = {1, -1, 0, 0}; 20 boolean[][] vis; 21 ArrayList<Node> arrayList; 22 23 public ArrayList<Node> winMazeGift(int[][] maze) { 24 ArrayList<ArrayList<Node>> result = new ArrayList<>(); 25 for (int i = 0; i < maze.length; i++) { 26 if (maze[i][0] == 0 || maze[i][0] == 8) { 27 result.add(mut(i, 0, maze)); 28 } 29 if (maze[i][maze[0].length - 1] == 0 || maze[i][maze[0].length - 1] == 8) { 30 result.add(mut(i, maze[0].length - 1, maze)); 31 } 32 } 33 34 for (int i = 1; i < maze[0].length - 1; i++) { 35 if (maze[0][i] == 0 || maze[0][i] == 8) { 36 result.add(mut(0, i, maze)); 37 } 38 if (maze[maze.length - 1][i] == 0 || maze[maze.length - 1][i] == 8) { 39 result.add(mut(maze.length - 1, i, maze)); 40 } 41 } 42 43 int min = Integer.MAX_VALUE; 44 for (ArrayList<Node> nodes : result) { 45 min = Math.min(min, nodes.size()); 46 } 47 48 int index = -1; 49 for (int i = 0; i < result.size(); i++) { 50 if (result.get(i).size() == min) { 51 index = i; 52 } 53 } 54 // 测试 55 for (int j = 0; j < min; j++) { 56 Node pop = result.get(index).get(j); 57 System.out.println("(" + pop.x + "," + pop.y + ")"); 58 } 59 60 return index == -1 ? new ArrayList<>() : result.get(index); 61 } 62 63 public ArrayList<Node> mut(int x, int y, int[][] maze) { 64 ArrayList<Node> res = new ArrayList<>(); 65 helper(x, y, maze); 66 Stack<Node> stack = new Stack<>(); 67 print(arrayList.get(arrayList.size() - 1), stack); 68 while (!stack.isEmpty()) { 69 Node pop = stack.pop(); 70 res.add(pop); 71 // System.out.println("(" + pop.x + "," + pop.y + ")"); 72 } 73 return res; 74 } 75 76 77 public void helper(int x, int y, int[][] maze) { 78 vis = new boolean[maze.length][maze[0].length]; 79 Queue<Node> queue = new LinkedList<>(); 80 // 用来保存每一个出队列的节点 81 arrayList = new ArrayList<>(); 82 queue.offer(new Node(x, y)); 83 vis[x][y] = true; 84 while (!queue.isEmpty()) { 85 Node top = queue.poll(); 86 arrayList.add(top); 87 if (maze[top.x][top.y] == 8) { 88 return; 89 } 90 for (int i = 0; i < 4; i++) { 91 int newX = top.x + X[i]; 92 int newY = top.y + Y[i]; 93 if (judge(newX, newY, maze)) { 94 vis[newX][newY] = true; 95 Node node = new Node(newX, newY); 96 queue.offer(node); 97 node.pre = top; 98 } 99 } 100 } 101 } 102 103 public boolean judge(int newX, int newY, int[][] maze) { 104 if (newX < 0 || newY < 0 || newX >= maze.length || newY >= maze[0].length) { 105 return false; 106 } 107 if (maze[newX][newY] == 1 || vis[newX][newY]) { 108 return false; 109 } 110 return true; 111 } 112 113 public void print(Node node, Stack<Node> stack) { 114 stack.push(node); 115 if (node.pre != null) { 116 print(node.pre, stack); 117 } 118 } 119 120 } 121 122 class Test_test1 { 123 public static void main(String[] args) { 124 test1 t = new test1(); 125 126 int[][] maze = {{0, 1, 1, 1}, 127 {0, 0, 0, 1}, 128 {1, 0, 8, 1}, 129 {1, 0, 1, 1}}; 130 131 // int[][] maze = {{0, 1, 1, 1}, 132 // {8, 0, 0, 1}, 133 // {1, 0, 0, 1}, 134 // {1, 0, 1, 1}}; 135 136 // int[][] maze = {{0, 0, 1, 1, 1, 1, 1, 1, 1, 1}, 137 // {1, 0, 0, 1, 0, 0, 0, 1, 0, 1}, 138 // {1, 0, 0, 1, 0, 0, 0, 1, 0, 1}, 139 // {1, 0, 0, 0, 0, 1, 1, 0, 0, 1}, 140 // {1, 0, 1, 1, 1, 0, 0, 0, 0, 1}, 141 // {1, 0, 0, 0, 1, 0, 0, 0, 0, 1}, 142 // {1, 0, 1, 0, 0, 0, 1, 0, 0, 1}, 143 // {1, 0, 1, 1, 1, 0, 1, 1, 0, 1}, 144 // {1, 1, 0, 0, 0, 0, 0, 0, 8, 1}, 145 // {1, 1, 1, 1, 1, 1, 1, 1, 1, 1}}; 146 147 System.out.println(t.winMazeGift(maze)); 148 } 149 }
8、电话号码的自由组合
问题:给定一个仅包含数字 2-9 的字符串,返回所有它能表示的字母组合。答案可以按任意顺序返回。
给出数字到字母的映射如下(与电话按键相同)。注意 1 不对应任何字母。
示例 1:
输入:digits = "23"
输出:["ad","ae","af","bd","be","bf","cd","ce","cf"]
示例 2:
输入:digits = ""
输出:[]
示例 3:
输入:digits = "2"
输出:["a","b","c"]
1 class Solution { 2 public List<String> letterCombinations(String digits) { 3 List<String> result = new ArrayList<>(); 4 if(digits == null || digits.isEmpty()){ 5 return result; 6 } 7 char[][] temp = {{'a','b','c'},{'d','e','f'},{'g','h','i'},{'j','k','l'},{'m','n','o'},{'p','q','r','s'},{'t','u','v'},{'w','x','y','z'}}; 8 Queue<String> queue = new LinkedList<>(); 9 queue.add(""); 10 while(queue.peek().length()!=digits.length()){ 11 String top = queue.poll(); 12 char[] temps = temp[digits.charAt(top.length()) - '2']; 13 for(int i=0;i<temps.length;i++){ 14 queue.offer(top+temps[i]); 15 } 16 } 17 while(!queue.isEmpty()){ 18 result.add(queue.poll()); 19 } 20 return result; 21 } 22 }