[leetcode刷题]——搜索
此博客主要记录力扣中关于搜索的题解,包括 BFS、DFS、Backtracking
BFS
一、计算在网格中从原点到特定点的最短路径长度
1091. 二进制矩阵中的最短路径 (medium) 2021-07-12
给你一个 n x n 的二进制矩阵 grid 中,返回矩阵中最短 畅通路径 的长度。如果不存在这样的路径,返回 -1 。
二进制矩阵中的 畅通路径 是一条从 左上角 单元格(即,(0, 0))到 右下角 单元格(即,(n - 1, n - 1))的路径,该路径同时满足下述要求:
路径途经的所有单元格都的值都是 0 。
路径中所有相邻的单元格应当在 8 个方向之一 上连通(即,相邻两单元之间彼此不同且共享一条边或者一个角)。
畅通路径的长度 是该路径途经的单元格总数。
经典BFS 题目。
class Solution { public int shortestPathBinaryMatrix(int[][] grid) { if(grid == null || grid.length == 0 || grid[0].length == 0){ return -1; } if(grid[0][0] == 1) return -1; int[][] dir = {{1,1}, {1, 0}, {1, -1}, {0, 1}, {0, -1}, {-1, 1}, {-1, 0}, {-1, -1}}; int m = grid.length; int n = grid[0].length; Queue<int[]> queue = new LinkedList<>(); queue.add(new int[]{0, 0}); grid[0][0] = 1; //遍历一个节点就置 1 ,记为堵塞 int path = 1; //第一层 while(!queue.isEmpty()){ int size = queue.size(); while(size > 0){ int[] cur = queue.poll(); int x = cur[0]; int y = cur[1]; //结束遍历,返回层数 if(x == m - 1 && y == n - 1){ return path; } for(int[] d : dir){ int x1 = x + d[0]; int y1 = y + d[1]; if(x1 < 0 || x1 >= m || y1 >= m || y1 < 0 || grid[x1][y1] == 1){ continue; } queue.add(new int[]{x1, y1}); grid[x1][y1] = 1; } size--; } path++; } return -1; } }
二、组成整数的最小平方数数量
279. 完全平方数 (medium) 2021-07-12
给定正整数 n,找到若干个完全平方数(比如 1, 4, 9, 16, ...)使得它们的和等于 n。你需要让组成和的完全平方数的个数最少。
给你一个整数 n ,返回和为 n 的完全平方数的 最少数量 。
完全平方数 是一个整数,其值等于另一个整数的平方;换句话说,其值等于一个整数自乘的积。例如,1、4、9 和 16 都是完全平方数,而 3 和 11 不是。
这个题可以使用 BFS 求解,但不是最优解,此方法时间击败 9% 空间击败 4%
class Solution { public int numSquares(int n) { List<Integer> squares = generateSquare(n); Queue<Integer> queue = new LinkedList<>(); queue.add(n); int level = 0; while(!queue.isEmpty()){ int size = queue.size(); level++; while(size-- > 0){ int cur = queue.poll(); for(int s : squares){ int next = cur - s; if(next < 0) break; if(next == 0) return level; queue.add(next); } } } return n; } //这个函数返回小于 n 的所有完全平方数的和,n >= 1 //实现方法并没有让两数直接相乘,而是使用数学规律 public List<Integer> generateSquare(int n){ List<Integer> list = new ArrayList<>(); int increment = 1; int gap = 2; int i = 1; while(i <= n){ list.add(i); i = i + increment + gap; increment += gap; } return list; } }
三、 最短单词路径
127. 单词接龙 (hard) 2021-07-12
字典 wordList 中从单词 beginWord 和 endWord 的 转换序列 是一个按下述规格形成的序列:
序列中第一个单词是 beginWord 。
序列中最后一个单词是 endWord 。
每次转换只能改变一个字母。
转换过程中的中间单词必须是字典 wordList 中的单词。
给你两个单词 beginWord 和 endWord 和一个字典 wordList ,找到从 beginWord 到 endWord 的 最短转换序列 中的 单词数目 。如果不存在这样的转换序列,返回 0。
官方解题方法:先给每个单词标号,给每一个单词分配一个id 。创建一个由单词word 到 id 之间的映射 wordId, 并将beginWord 与 wordList 中所有的单词都加入到这个映射中。检查endWord是否在该映射内,如果不存在则输入无解。
建图的优化方法,创建虚拟节点。如 hit ,我们创建三个虚拟节点 *it , h*t, hi* ,让单词连接虚拟节点再连接单词。最后将路径长度除以 2 。
官方答案如下
class Solution { Map<String, Integer> wordId = new HashMap<String, Integer>(); List<List<Integer>> edge = new ArrayList<List<Integer>>(); int nodeNum = 0; public int ladderLength(String beginWord, String endWord, List<String> wordList) { for(String word : wordList){ addEdge(word); } addEdge(beginWord); if(!wordId.containsKey(endWord)){ return 0; } int[] dis = new int[nodeNum]; Arrays.fill(dis, Integer.MAX_VALUE); int beginId = wordId.get(beginWord); int endId = wordId.get(endWord); dis[beginId] = 0; Queue<Integer> que = new LinkedList<Integer>(); que.add(beginId); while(!que.isEmpty()){ int x = que.poll(); if(x == endId){ return dis[endId] / 2 + 1; } for(int it : edge.get(x)){ if(dis[it] == Integer.MAX_VALUE){ dis[it] = dis[x] + 1; que.add(it); } } } return 0; } public void addEdge(String word){ addWord(word); int id1 = wordId.get(word); char[] array = word.toCharArray(); int length = array.length; for(int i = 0; i < length; i++){ char tmp = array[i]; array[i] = '*'; String newWord = new String(array); addWord(newWord); int id2 = wordId.get(newWord); edge.get(id1).add(id2); edge.get(id2).add(id1); array[i] = tmp; } } public void addWord(String word){ if(!wordId.containsKey(word)){ wordId.put(word, nodeNum++); edge.add(new ArrayList<Integer>()); } } }
DFS
一、 查找最大的连通面积
695. 岛屿的最大面积 (medium ) 2021-07-13
给定一个包含了一些 0 和 1 的非空二维数组 grid 。
一个 岛屿 是由一些相邻的 1 (代表土地) 构成的组合,这里的「相邻」要求两个 1 必须在水平或者竖直方向上相邻。你可以假设 grid 的四个边缘都被 0(代表水)包围着。
找到给定的二维数组中最大的岛屿面积。(如果没有岛屿,则返回面积为 0 。)
class Solution { private int m, n; private int[][] direction = {{0, 1}, {0, - 1}, {1, 0}, {-1, 0}}; public int maxAreaOfIsland(int[][] grid) { if(grid == null || grid.length == 0){ return 0; } m = grid.length; n = grid[0].length; int maxArea = 0; for(int i = 0; i < m; i++){ for(int j = 0; j < n; j++){ maxArea = Math.max(maxArea, dfs(grid, i, j)); } } return maxArea; } private int dfs(int[][] grid, int r, int c){ if(r < 0 || r >= m || c < 0 || c >= n || grid[r][c] == 0){ return 0; } grid[r][c] = 0; int area = 1; for(int[] d : direction){ area = area + dfs(grid, r + d[0], c + d[1]); } return area; } }
二、矩阵中的连通分量数目
200. 岛屿数量 (medium) 2021-07-13
给你一个由 '1'(陆地)和 '0'(水)组成的的二维网格,请你计算网格中岛屿的数量。
岛屿总是被水包围,并且每座岛屿只能由水平方向和/或竖直方向上相邻的陆地连接形成。
此外,你可以假设该网格的四条边均被水包围。
class Solution { private int[][] direction = {{1, 0}, {-1, 0}, {0, 1}, {0, -1}}; private int m; private int n; public int numIslands(char[][] grid) { m = grid.length; n = grid[0].length; int count = 0; for(int i = 0; i < m; i++){ for(int j = 0; j < n; j++){ if(dfs(grid, i, j)){ count++; } } } return count; } private boolean dfs(char[][] grid, int r, int c){ if(r < 0 || r >= m || c < 0 || c >= n || grid[r][c] == '0'){ return false; } grid[r][c] = '0'; //将遍历到的这个节点置为0 for(int[] dir : direction){ dfs(grid, r + dir[0] , c + dir[1]); } return true; } }
三、好友关系的连通分量数目
547. 省份数量 (medium) 2021-07-14
有 n 个城市,其中一些彼此相连,另一些没有相连。如果城市 a 与城市 b 直接相连,且城市 b 与城市 c 直接相连,那么城市 a 与城市 c 间接相连。
省份 是一组直接或间接相连的城市,组内不含其他没有相连的城市。
给你一个 n x n 的矩阵 isConnected ,其中 isConnected[i][j] = 1 表示第 i 个城市和第 j 个城市直接相连,而 isConnected[i][j] = 0 表示二者不直接相连。
返回矩阵中 省份 的数量。
这个题和上面的岛屿相通有点相似但是不尽相同,这个矩阵表示的不是物理连通关系,而是逻辑上的连通。
class Solution { private boolean[] isVisted; public int findCircleNum(int[][] isConnected) { int n = isConnected.length; isVisted = new boolean[n]; int num = 0; for(int j = 0; j < n; j++){ if(dfs(isConnected, j, isVisted)){ num++; } } return num; } public boolean dfs(int[][] isConnected, int i, boolean[] isVisted){ int n = isConnected.length; if(i < 0 || i >= n || isVisted[i] == true){ return false; } isVisted[i] = true; for(int k = 0; k < n; k++){ if(isConnected[i][k] == 1){ dfs(isConnected, k, isVisted); } } return true; } }
四、 填充封闭区域
130. 被围绕的区域 (medium)2021-07-14
给你一个
m x n的矩阵board,由若干字符'X'和'O',找到所有被'X'围绕的区域,并将这些区域里所有的'O'用'X'填充。
class Solution { private boolean[][] isVisted; private int m; private int n; private int[][] direction = {{0, 1}, {0, -1}, {1, 0}, {-1, 0}}; public void solve(char[][] board) { m = board.length; n = board[0].length; isVisted = new boolean[m][n]; for(int i = 0; i < m; i++){ dfs(board, i, 0); dfs(board, i, n - 1); } for(int j = 0; j < n; j++){ dfs(board, 0, j); dfs(board, m - 1, j); } for(int i = 0; i < m; i++){ for(int j = 0; j < n; j++){ if(board[i][j] == 'O' && !isVisted[i][j]){ board[i][j] = 'X'; } } } return; } public void dfs(char[][] board, int r, int c){ if(r < 0 || r >= m || c < 0 || c >= n || board[r][c] == 'X' || isVisted[r][c] == true){ return; } isVisted[r][c] = true; for(int[] dir : direction){ dfs(board, r + dir[0], c + dir[1]); } return; } }
五、 能到达的太平洋和大西洋的区域
417. 太平洋大西洋水的问题 (medium) 2021-07-14
给定一个 m x n 的非负整数矩阵来表示一片大陆上各个单元格的高度。“太平洋”处于大陆的左边界和上边界,而“大西洋”处于大陆的右边界和下边界。
规定水流只能按照上、下、左、右四个方向流动,且只能从高到低或者在同等高度上流动。
请找出那些水流既可以流动到“太平洋”,又能流动到“大西洋”的陆地单元的坐标。
这个题,使用dfs 的思路没错。但是算法思想的出发点,我初始的想法是遍历所有节点,使其向高度小的地方遍历,直至边缘,这种方法过于复杂.
class Solution { private int m, n; private int[][] direction = {{0, 1}, {0, -1}, {1, 0}, {-1, 0}}; public List<List<Integer>> pacificAtlantic(int[][] heights) { List<List<Integer>> ret = new ArrayList<>(); if(heights == null || heights.length == 0){ return ret; } m = heights.length; n = heights[0].length; boolean[][] canReachP = new boolean[m][n]; boolean[][] canReachA = new boolean[m][n]; for(int i = 0; i < m; i++){ dfs(heights, i, 0, canReachP); dfs(heights, i, n - 1, canReachA); } for(int i = 0; i < n; i++){ dfs(heights, 0, i, canReachP); dfs(heights, m - 1, i, canReachA); } for(int i = 0; i < m; i++){ for(int j = 0; j < n; j++){ if(canReachA[i][j] && canReachP[i][j]){ ret.add(Arrays.asList(i, j)); } } } return ret; } private void dfs(int[][] heights, int r, int c, boolean[][] canReach){ if(canReach[r][c]) return; canReach[r][c] = true; for(int[] d : direction){ int nextR = d[0] + r; int nextC = d[1] + c; if(nextR < 0 || nextR >= m || nextC < 0 || nextC >= n || heights[r][c] > heights[nextR][nextC]){ continue; } dfs(heights, nextR, nextC, canReach); } } }
Backtracking
一、 数字键盘组合
17. 电话号码的字母组合 (medium) 2021-07-14
给定一个仅包含数字 2-9 的字符串,返回所有它能表示的字母组合。答案可以按 任意顺序 返回。
给出数字到字母的映射如下(与电话按键相同)。注意 1 不对应任何字母。
回溯的经典题目,需要多加练习。
private static final String[] KEYS = {"", "", "abc", "def", "ghi", "jkl", "mno", "pqrs", "tuv", "wxyz"}; public List<String> letterCombinations(String digits) { List<String> combinations = new ArrayList<>(); if (digits == null || digits.length() == 0) { return combinations; } doCombination(new StringBuilder(), combinations, digits); return combinations; } private void doCombination(StringBuilder prefix, List<String> combinations, final String digits) { if (prefix.length() == digits.length()) { combinations.add(prefix.toString()); return; } int curDigits = digits.charAt(prefix.length()) - '0'; String letters = KEYS[curDigits]; for (char c : letters.toCharArray()) { prefix.append(c); // 添加 doCombination(prefix, combinations, digits); prefix.deleteCharAt(prefix.length() - 1); // 删除 } }
二、Ip地址划分
93.复原IP地址 (medium)2021-07-15
给定一个只包含数字的字符串,用以表示一个 IP 地址,返回所有可能从 s 获得的 有效 IP 地址 。你可以按任何顺序返回答案。
有效 IP 地址 正好由四个整数(每个整数位于 0 到 255 之间组成,且不能含有前导 0),整数之间用 '.' 分隔。
例如:"0.1.2.201" 和 "192.168.1.1" 是 有效 IP 地址,但是 "0.011.255.245"、"192.168.1.312" 和 "192.168@1.1" 是 无效 IP 地址。
回溯题,使用常规套路解题
class Solution { public List<String> restoreIpAddresses(String s) { List<String> retList = new ArrayList<>(); if(s.length() < 4 || s.length() > 12) return retList; restoreIp(s, retList, new ArrayList<Integer>(), 0); return retList; } public void restoreIp(String s, List<String> list, List<Integer> prefix, int idx){ if(prefix.size() == 4 && idx == s.length()){ addIp(list, prefix); //将整理好的ip地址放进列表 } for(int i = 1; i <=3; i++){ if(idx + i > s.length()) break; String substr = s.substring(idx, idx + i); //左闭右开的区间 if(substr.length() > 1 && substr.charAt(0) == '0') break; int num = Integer.valueOf(substr); if(num > 255) break; prefix.add(num); restoreIp(s, list, prefix, idx + i); prefix.remove(prefix.size() - 1); } } public void addIp(List<String> list, List<Integer> prefix){ StringBuffer strb = new StringBuffer(); for(int pre : prefix){ strb.append(pre + "."); } strb.deleteCharAt(strb.length() - 1); list.add(strb.toString()); } }
三、在矩阵中寻找字符串
79. 单词搜索 (medium) 2021-07-19
给定一个 m x n 二维字符网格 board 和一个字符串单词 word 。如果 word 存在于网格中,返回 true ;否则,返回 false 。
单词必须按照字母顺序,通过相邻的单元格内的字母构成,其中“相邻”单元格是那些水平相邻或垂直相邻的单元格。同一个单元格内的字母不允许被重复使用。
剑指offer上的原题,写了第二遍,居然又写了bug。
class Solution { private int m, n; private boolean[][] isVisted; private int[][] directions = {{0, 1}, {0, -1}, {1, 0}, {-1, 0}}; public boolean exist(char[][] board, String word) { if(board.length == 0) return false; m = board.length; n = board[0].length; isVisted = new boolean[m][n]; for(int i = 0; i < m; i++){ for(int j = 0; j < n; j++){ if(search(board, word, 0, i, j)){ return true; } } } return false; } private boolean search(char[][] board, String word,int index, int r, int c){ if(r < 0 || r>=m||c<0||c>=n|| index>=word.length()||isVisted[r][c]|| board[r][c] != word.charAt(index)) { return false; } if(board[r][c] == word.charAt(index)){ index++; isVisted[r][c] = true; } if(index == word.length()){ return true; } for(int[] dir : directions){ if(search(board, word, index, r + dir[0], c + dir[1])){ return true; } } isVisted[r][c] = false; return false; } }
四、输出二叉树中所有根到叶子的路径
257. 二叉树的所有路径 (easy) 2021-07-19
给定一个二叉树,返回所有从根节点到叶子节点的路径。
说明: 叶子节点是指没有子节点的节点。
这个题是我看完答案后得到的启发,一直在 Sting 和StringBuffer 之间进行切换操作。
我之前的代码是直接使用StringBuffer 进行传递,但是它只会不停的增加长度。
class Solution { public List<String> binaryTreePaths(TreeNode root) { List<String> ret = new ArrayList<>(); if(root == null) return ret; search(root, ret, ""); return ret; } private void search(TreeNode node, List<String> list, String prefix){ if(node == null) return; StringBuffer prefixSB = new StringBuffer(prefix); if(node.left == null && node.right == null){ prefixSB.append(node.val+""); list.add(prefixSB.toString()); return; } prefixSB.append(node.val +"->"); search(node.left, list, prefixSB.toString()); search(node.right, list, prefixSB.toString()); } }
又把代码改了改, 下面这个代码比较符合我的代码习惯
class Solution { public List<String> binaryTreePaths(TreeNode root) { List<String> list = new ArrayList<>(); if(root == null) return list; search(root, list, new ArrayList<Integer>()); return list; } public void search(TreeNode node, List<String> list, List<Integer> prefix){ if(node == null) return; if(node.left == null && node.right == null) { prefix.add(node.val); doRoad(prefix, list); }else{ prefix.add(node.val); search(node.left, list, prefix); search(node.right, list, prefix); } prefix.remove(prefix.size() - 1); return; } public void doRoad(List<Integer> prefix, List<String> list){ StringBuffer strb = new StringBuffer(); for(Integer pre : prefix){ strb.append(pre + "->"); } strb.deleteCharAt(strb.length() - 1); strb.deleteCharAt(strb.length() - 1); list.add(strb.toString()); } }
五、排列
46. 全排列 (medium) 2021-07-20
给定一个不含重复数字的数组
nums,返回其 所有可能的全排列 。你可以 按任意顺序 返回答案。
在循环那个地方,逻辑上有点难理解。
需要注意的是 permutes.add(new ArrayList(permuteList)), 需要新建一个新 ArrayList 。
class Solution { public List<List<Integer>> permute(int[] nums) { List<List<Integer>> permutes = new ArrayList<>(); List<Integer> permuteList = new ArrayList<>(); boolean[] isVisted = new boolean[nums.length]; searchPermute(permutes, permuteList, isVisted, nums); return permutes; } public void searchPermute(List<List<Integer>> permutes, List<Integer> permuteList,boolean[] isVisted,int[] nums){ if(permuteList.size() == nums.length){ permutes.add(new ArrayList(permuteList)); } for(int i = 0; i < nums.length; i++){ if(isVisted[i]) continue; permuteList.add(nums[i]); isVisted[i] = true; searchPermute(permutes, permuteList, isVisted, nums); isVisted[i] = false; permuteList.remove(permuteList.size() - 1); } return; } }
六、含有相同元素求排列
47. 全排列Ⅱ (medium)2021-07-20
给定一个可包含重复数字的序列
nums,按任意顺序 返回所有不重复的全排列。
第一个想法当然是魔改上一题, 然后时间击败 5% ,哈哈。
class Solution { public List<List<Integer>> permuteUnique(int[] nums) { List<List<Integer>> permutes = new ArrayList<>(); List<Integer> permuteList = new ArrayList<>(); boolean[] isVisted = new boolean[nums.length]; searchPermute(permutes, permuteList, isVisted, nums); return permutes; } public void searchPermute(List<List<Integer>> permutes, List<Integer> permuteList,boolean[] isVisted,int[] nums){ if(permuteList.size() == nums.length){ if(!permutes.contains(permuteList)){ permutes.add(new ArrayList(permuteList)); } } for(int i = 0; i < nums.length; i++){ if(isVisted[i]) continue; permuteList.add(nums[i]); isVisted[i] = true; searchPermute(permutes, permuteList, isVisted, nums); isVisted[i] = false; permuteList.remove(permuteList.size() - 1); } return; } }
list.contains()进行判断的时间复杂度太高了。
大佬的做法,先将nums 数组进行排序,然后在添加排列的时候进行重复的判断。
class Solution { public List<List<Integer>> permuteUnique(int[] nums) { List<List<Integer>> permutes = new ArrayList<>(); List<Integer> permuteList = new ArrayList<>(); boolean[] isVisted = new boolean[nums.length]; Arrays.sort(nums); searchPermute(permutes, permuteList, isVisted, nums); return permutes; } public void searchPermute(List<List<Integer>> permutes, List<Integer> permuteList,boolean[] isVisted,int[] nums){ if(permuteList.size() == nums.length){ permutes.add(new ArrayList(permuteList)); } for(int i = 0; i < nums.length; i++){ if (i != 0 && nums[i] == nums[i - 1] && !isVisted[i - 1]) { continue; // 防止重复 } if(isVisted[i]) continue; permuteList.add(nums[i]); isVisted[i] = true; searchPermute(permutes, permuteList, isVisted, nums); isVisted[i] = false; permuteList.remove(permuteList.size() - 1); } return; } }
七、 组合
77. 组合 (medium) 2021-07-20
给定两个整数
n和k,返回范围[1, n]中所有可能的k个数的组合。你可以按 任何顺序 返回答案。
上述题目都是排列问题,这个题是组合问题。
解题方法大致相同,需要多增加一个参数,避免重复遍历
class Solution { public List<List<Integer>> combine(int n, int k) { List<List<Integer>> combines = new ArrayList<>(); List<Integer> combineList = new ArrayList<>(); searchCombine(combines, combineList, n, k,1); return combines; } public void searchCombine(List<List<Integer>> combines,List<Integer> combineList,int n, int k, int start){ if(combineList.size() == k){ combines.add(new ArrayList(combineList)); } for(int i = start; i <= n; i++){ combineList.add(i); searchCombine(combines, combineList, n, k, start + 1); combineList.remove(combineList.size() - 1); start ++; } return; } }
八、组合求和
39. 组合求和 (medium) 2021-07-21
给定一个无重复元素的正整数数组 candidates 和一个正整数 target ,找出 candidates 中所有可以使数字和为目标数 target 的唯一组合。
candidates 中的数字可以无限制重复被选取。如果至少一个所选数字数量不同,则两种组合是唯一的。
对于给定的输入,保证和为 target 的唯一组合数少于 150 个。
组合问题,我的常见思路,时间超过 9%的用户, 哈哈。
class Solution { public List<List<Integer>> combinationSum(int[] candidates, int target) { List<List<Integer>> ret = new ArrayList<>(); List<Integer> list = new ArrayList<>(); search(candidates, target, ret, list, 0); return ret; } public void search(int[] candidates, int target, List<List<Integer>> ret, List<Integer> list, int start){ if(getSum(list) > target){ return; } if(getSum(list) == target){ ret.add(new ArrayList(list)); } for(int i = start; i < candidates.length; i++){ list.add(candidates[i]); search(candidates, target, ret, list, i); list.remove(list.size() - 1); } } public int getSum(List<Integer> list){ int sum = 0; for(int l : list){ sum += l; } return sum; } }
使用getSum (list) 太消耗时间了, 每一次遍历将target 减去 数组中的值, 优化程序。 超过 77% 的用户,yes
class Solution { public List<List<Integer>> combinationSum(int[] candidates, int target) { List<List<Integer>> ret = new ArrayList<>(); List<Integer> list = new ArrayList<>(); search(candidates, target, ret, list, 0); return ret; } public void search(int[] candidates, int target, List<List<Integer>> ret, List<Integer> list, int start){ if(target < 0){ return; } if(target == 0){ ret.add(new ArrayList(list)); } for(int i = start; i < candidates.length; i++){ list.add(candidates[i]); search(candidates, target - candidates[i], ret, list, i); list.remove(list.size() - 1); } } }
九、含有相同元素的组合求和
40. 组合求和 Ⅱ (medium) 2021-07-21
给定一个数组 candidates 和一个目标数 target ,找出 candidates 中所有可以使数字和为 target 的组合。
candidates 中的每个数字在每个组合中只能使用一次。
这个题是上个题的变形,数组中有重复,但是组合中每个数只能用一次。
最重要的一段代码是这个。
for(int i = start; i < candidates.length; i++){
if (i != 0 && candidates[i] == candidates[i - 1] && !isVisited[i - 1]) {
continue;
}
class Solution { public List<List<Integer>> combinationSum2(int[] candidates, int target) { List<List<Integer>> ret = new ArrayList<>(); List<Integer> list = new ArrayList<>(); Arrays.sort(candidates); boolean[] isVisited = new boolean[candidates.length]; search(ret, list, candidates, target, 0, isVisited); return ret; } public void search(List<List<Integer>> ret, List<Integer> list, int[] candidates, int target, int start, boolean[] isVisited){ if(target < 0) return; if(target == 0) { ret.add(new ArrayList(list)); return; } for(int i = start; i < candidates.length; i++){ if (i != 0 && candidates[i] == candidates[i - 1] && !isVisited[i - 1]) { continue; } //if(isVisited[i]) return; isVisited[i] = true; list.add(candidates[i]); search(ret, list, candidates, target - candidates[i], i+1, isVisited); isVisited[i] = false; list.remove(list.size() - 1); } } }
十、1-9 数字的组合求和
216. 组合求和Ⅲ (medium) 2021-07-21
找出所有相加之和为 n 的 k 个数的组合。组合中只允许含有 1 - 9 的正整数,并且每种组合中不存在重复的数字。
说明:
所有数字都是正整数。
解集不能包含重复的组合。
超过 100% 用户,开心
class Solution { public static int[] nums = {1,2,3,4,5,6,7,8,9}; public List<List<Integer>> combinationSum3(int k, int n) { List<List<Integer>> ret = new ArrayList<>(); List<Integer> list = new ArrayList<>(); search(ret, list, k, n, 0); return ret; } public void search(List<List<Integer>> ret, List<Integer> list, int k, int n, int start){ if(n < 0 || k < 0) return; if(n == 0 && k == 0) ret.add(new ArrayList(list)); for(int i = start; i < nums.length; i++){ list.add(nums[i]); k--; search(ret, list, k, n - nums[i], i + 1); list.remove(list.size() - 1); k++; } } }
十一、子集
78. 子集 (medium) 2021-07-21
给你一个整数数组
nums,数组中的元素 互不相同 。返回该数组所有可能的子集(幂集)。解集 不能 包含重复的子集。你可以按 任意顺序 返回解集。
又一个超 100%用户的答案
class Solution { public List<List<Integer>> subsets(int[] nums) { List<List<Integer>> ret = new ArrayList<>(); ret.add(new ArrayList()); for(int i = 1; i <= nums.length; i++){ search(nums, ret, new ArrayList(), i,0); } return ret; } public void search(int[] nums, List<List<Integer>> ret, List<Integer> list, int k, int start){ if(list.size() > k) return; if(list.size() == k) ret.add(new ArrayList(list)); for(int i = start; i < nums.length; i++){ list.add(nums[i]); search(nums, ret, list, k, i + 1); list.remove(list.size() - 1); } } }
十二、含有相同元素求子集
90. 子集Ⅱ (medium) 2021-07-21
给你一个整数数组 nums ,其中可能包含重复元素,请你返回该数组所有可能的子集(幂集)。
解集 不能 包含重复的子集。返回的解集中,子集可以按 任意顺序 排列。
超过 99.9% 的用户
class Solution { public List<List<Integer>> subsetsWithDup(int[] nums) { List<List<Integer>> ret = new ArrayList<>(); List<Integer> list = new ArrayList<>(); boolean[] hasVisited = new boolean[nums.length]; Arrays.sort(nums); for(int i = 0; i <= nums.length; i++){ search(nums, ret, list, i, 0, hasVisited); } return ret; } public void search(int[] nums,List<List<Integer>> ret, List<Integer> list, int k, int start, boolean[] hasVisited){ if(list.size() == k){ ret.add(new ArrayList(list)); return; } for(int i = start; i < nums.length; i++){ if(i != 0 && nums[i] == nums[i - 1] && !hasVisited[i - 1]){ continue; } list.add(nums[i]); hasVisited[i] = true; search(nums, ret, list, k, i + 1, hasVisited); list.remove(list.size() - 1); hasVisited[i] = false; } } }
十三、分割字符串使得每个字符串都是回文数
131. 分割回文数 (medium) 2021-07-23
给你一个字符串
s,请你将s分割成一些子串,使每个子串都是 回文串 。返回s所有可能的分割方案。回文串 是正着读和反着读都一样的字符串。
class Solution { public List<List<String>> partition(String s) { List<List<String>> ret = new ArrayList<>(); List<String> list = new ArrayList<>(); search(s, ret, list, 0); return ret; } public void search(String s, List<List<String>> ret, List<String> list,int start){ int len = s.length(); if(start > len) return; if(start == len){ ret.add(new ArrayList(list)); } for(int i = start; i <= len; i++){ if(!isPalindrome(s.substring(start, i))) continue; //左闭右开 list.add(s.substring(start, i)); search(s, ret, list, i); list.remove(list.size() - 1); } return; } public boolean isPalindrome(String s){ char front; char back; int len = s.length(); if(len == 0) return false; if(len == 1) return true; for(int i = 0; i < len / 2; i++){ front = s.charAt(i); back = s.charAt(len - 1 - i); if(front != back) return false; } return true; } }
十四、数独
37. 解数独 (hard) 2021-07-23
编写一个程序,通过填充空格来解决数独问题。
数独的解法需 遵循如下规则:
数字 1-9 在每一行只能出现一次。
数字 1-9 在每一列只能出现一次。
数字 1-9 在每一个以粗实线分隔的 3x3 宫内只能出现一次。(请参考示例图)
数独部分空格内已填入了数字,空白格用 '.' 表示。

这个题是个 hard 题, 但是思路并不但理解。难顶的是我写了两天的bug 没改出来,贴个答案,思路相同。
我的回溯函数定义为 void ,他的是Boolean 类型,我的就始终报错。
后面需要多练习几次
class Solution { public void solveSudoku(char[][] board) { if(board==null||board.length<=0||board[0].length<=0) return; char[] nums = {'1','2','3','4','5','6','7','8','9'}; dfs(board, 0, 0, nums); return; } boolean dfs(char[][] board,int x,int y,char[] nums){ if(y>=board[0].length) return dfs(board,x+1,0,nums); if(x>=board.length) return true; if(board[x][y]!='.') return dfs(board,x,y+1,nums); for(char num:nums){ if(check(board,x,y,num)){ board[x][y]=num;//填入 if(dfs(board,x,y+1,nums)){ return true; }else{//回溯 board[x][y]='.'; } } } return false; } boolean check(char[][] board,int x,int y,char c){ for(int i=0;i<board.length;i++){//横竖 if(board[x][i]==c||board[i][y]==c) return false; } for(int i=x/3*3;i<x/3*3+3;i++){//找到九宫格左上元素,依次遍历 for(int j=y/3*3;j<y/3*3+3;j++){ if(board[i][j]==c) return false; } } return true; } }
下面是我独立写出来的,用的是我的惯性解题思路,需要注意的一点是。这个题和之前排列组合的题区别在于他只有一个结果。我错误判断找到正确答案后会返回,事实上使用回溯算法是会找出所有解,也就是会遍历所有方法。所以,这个题需要像排列组合题一样,设置一个空数组,当找到正确答案后放置进去。
十五、 N 皇后
51. N 皇后 (hard) 2021-07-24
n 皇后问题 研究的是如何将 n 个皇后放置在 n×n 的棋盘上,并且使皇后彼此之间不能相互攻击。
给你一个整数 n ,返回所有不同的 n 皇后问题 的解决方案。
每一种解法包含一个不同的 n 皇后问题 的棋子放置方案,该方案中 'Q' 和 '.' 分别代表了皇后和空位。
虽然时间击败 5% ,但是没参考任何答案按照自己的解题套路独自完成的。
class Solution { public List<List<String>> solveNQueens(int n) { List<List<String>> ret= new ArrayList<>(); char[][] chessBoard = new char[n][n]; for(int i = 0; i < n; i++){ for(int j = 0; j < n; j++){ chessBoard[i][j] = '.'; } } boolean[][] road = new boolean[n][n]; //false代表可通行 search(ret, chessBoard, road, n, 0, 0, 0); return ret; } public void search(List<List<String>> ret, char[][] chessBoard,boolean[][] road, int n, int count, int start_x, int start_y){ if(count == n){ List<String> list = new ArrayList<>(); for(int i = 0; i < n; i++){ String s = String.valueOf(chessBoard[i]); list.add(s); } ret.add(list); return; } if(start_x >= n) return ; while(road[start_x][start_y] == true){ if(start_y < n - 1){ start_y++; }else{ start_x++; start_y = 0; } if(start_x >= n) return; } for(int i = start_x; i < n; i++){ for(int j = start_y; j < n; j++){ if(road[i][j] == true) continue; if(road[i][j] == false){ chessBoard[i][j] = 'Q'; } count++; refresh(n, chessBoard, road); search(ret, chessBoard, road, n, count, i + 1, 0); count--; chessBoard[i][j] = '.'; refresh(n, chessBoard, road); } } return; } public void refresh(int n, char[][] chessBoard, boolean[][] road){ for(int i = 0; i < n; i++){ for(int j = 0; j < n; j++){ road[i][j] = false; } } //重置,不能直接new一个新的 for(int i = 0; i < n; i++){ for(int j = 0; j < n; j++){ if(chessBoard[i][j] == 'Q'){ for(int k = 0; k < n; k++){ road[k][j] = true; road[i][k] = true; //行和列 } int x1 = i, x2 = i, x3 = i, x4 = i; int y1 = j, y2 = j, y3 = j, y4 = j; while(0 <= x1 && x1 < n && 0 <= y1 && y1 < n){ road[x1--][y1--] = true; } while(0 <= x2 && x2 < n && 0 <= y2 && y2 < n){ road[x2--][y2++] = true; } while(0 <= x3 && x3 < n && 0 <= y3 && y3 < n){ road[x3++][y3--] = true; } while(0 <= x4 && x4 < n && 0 <= y4 && y4 < n){ road[x4++][y4++] = true; } } } } } }

浙公网安备 33010602011771号