算法题解之图论与搜索
Additive Number
加法数
思路:一开始以为要用DP来做,但是这道题除非能保证每个相加数的划分是唯一的(事实上不是唯一的),否则只能用搜索。
1 public class Solution { 2 public boolean isAdditiveNumber(String num) { 3 for (int firstEnd = 0; firstEnd <= num.length() - 3; firstEnd++) { 4 if (num.charAt(0) == '0' && firstEnd > 0) { 5 break; 6 } 7 for (int secondEnd = firstEnd + 1; secondEnd <= num.length() - 2; secondEnd++) { 8 if (num.charAt(firstEnd + 1) == '0' && secondEnd > firstEnd + 1) { 9 break; 10 } 11 long first = Long.parseLong(num.substring(0, firstEnd + 1)); 12 long second = Long.parseLong(num.substring(firstEnd + 1, secondEnd + 1)); 13 if (helper(num, secondEnd + 1, first + second, second)) { 14 return true; 15 } 16 } 17 } 18 return false; 19 } 20 21 public boolean helper(String num, int start, long expect_num, long pre_num) { 22 if (start > num.length() - 1) { 23 return true; 24 } 25 if (num.charAt(start) == '0' && expect_num != 0) { 26 return false; 27 } 28 int end = start + String.valueOf(expect_num).length() - 1; 29 if (end + 1 > num.length()) { 30 return false; 31 } 32 long cur = Long.parseLong(num.substring(start, end + 1)); 33 if (cur == expect_num) { 34 long next_expect = pre_num + cur; 35 return helper(num, end + 1, next_expect, cur); 36 } 37 return false; 38 } 39 }
Clone Graph
克隆图
思路:类似于copy random list,先克隆每个节点的值,建立新老节点的hash映射,再根据映射关系克隆邻接表。
/** * Definition for undirected graph. * class UndirectedGraphNode { * int label; * ArrayList<UndirectedGraphNode> neighbors; * UndirectedGraphNode(int x) { label = x; neighbors = new ArrayList<UndirectedGraphNode>(); } * }; */ public class Solution { /** * @param node: A undirected graph node * @return: A undirected graph node */ public UndirectedGraphNode cloneGraph(UndirectedGraphNode node) { // write your code here if (node == null) { return null; } Map<UndirectedGraphNode, UndirectedGraphNode> old2new = new HashMap<UndirectedGraphNode, UndirectedGraphNode>(); Queue<UndirectedGraphNode> q = new LinkedList<UndirectedGraphNode>(); q.offer(node); UndirectedGraphNode copy = new UndirectedGraphNode(node.label); copy.neighbors = new ArrayList<UndirectedGraphNode>(node.neighbors); old2new.put(node, copy); while (!q.isEmpty()) { int size = q.size(); for (int i = 1; i <= size; i++) { UndirectedGraphNode cur = q.poll(); for (UndirectedGraphNode next : cur.neighbors) { if (!old2new.containsKey(next)) { q.offer(next); UndirectedGraphNode copyNode = new UndirectedGraphNode(next.label); copyNode.neighbors = new ArrayList<UndirectedGraphNode>(next.neighbors); old2new.put(next, copyNode); } } } } for (Map.Entry<UndirectedGraphNode, UndirectedGraphNode> entry : old2new.entrySet()) { UndirectedGraphNode copy2 = entry.getValue(); ArrayList<UndirectedGraphNode> neighbors = copy2.neighbors; for (int i = 0; i < neighbors.size(); i++) { neighbors.set(i, old2new.get(neighbors.get(i))); } } return old2new.get(node); } }
Course Schedule
课程计划
思路:这道题的本质其实是判断一张图是否存在拓扑排序。拓扑排序表示的是修课的顺序。根据边集合构造图,然后就是拓扑排序那一套了。
1 public class Solution { 2 public boolean canFinish(int numCourses, int[][] prerequisites) { 3 Map<Integer, Value> map = new HashMap<Integer, Value>(); 4 for (int i = 0; i < prerequisites.length; i++) { 5 int to = prerequisites[i][0]; 6 int from = prerequisites[i][1]; 7 8 if (!map.containsKey(from)) { 9 map.put(from, new Value()); 10 } 11 if (!map.containsKey(to)) { 12 map.put(to, new Value()); 13 } 14 map.get(from).neighbors.add(to); 15 map.get(to).inEdges += 1; 16 } 17 18 19 Queue<Integer> q = new LinkedList<Integer>(); 20 for (Integer course : map.keySet()) { 21 if (map.get(course).inEdges == 0) { 22 q.offer(course); 23 } 24 } 25 int notFinished = map.size(); 26 while (!q.isEmpty()) { 27 int finish = q.poll(); 28 notFinished--; 29 for (Integer course : map.get(finish).neighbors) { 30 if (--map.get(course).inEdges == 0) { 31 q.offer(course); 32 } 33 } 34 } 35 if (notFinished == 0) { 36 return true; 37 } else { 38 return false; 39 } 40 } 41 42 } 43 44 class Value { 45 int inEdges; 46 List<Integer> neighbors; 47 Value() { 48 neighbors = new ArrayList<Integer>(); 49 } 50 }
Course Schedule II
课程计划II
思路:本质就是求拓扑排序。
1 public class Solution { 2 public int[] findOrder(int numCourses, int[][] prerequisites) { 3 Map<Integer, Value> map = new HashMap<Integer, Value>(); 4 for (int i = 0; i < prerequisites.length; i++) { 5 int to = prerequisites[i][0]; 6 int from = prerequisites[i][1]; 7 8 if (!map.containsKey(from)) { 9 map.put(from, new Value()); 10 } 11 if (!map.containsKey(to)) { 12 map.put(to, new Value()); 13 } 14 map.get(from).neighbors.add(to); 15 map.get(to).inEdges += 1; 16 } 17 18 Queue<Integer> q = new LinkedList<Integer>(); 19 for (Integer course : map.keySet()) { 20 if (map.get(course).inEdges == 0) { 21 q.offer(course); 22 } 23 } 24 25 int[] res = new int[numCourses]; 26 int cur = 0; 27 while (!q.isEmpty()) { 28 int finish = q.poll(); 29 res[cur++] = finish; 30 for (Integer course : map.get(finish).neighbors) { 31 if (--map.get(course).inEdges == 0) { 32 q.offer(course); 33 } 34 } 35 } 36 if (cur != map.size()) { 37 return new int[0]; 38 } 39 40 for (int i = 0; i < numCourses; i++) { 41 if (!map.containsKey(i)) { 42 res[cur++] = i; 43 } 44 } 45 46 return res; 47 } 48 49 } 50 51 class Value { 52 int inEdges; 53 List<Integer> neighbors; 54 Value() { 55 neighbors = new ArrayList<Integer>(); 56 } 57 }
Combination Sum
组合sum
思路:常规DFS题,先排序,再搜索。注意代码中如何去重。
public class Solution { /** * @param candidates: A list of integers * @param target:An integer * @return: A list of lists of integers */ public List<List<Integer>> combinationSum(int[] candidates, int target) { // write your code here Arrays.sort(candidates); List<List<Integer>> res = new ArrayList<List<Integer>>(); helper(res, new ArrayList<Integer>(), candidates, 0, 0, target); return res; } public void helper(List<List<Integer>> res, List<Integer> cur, int[] candidates, int start, int sum, int target) { if (sum == target) { List<Integer> tmp = new ArrayList<Integer>(cur); res.add(tmp); return; } for (int i = start; i < candidates.length;) { if (sum + candidates[i] > target) { break; } cur.add(candidates[i]); helper(res, cur, candidates, i, sum + candidates[i], target); cur.remove(cur.size() - 1); if(i == candidates.length - 1) { break; } while (i < candidates.length - 1 && candidates[i++] == candidates[i]) {} if (i == candidates.length - 1 && candidates[i] == candidates[i-1]) { i++; } } } }
Find the Connected Component in the Undirected Graph
找无向图的连通块
思路:以图中任意一个节点为起点做搜索,搜索完就找到一个连通块,并把搜索过的节点加入set。再对图中其他节点,如果搜索过就pass,没搜索过就搜索。
/** * Definition for Undirected graph. * class UndirectedGraphNode { * int label; * ArrayList<UndirectedGraphNode> neighbors; * UndirectedGraphNode(int x) { label = x; neighbors = new ArrayList<UndirectedGraphNode>(); } * }; */ public class Solution { /** * @param nodes a array of Undirected graph node * @return a connected set of a Undirected graph */ public List<List<Integer>> connectedSet(ArrayList<UndirectedGraphNode> nodes) { // Write your code here Set<UndirectedGraphNode> searchedNodes = new HashSet<UndirectedGraphNode>(); List<List<Integer>> res = new ArrayList<List<Integer>>(); for (UndirectedGraphNode node : nodes) { if (searchedNodes.contains(node)) { continue; } List<Integer> cur_res = new ArrayList<Integer>(); Queue<UndirectedGraphNode> q = new LinkedList<UndirectedGraphNode>(); Set<UndirectedGraphNode> set = new HashSet<UndirectedGraphNode>(); q.offer(node); set.add(node); searchedNodes.add(node); while (!q.isEmpty()) { int size = q.size(); for (int i = 1; i <= size; i++) { UndirectedGraphNode cur = q.poll(); cur_res.add(cur.label); for (UndirectedGraphNode neighbor : cur.neighbors) { if (!set.contains(neighbor)) { q.offer(neighbor); set.add(neighbor); searchedNodes.add(neighbor); } } } } Collections.sort(cur_res); res.add(cur_res); } return res; } }
k Sum II
k数和II
思路:常规的搜索题。注意求方案个数的K sum是用动态规划。
public class Solution { /** * @param A: an integer array. * @param k: a positive integer (k <= length(A)) * @param target: a integer * @return a list of lists of integer */ public ArrayList<ArrayList<Integer>> kSumII(int[] A, int k, int target) { // write your code here ArrayList<ArrayList<Integer>> res = new ArrayList<ArrayList<Integer>>(); helper(res, new ArrayList<Integer>(), 0, target, k, 0, A); return res; } public void helper(ArrayList<ArrayList<Integer>> res, ArrayList<Integer> cur, int start, int target, int k, int sum, int[] A) { if (cur.size() == k) { if (sum == target) { res.add(new ArrayList<Integer>(cur)); } return; } for (int i = start; i < A.length; i++) { cur.add(A[i]); helper(res, cur, i + 1, target, k, sum + A[i], A); cur.remove(cur.size() - 1); } } }
Minimum Height Trees
最小高度树
思路:不断去掉外面一层叶子节点(即度为1的点),最后剩下的一个或两个点就是root。原理暂时还不懂。。
1 public class Solution { 2 public List<Integer> findMinHeightTrees(int n, int[][] edges) { 3 List<Integer> res = new ArrayList<Integer>(); 4 if (n == 1) { 5 res.add(0); 6 return res; 7 } 8 if (n == 2) { 9 res.add(0); 10 res.add(1); 11 return res; 12 } 13 14 List<Set<Integer>> map = new ArrayList<Set<Integer>>(); 15 for (int i = 0; i < edges.length; i++) { 16 int n1 = edges[i][0]; 17 int n2 = edges[i][1]; 18 if (map.get(n1) == null) { 19 map.add(n1, new HashSet<Integer>()); 20 } 21 if (map.get(n2) == null) { 22 map.put(n2, new HashSet<Integer>()); 23 } 24 map.get(n1).add(n2); 25 map.get(n2).add(n1); 26 } 27 28 Queue<Integer> q = new LinkedList<Integer>(); 29 for (Integer node : map.keySet()) { 30 if (map.get(node).size() == 1) { 31 q.offer(node); 32 } 33 } 34 35 while (!q.isEmpty()) { 36 int size = q.size(); 37 for (int i = 1; i <= size; i++) { 38 int cur = q.poll(); 39 int neighbor = map.get(cur).iterator().next(); 40 map.get(neighbor).remove(cur); 41 if (map.get(neighbor).size() == 1) { 42 q.offer(neighbor); 43 } 44 map.remove(cur); 45 } 46 if (map.size() <= 2) { 47 break; 48 } 49 } 50 51 for (Integer node : map.keySet()) { 52 res.add(node); 53 } 54 return res; 55 } 56 }
Number of Islands
小岛数量
思路:遍历每个元素,如果是岛屿并且没有被搜过就BFS这个岛,同时岛屿数加一。
1 public class Solution { 2 public int numIslands(char[][] grid) { 3 if (grid == null || grid.length == 0 || grid[0].length == 0) { 4 return 0; 5 } 6 int res = 0; 7 boolean[][] searched = new boolean[grid.length][grid[0].length]; 8 for (int i = 0; i < grid.length; i++) { 9 for (int j = 0; j < grid[0].length; j++) { 10 if (grid[i][j] == '1' && !searched[i][j]) { 11 res++; 12 searchAndMark(grid, i, j, searched); 13 } 14 } 15 } 16 return res; 17 } 18 19 public void searchAndMark(char[][] grid, int cur_x, int cur_y, boolean[][] searched) { 20 21 if (cur_x >= 0 && cur_x < grid.length && cur_y >= 0 && cur_y < grid[0].length 22 && !searched[cur_x][cur_y] && grid[cur_x][cur_y] == '1') { 23 searched[cur_x][cur_y] = true; 24 searchAndMark(grid, cur_x - 1, cur_y, searched); 25 searchAndMark(grid, cur_x, cur_y - 1, searched); 26 searchAndMark(grid, cur_x + 1, cur_y, searched); 27 searchAndMark(grid, cur_x, cur_y + 1, searched); 28 } 29 } 30 }
N-Queens
N皇后
思路:常规DFS。使用三个boolean数组分别记录每一列,每一条主对角线(i-j相等),每一条副对角线(i+j相等)是否能放。再用一个字符数组board表示当前的棋牌放置。
class Solution { /** * Get all distinct N-Queen solutions * @param n: The number of queens * @return: All distinct solutions * For example, A string '...Q' shows a queen on forth position */ ArrayList<ArrayList<String>> solveNQueens(int n) { // write your code here char[][] board = new char[n][n]; for (int i = 0; i < n; i++) { for (int j = 0; j < n; j++) { board[i][j] = '.'; } } boolean[] col_used = new boolean[n]; boolean[] pos_diag_used = new boolean[2 * n]; boolean[] neg_diag_used = new boolean[2 * n]; ArrayList<ArrayList<String>> res = new ArrayList<ArrayList<String>>(); dfs(res, board, col_used, pos_diag_used, neg_diag_used, 0); return res; } public void dfs(ArrayList<ArrayList<String>> res, char[][] board, boolean[] col_used, boolean[] pos_diag_used, boolean[] neg_diag_used, int cur_row) { int n = board.length; if (cur_row == n) { ArrayList<String> curSolution = buildRes(board); res.add(curSolution); return; } for (int j = 0; j < n; j++) { int pos_diag_index = (cur_row - j >= 0) ? cur_row - j : n - 1 + j - cur_row; if (col_used[j] == false && pos_diag_used[pos_diag_index] == false && neg_diag_used[cur_row + j] == false) { col_used[j] = true; pos_diag_used[pos_diag_index] = true; neg_diag_used[cur_row + j] = true; board[cur_row][j] = 'Q'; dfs(res, board, col_used, pos_diag_used, neg_diag_used, cur_row + 1); col_used[j] = false; pos_diag_used[pos_diag_index] = false; neg_diag_used[cur_row + j] = false; board[cur_row][j] = '.'; } } } public ArrayList<String> buildRes(char[][] board) { ArrayList<String> solution = new ArrayList<String>(); for (int i = 0; i < board.length; i++) { solution.add(String.valueOf(board[i])); } return solution; } };
N-Queens II
N皇后II
思路:常规DFS搜索题。使用三个boolean数组分别记录每一列,每一条主对角线(i-j相等),每一条副对角线(i+j相等)是否能放。
class Solution { /** * Calculate the total number of distinct N-Queen solutions. * @param n: The number of queens. * @return: The total number of distinct solutions. */ int solutionCount = 0; public int totalNQueens(int n) { //write your code here boolean[] col_Used = new boolean[n]; boolean[] diag_pos_used = new boolean[2 * n]; boolean[] diag_neg_used = new boolean[2 * n]; dfs(col_Used, diag_pos_used, diag_neg_used, 0, n); return solutionCount; } public void dfs(boolean[] col_Used, boolean[] diag_pos_used, boolean[] diag_neg_used, int cur_row, int n) { if (cur_row == n) { solutionCount++; return; } for (int i = 0; i < n; i++) { int tmp = (cur_row - i) >= 0 ? cur_row - i : n-1+(i-cur_row); if (col_Used[i] == false && diag_pos_used[tmp] == false && diag_neg_used[i+cur_row] == false) { col_Used[i] = true; diag_pos_used[tmp] = true; diag_neg_used[i+cur_row] = true; dfs(col_Used, diag_pos_used, diag_neg_used, cur_row + 1, n); col_Used[i] = false; diag_pos_used[tmp] = false; diag_neg_used[i+cur_row] = false; } } } }
Palindrome Partitioning
分割回文串
思路:时间复杂度是O(2^n),即每个切口切或不切,有2^n-1种切法。先用动态规划求出一个二维数组表示每个字串是否是回文串,再进行DFS。
public class Solution { /** * @param s: A string * @return: A list of lists of string */ public List<List<String>> partition(String s) { // write your code here int[][] dp = new int[s.length()][s.length()]; for (int i = 0; i <= s.length() - 1; i++) { dp[i][i] = 1; } for (int i = 0; i <= s.length() - 2; i++) { if (s.charAt(i) == s.charAt(i + 1)) { dp[i][i+1] = 1; } } for (int len = 3; len <= s.length(); len++) { for (int i = 0; i <= s.length() - len; i++) { if (s.charAt(i) == s.charAt(i+len-1) && dp[i+1][i+len-2] == 1) { dp[i][i+len-1] = 1; } } } List<List<String>> res = new ArrayList<List<String>>(); helper(res, new ArrayList<String>(), s, 0, dp); return res; } public void helper(List<List<String>> res, List<String> cur, String s, int start, int[][] dp) { if (start > s.length() - 1) { List<String> tmp = new ArrayList<String>(cur); res.add(tmp); return; } for (int end = start; end <= s.length() - 1; end++) { if (dp[start][end] == 1) { cur.add(s.substring(start, end + 1)); helper(res, cur, s, end + 1, dp); cur.remove(cur.size() - 1); } } } }
Permutations II
带重复元素的排列
思路:常规DFS。记录一个used数组。如何避免重复: 比如,给出一个排好序的数组,[1,2,2],那么第一个2和第二2如果在结果中互换位置, 我们也认为是同一种方案,所以我们强制要求相同的数字,原来排在前面的,在结果当中也应该排在前面,这样就保证了唯一性。所以当前面的2还没有使用的时 候,就不应该让后面的2使用。
class Solution { /** * @param nums: A list of integers. * @return: A list of unique permutations. */ public List<List<Integer>> permuteUnique(int[] nums) { // Write your code here Arrays.sort(nums); List<List<Integer>> res = new ArrayList<List<Integer>>(); helper(res, new ArrayList<Integer>(), new boolean[nums.length], nums); return res; } public void helper(List<List<Integer>> res, List<Integer> cur, boolean[] used, int[] nums) { if (cur.size() == nums.length) { List<Integer> tmp = new ArrayList<Integer>(cur); res.add(tmp); return; } for (int i = 0; i < nums.length; i++) { if (used[i] == false) { if (i != 0 && nums[i] == nums[i-1] && used[i-1] == false) { continue; } cur.add(nums[i]); used[i] = true; helper(res, cur, used, nums); used[i] = false; cur.remove(cur.size() - 1); } } } }
Permutation Sequence
全排列序列
思路:这道题用搜索会超时。放在这是为了与PermutationsI和II对比。这道题可以用O(n)的时间复杂度来求解。
第一位是数字可能是1~n,其中以每个数字打头的有(n-1)!种全排列,因此第一位数应该是 1 ~ n中的第 k / (n-1)!个数,设为a;
接着,问题转化为,1~n中去掉a后,剩下的数中第 k%(n - 1)!个全排列,按照找第一位数的方法再来找第二位数,第三位数..........。
1 public class Solution { 2 public String getPermutation(int n, int k) { 3 String res = ""; 4 List<Integer> nums = new ArrayList<Integer>(); 5 for (int i = 1; i <= 9; i++) { 6 nums.add(i); 7 } 8 9 //facs[i]表示阶乘i! 10 int[] facs = new int[10]; 11 facs[1] = 1; 12 for (int i = 2; i <= 9; i++) { 13 facs[i] = facs[i - 1] * i; 14 } 15 16 int j = 0; //j表示这一位数是剩余的数的第j个数 17 int m = n; //m表示剩下多少个数 18 for (int i = 1; i < n; i++) { 19 int fac = facs[m - 1]; 20 j = k % fac == 0 ? k / fac - 1 : k / fac; 21 k = k % fac == 0 ? fac : k % fac; 22 res += String.valueOf(nums.get(j)); 23 nums.remove(j); 24 m--; 25 } 26 res += String.valueOf(nums.get(0)); 27 return res; 28 } 29 }
Subsets II
带重复元素的子集
思路:常规dfs题目,先排序,后搜索.注意如何去重
class Solution { /** * @param nums: A set of numbers. * @return: A list of lists. All valid subsets. */ public ArrayList<ArrayList<Integer>> subsetsWithDup(int[] nums) { // write your code here Arrays.sort(nums); ArrayList<ArrayList<Integer>> res = new ArrayList<ArrayList<Integer>>(); helper(res, new ArrayList<Integer>(), nums, 0); return res; } public void helper(ArrayList<ArrayList<Integer>> res, ArrayList<Integer> cur, int[] nums, int start) { ArrayList<Integer> tmp = new ArrayList<Integer>(cur); res.add(tmp); for (int i = start; i < nums.length;) { cur.add(nums[i]); helper(res, cur, nums, i + 1); cur.remove(cur.size() - 1); if (i == nums.length - 1) { i++; } while (i < nums.length - 1 && nums[i++] == nums[i]) {} if (i == nums.length - 1 && nums[i-1] == nums[i]) { i++; } } } }
Six Degrees
六度问题
思路:使用BFS,一层一层搜索,到达目标节点的层数就是答案。
/** * Definition for Undirected graph. * class UndirectedGraphNode { * int label; * List<UndirectedGraphNode> neighbors; * UndirectedGraphNode(int x) { * label = x; * neighbors = new ArrayList<UndirectedGraphNode>(); * } * }; */ public class Solution { /** * @param graph a list of Undirected graph node * @param s, t two Undirected graph nodes * @return an integer */ public int sixDegrees(List<UndirectedGraphNode> graph, UndirectedGraphNode s, UndirectedGraphNode t) { // Write your code here if (s.equals(t)) { return 0; } Set<UndirectedGraphNode> set = new HashSet<UndirectedGraphNode>(); Queue<UndirectedGraphNode> queue = new LinkedList<UndirectedGraphNode>(); int layernum = 1; set.add(s); queue.offer(s); while (!queue.isEmpty()) { int size = queue.size(); for (int i = 1; i <= size; i++) { UndirectedGraphNode cur = queue.poll(); for (int j = 0; j < cur.neighbors.size(); j++) { UndirectedGraphNode next = cur.neighbors.get(j); if (set.contains(next)) { continue; } set.add(next); queue.offer(next); if (next.equals(t)) { return layernum; } } } layernum++; } return -1; } }
Topological Sorting
拓扑排序
思路:先找出任意一个没有入边的点。然后显示出该点,并将它及其边从图中删除。然后对图中其余部分应用这种方法。
具体来说:先求每个节点的入度,然后把入度为0的节点放入队列中,出队的时候记录节点并将邻接表中所有节点的入度都减1,如果产生新的入度为0的节点就放入队列。直到队列为空。
/** * Definition for Directed graph. * class DirectedGraphNode { * int label; * ArrayList<DirectedGraphNode> neighbors; * DirectedGraphNode(int x) { label = x; neighbors = new ArrayList<DirectedGraphNode>(); } * }; */ public class Solution { /** * @param graph: A list of Directed graph node * @return: Any topological order for the given graph. */ public ArrayList<DirectedGraphNode> topSort(ArrayList<DirectedGraphNode> graph) { // write your code here Map<DirectedGraphNode, Integer> nodeMap = new HashMap<DirectedGraphNode, Integer>(); for (DirectedGraphNode node : graph) { nodeMap.put(node, 0); } for (DirectedGraphNode node : graph) { for (DirectedGraphNode neighbor : node.neighbors) { nodeMap.put(neighbor, nodeMap.get(neighbor) + 1); } } Queue<DirectedGraphNode> q = new LinkedList<DirectedGraphNode>(); for (Map.Entry<DirectedGraphNode, Integer> entry : nodeMap.entrySet()) { if (entry.getValue() == 0) { q.offer(entry.getKey()); } } ArrayList<DirectedGraphNode> res = new ArrayList<DirectedGraphNode>(); while (!q.isEmpty()) { DirectedGraphNode cur = q.poll(); res.add(cur); for (DirectedGraphNode neighbor : cur.neighbors) { nodeMap.put(neighbor, nodeMap.get(neighbor) - 1); if (nodeMap.get(neighbor) == 0) { q.offer(neighbor); } } } return res; } }
Word Ladder
单词接龙
思路:第一步:求出每个单词的邻接单词表,即word2neighbors,它的键是dict中每个单词,值是与该单词相差一个单词的所有单词。
第二步:根据邻接单词表,从end开始作bfs遍历,一层一层遍历,直到找到start,返回当前的层数。
public class Solution { /** * @param start, a string * @param end, a string * @param dict, a set of string * @return an integer */ public int ladderLength(String start, String end, Set<String> dict) { // write your code here if (start.equals(end)) { return 1; } dict.add(start); dict.add(end); int len = start.length(); Map<String, List<String>> word2neighbors = getNeibors(dict, len); return bfs(word2neighbors, start, end); } public Map<String, List<String>> getNeibors(Set<String> dict, int len) { Map<String, List<String>> word2neighbors = new HashMap<String, List<String>>(); for (int i = 0; i < len; i++) { Map<String, List<String>> rep2words = new HashMap<String, List<String>>(); for (String word : dict) { String rep = word.substring(0, i) + word.substring(i + 1); updateMap(rep2words, rep, word); } for (Map.Entry<String, List<String>> entry : rep2words.entrySet()) { List<String> words = entry.getValue(); for (String word1 : words) { for (String word2 : words) { if (!word1.equals(word2)) { updateMap(word2neighbors, word1, word2); } } } } } return word2neighbors; } public int bfs(Map<String, List<String>> word2neighbors, String start, String end) { Set<String> searched = new HashSet<String>(); Queue<String> q = new LinkedList<String>(); q.offer(start); searched.add(start); int layer = 1; while(!q.isEmpty()) { int size = q.size(); for (int i = 1; i <= size; i++) { String cur = q.poll(); List<String> neighbors = word2neighbors.get(cur); for (String neighbor : neighbors) { if (!searched.contains(neighbor)) { if (neighbor.equals(end)) { return layer + 1; } q.offer(neighbor); searched.add(neighbor); } } } layer++; } return -1; } public void updateMap(Map<String, List<String>> map, String key, String value) { List<String> list = map.get(key); if (list == null) { list = new ArrayList<String>(); list.add(value); map.put(key, list); } else { list.add(value); } } }
Word Ladder II
单词接龙II
思路1:本题是搜索题目中的较难题。
第一步:求出每个单词的邻接单词表,即word2neighbors,它的键是dict中每个单词,值是与该单词相差一个单词的所有单词。
第二步:根据邻接单词表,从end开始作bfs遍历,一层一层遍历,直到找到start。并生成prewords表,它的键是遍历过的每个单词,值是在遍历路径上连到该单词的所有的前驱单词;
第三步:根据prewords表,从start开始作dfs遍历,遍历到end时就将路径加入res。
public class Solution { /** * @param start, a string * @param end, a string * @param dict, a set of string * @return a list of lists of string */ public List<List<String>> findLadders(String start, String end, Set<String> dict) { // write your code here dict.add(start); dict.add(end); int len = start.length(); Map<String, List<String>> word2neighbors = getNeibors(dict, len); Map<String, List<String>> prewords = bfs(word2neighbors, start, end); List<List<String>> res = new ArrayList<List<String>>(); List<String> cur = new ArrayList<String>(); cur.add(start); dfs(res, cur, prewords, start, end); return res; } public Map<String, List<String>> getNeibors(Set<String> dict, int len) { Map<String, List<String>> word2neighbors = new HashMap<String, List<String>>(); for (int i = 0; i < len; i++) { Map<String, List<String>> rep2words = new HashMap<String, List<String>>(); for (String word : dict) { String rep = word.substring(0, i) + word.substring(i + 1); updateMap(rep2words, rep, word); } for (Map.Entry<String, List<String>> entry : rep2words.entrySet()) { List<String> words = entry.getValue(); for (String word1 : words) { for (String word2 : words) { if (!word1.equals(word2)) { updateMap(word2neighbors, word1, word2); } } } } } return word2neighbors; } public Map<String, List<String>> bfs(Map<String, List<String>> word2neighbors, String start, String end) { Set<String> searched = new HashSet<String>(); Map<String, List<String>> prewords = new HashMap<String, List<String>>(); Queue<String> q = new LinkedList<String>(); q.offer(end); searched.add(end); boolean find = false; while(!q.isEmpty()) { int size = q.size(); Set<String> nextLev = new HashSet<String>(); for (int i = 1; i <= size; i++) { String cur = q.poll(); List<String> neighbors = word2neighbors.get(cur); for (String neighbor : neighbors) { if (!searched.contains(neighbor)) { if (neighbor.equals(start)) { find = true; } if (!nextLev.contains(neighbor)) { q.offer(neighbor); nextLev.add(neighbor); } updateMap(prewords, neighbor, cur); } } } if (find) { break; } searched.addAll(nextLev); } return prewords; } public void dfs(List<List<String>> res, List<String> cur, Map<String, List<String>> prewords, String curstring, String end) { if (curstring.equals(end)) { List<String> tmp = new ArrayList<String>(cur); res.add(tmp); return; } List<String> words = prewords.get(curstring); for (String word : words) { cur.add(word); dfs(res, cur, prewords, word, end); cur.remove(cur.size() - 1); } } public void updateMap(Map<String, List<String>> map, String key, String value) { List<String> list = map.get(key); if (list == null) { list = new ArrayList<String>(); list.add(value); map.put(key, list); } else { list.add(value); } } }
思路2:(九章算法答案)
第一步:与思路1一样;
第二步:从start做bfs,求出所有节点到start的最短距离distance(相当于位于第几层)。
第三步:从end做dfs,每个节点只往它的邻节点中distance值小1的节点搜索。
public class Solution { public List<List<String>> findLadders(String start, String end, Set<String> dict) { List<List<String>> ladders = new ArrayList<List<String>>(); Map<String, List<String>> map = new HashMap<String, List<String>>(); Map<String, Integer> distance = new HashMap<String, Integer>(); dict.add(start); dict.add(end); bfs(map, distance, start, end, dict); List<String> path = new ArrayList<String>(); dfs(ladders, path, end, start, distance, map); return ladders; } void dfs(List<List<String>> ladders, List<String> path, String crt, String start, Map<String, Integer> distance, Map<String, List<String>> map) { path.add(crt); if (crt.equals(start)) { Collections.reverse(path); ladders.add(new ArrayList<String>(path)); Collections.reverse(path); } else { for (String next : map.get(crt)) { if (distance.containsKey(next) && distance.get(crt) == distance.get(next) + 1) { dfs(ladders, path, next, start, distance, map); } } } path.remove(path.size() - 1); } void bfs(Map<String, List<String>> map, Map<String, Integer> distance, String start, String end, Set<String> dict) { Queue<String> q = new LinkedList<String>(); q.offer(start); distance.put(start, 0); for (String s : dict) { map.put(s, new ArrayList<String>()); } while (!q.isEmpty()) { String crt = q.poll(); List<String> nextList = expand(crt, dict); for (String next : nextList) { map.get(next).add(crt); if (!distance.containsKey(next)) { distance.put(next, distance.get(crt) + 1); q.offer(next); } } } } List<String> expand(String crt, Set<String> dict) { List<String> expansion = new ArrayList<String>(); for (int i = 0; i < crt.length(); i++) { for (char ch = 'a'; ch <= 'z'; ch++) { if (ch != crt.charAt(i)) { String expanded = crt.substring(0, i) + ch + crt.substring(i + 1); if (dict.contains(expanded)) { expansion.add(expanded); } } } } return expansion; } }