回溯算法

这部分主要是学习了 labuladong 公众号中对于回溯算法的讲解

刷了些 leetcode 题,在此做一些记录,不然没几天就忘光光了

总结

概述

回溯是 DFS 中的一种技巧。回溯法采用 试错 的思想,尝试分步的去解决一个问题,在分步解决问题的过程中,当它通过尝试发现现有的分步答案不能得到有效的正确的解答的时候,它将取消上一步甚至是上几步的计算,再通过其它的可能的分步解答再次尝试寻找问题的答案

通俗上讲,回溯是一种走不通就回头的算法。

摘录自:https://leetcode-solution-leetcode-pp.gitbook.io/leetcode-solution/thinkings/backtrack

在进行回溯时,需要考虑3个问题

  1. 路径:也就是已经做出的选择。
  2. 选择列表:也就是你当前可以做的选择。
  3. 结束条件:也就是到达决策树底层,无法再做选择的条件。

算法框架如下:

  1. List<List<...>> res = new LinkedList<>();
  2. // List<...> res = new LinkedList<>();
  3. void backtrack(路径, 选择列表){
  4. if(满足结束条件){
  5. res.add(路径);
  6. return;
  7. }
  8. for(选择 :选择列表){
  9. // 做选择
  10. backtrack(路径, 选择列表)
  11. // 撤销选择
  12. }
  13. }

特殊说明

在撤销选择时,若是如下定义:List<List<Integer>> res = new LinkedList<>();List类若要移除最后一个元素:

  • 方式1:track.remove(track.size() - 1);,推荐,因为选择列表出现重复的元素时,方式2就会有问题!
  • 方式2:track.remove(Integer.valueOf(nums[i]));,直接remove(i)是不对的,因为 i 为 index 而不是添加的元素

经典问题

全排列问题

问题描述:有n个不重复的数,能排列出多少种n位数的可能,其中用过的数字是不能再用的

  1. List<List<Integer>> res = new LinkedList<>();
  2. /* 主函数,输入一组不重复的数字,返回它们的全排列 */
  3. List<List<Integer>> permute(int[] nums) {
  4. // 记录「路径」
  5. LinkedList<Integer> track = new LinkedList<>();
  6. // 输入:选择列表, 路径
  7. backtrack(nums, track);
  8. return res;
  9. }
  10. // 路径:记录在 track 中
  11. // 选择列表:nums 中不存在于 track 的那些元素
  12. // 结束条件:nums 中的元素全都在 track 中出现
  13. void backtrack(int[] nums, LinkedList<Integer> track) {
  14. // 触发结束条件
  15. if (track.size() == nums.length) {
  16. res.add(new LinkedList(track));
  17. return;
  18. }
  19. for (int i = 0; i < nums.length; i++) {
  20. // 排除不合法的选择
  21. if (track.contains(nums[i]))
  22. continue;
  23. // 前序:做选择
  24. track.add(nums[i]);
  25. // 进入下一层决策树
  26. backtrack(nums, track);
  27. // 后序:取消选择
  28. track.removeLast();
  29. // 注意:若要用reomve的话,track.remove(i)是错误的,应该是track.remove(Integer.valueOf(i));
  30. }
  31. }
  1. public static void main(String[] args) {
  2. int[] nums = {1,2,3};
  3. Demo demo = new Demo();
  4. demo.permute(nums);
  5. System.out.println(demo.res.toString());
  6. // [[1, 2, 3], [1, 3, 2], [2, 1, 3], [2, 3, 1], [3, 1, 2], [3, 2, 1]]
  7. }

八皇后问题

题目描述:51. N 皇后

输入:4

输出:[[".Q..","...Q","Q...","..Q."],["..Q.","Q...","...Q",".Q.."]]

  1. List<List<String>> res = new LinkedList<>();
  2. /* 输入棋盘边长 n,返回所有合法的放置 */
  3. public List<List<String>> solveNQueens(int n) {
  4. // '.' 表示空,'Q' 表示皇后,初始化空棋盘。
  5. List<char[]> board = new LinkedList<>();
  6. // 初始化路径
  7. for(int i=0; i<n; i++){
  8. char[] arr = new char[n];
  9. Arrays.fill(arr, '.');
  10. board.add(arr);
  11. }
  12. backtrack(board, 0);
  13. return res;
  14. }
  15. // 路径:board 中小于 row 的那些行都已经成功放置了皇后
  16. // 选择列表:第 row 行的所有列都是放置皇后的选择
  17. // 结束条件:row 超过 board 的最后一行
  18. private void backtrack(List<char[]> board, int row){
  19. // 触发结束条件
  20. if(board.size() == row){
  21. res.add(transform(board));
  22. return;
  23. }
  24. // 第 row 行的所有列都是放置皇后的选择
  25. int n = board.get(row).length;
  26. for(int col=0; col<n; col++){
  27. // 排除不合法选择
  28. if(!isValid(board, row, col)){
  29. continue;
  30. }
  31. // 做选择
  32. board.get(row)[col] = 'Q';
  33. // 进入下一行决策,即row行已经放好皇后了,要在row+1行放
  34. backtrack(board, row+1);
  35. // 撤销选择
  36. board.get(row)[col] = '.';
  37. }
  38. }
  39. private Boolean isValid(List<char[]> board, int row, int col) {
  40. int n = board.size();
  41. // 遍历所有行,固定列:检查列是否有皇后互相冲突
  42. for(int i = 0; i < n; i++) {
  43. if(board.get(i)[col] == 'Q'){
  44. return false;
  45. }
  46. }
  47. // 检查右上方是否有皇后互相冲突,因为是一行一行放皇后因此只要检查上方
  48. for(int i = row - 1, j = col + 1; i >= 0 && j < n; i--,j++){
  49. if(board.get(i)[j] == 'Q'){
  50. return false;
  51. }
  52. }
  53. // 检查左上方是否有皇后互相冲突,因为是一行一行放皇后因此只要检查上方
  54. for(int i = row - 1, j = col - 1; i >= 0 && j >= 0; i--, j--){
  55. if(board.get(i)[j] == 'Q'){
  56. return false;
  57. }
  58. }
  59. return true;
  60. }
  61. private List<String> transform(List<char[]> board){
  62. List<String> newBoard = new LinkedList<>();
  63. for(char[] row : board){
  64. newBoard.add(new String(row));
  65. }
  66. return newBoard;
  67. }

子集问题

题目描述:78. 子集

  1. // 数学归纳思想
  2. public List<List<Integer>> subsets(int[] nums) {
  3. List<List<Integer>> res = new LinkedList<>();
  4. // 先加入[]的情况
  5. res.add(new LinkedList<Integer>());
  6. for(int i=0; i<nums.length; i++){
  7. // 每多一个数字,都在原先的基础上添加这个数字再加到res中
  8. int size = res.size();
  9. for(int j=0; j<size; j++){
  10. List<Integer> sub = new LinkedList<>(res.get(j));
  11. sub.add(nums[i]);
  12. res.add(sub);
  13. }
  14. }
  15. return res;
  16. }
  17. // 回溯解法
  18. List<List<Integer>> res = new LinkedList<>();
  19. public List<List<Integer>> subsets(int[] nums) {
  20. List<Integer> track = new LinkedList<>();;
  21. backtrack(nums, 0, track);
  22. return res;
  23. }
  24. private void backtrack(int[] nums, int start, List<Integer> track){
  25. res.add(new LinkedList(track));
  26. // 这里的i=start做了可选集和的限定
  27. for(int i=start; i<nums.length; i++){
  28. // 做选择
  29. track.add(nums[i]);
  30. // 回溯
  31. backtrack(nums, i+1, track);
  32. // 撤销选择
  33. track.remove(Integer.valueOf(nums[i]));
  34. }
  35. }

组合问题

题目描述:77. 组合

  1. List<List<Integer>> res = new LinkedList<>();
  2. public List<List<Integer>> combine(int n, int k) {
  3. List<Integer> track = new LinkedList<>();
  4. // k限定了尺寸,start限定了选择列表
  5. backtrack(n, k, 1, track);
  6. return res;
  7. }
  8. private void backtrack(int n, int k, int start, List<Integer> track){
  9. // 剪枝:可有可无,加上后效率提升很多
  10. if(track.size() + n-start + 1 < k){
  11. return;
  12. }
  13. if(k == track.size()){
  14. res.add(new LinkedList(track));
  15. return;
  16. }
  17. for(int i=start; i<=n; i++){
  18. track.add(i);
  19. backtrack(n, k, i+1, track);
  20. track.remove(Integer.valueOf(i));
  21. }
  22. }

排列问题

问题描述:46. 全排列,和之前的全排列一样,再贴一个代码吧

  1. List<List<Integer>> res = new LinkedList<>();
  2. public List<List<Integer>> permute(int[] nums) {
  3. List<Integer> track = new LinkedList<>();
  4. backtrack(nums, track);
  5. return res;
  6. }
  7. private void backtrack(int[] nums, List<Integer> track){
  8. if(nums.length == track.size()){
  9. res.add(new LinkedList(track));
  10. return;
  11. }
  12. for(int i=0; i<nums.length; i++){
  13. // 和组合问题不同就是在这里多了一个判断,并且开始的索引不需要修改
  14. if(track.contains(nums[i])){
  15. continue;
  16. }
  17. track.add(nums[i]);
  18. backtrack(nums, track);
  19. track.remove(Integer.valueOf(nums[i]));
  20. }
  21. }

数独问题

问题描述:37. 解数独

  1. public void solveSudoku(char[][] board) {
  2. backtrack(board, 0, 0);
  3. }
  4. private boolean backtrack(char[][] board, int r, int c){
  5. int m=9, n=9;
  6. if(c == n){
  7. // 穷举到最后一列的话就换到下一行重新开始
  8. return backtrack(board, r+1, 0);
  9. }
  10. if(r == m){
  11. // 找到一个可行解,触发 base case
  12. return true;
  13. }
  14. // 对棋盘的每个位置进行穷举
  15. for(int i=r; i< m; i++){
  16. for(int j=c; j< n; j++){
  17. if(board[i][j] != '.'){
  18. // 如果该位置是预设的数字,不用我们操心
  19. return backtrack(board, i, j + 1);
  20. }
  21. // 对每个数都进行穷举
  22. for(char ch='1'; ch <= '9'; ch++){
  23. // 如果遇到不合法的数字,就跳过
  24. if( !isValid(board, i, j, ch)){
  25. continue;
  26. }
  27. // 做选择:
  28. board[i][j] = ch;
  29. // 继续穷举下一个位置
  30. // 如果找到一个可行解,立即结束
  31. if(backtrack(board, i, j+1)){
  32. return true;
  33. }
  34. // 撤销选择
  35. board[i][j] = '.';
  36. }
  37. // 穷举完 1~9,依然没有找到可行解,此路不通, 回溯
  38. return false;
  39. }
  40. }
  41. return false;
  42. }
  43. // 判断 board[i][j] 是否可以填入 n
  44. private boolean isValid(char[][] board, int r, int c, char n) {
  45. for (int i = 0; i < 9; i++) {
  46. // 判断行是否存在重复
  47. if (board[r][i] == n) return false;
  48. // 判断列是否存在重复
  49. if (board[i][c] == n) return false;
  50. // 判断 3 x 3 方框是否存在重复
  51. if (board[(r/3)*3 + i/3][(c/3)*3 + i%3] == n)
  52. return false;
  53. }
  54. return true;
  55. }

括号生成

题目描述:22. 括号生成

  1. List<String> res = new LinkedList<>();
  2. public List<String> generateParenthesis(int n) {
  3. if(n == 0){
  4. return res;
  5. }
  6. // 回溯过程中的路径
  7. char[] track = new char[n*2];
  8. Arrays.fill(track, '.');
  9. backtrack(n, n, 0, track);
  10. return res;
  11. }
  12. // 可用的左括号数量为 left 个,可用的右括号数量为 rgiht 个,已经放的个数index
  13. private void backtrack(int left, int right, int index, char[] track){
  14. // 若左括号剩下的多,说明不合法
  15. if(left > right){
  16. return;
  17. }
  18. // 数量小于 0 肯定是不合法的
  19. if(left < 0 || right < 0){
  20. return;
  21. }
  22. // 当所有括号都恰好用完时,得到一个合法的括号组合
  23. if(left == 0 && right == 0){
  24. res.add(new String(track));
  25. return;
  26. }
  27. // 尝试放一个左括号
  28. track[index] = '(';
  29. backtrack(left-1, right, index+1, track);
  30. track[index] = '.';
  31. // 尝试放一个右括号
  32. track[index] = ')';
  33. backtrack(left, right-1, index+1, track);
  34. track[index] = '.';
  35. }

回溯算法VS动态规划

题目描述:494. 目标和

  1. // 回溯算法:
  2. int res = 0;
  3. public int findTargetSumWays(int[] nums, int S) {
  4. backbarck(nums, S, 0, 0);
  5. return res;
  6. }
  7. private void backbarck(int[] nums, int target, int start, int sum){
  8. if(start == nums.length){
  9. if(sum == target){
  10. res+=1;
  11. }
  12. return;
  13. }
  14. sum += nums[start];
  15. backbarck(nums, target, start+1, sum);
  16. sum -= nums[start];
  17. sum -= nums[start];
  18. backbarck(nums, target, start+1, sum);
  19. sum += nums[start];
  20. }
  21. // 存在子问题:加备忘录的优化
  22. // + + +/- +/- +/- 和 + - +/- +/- +/- 后面的计算就是重复的
  23. Map<String, Integer> cache = new HashMap<>();
  24. public int findTargetSumWays(int[] nums, int S) {
  25. return dp(nums, S, 0, 0);
  26. }
  27. private int dp(int[] nums, int target, int start, int sum){
  28. if(start == nums.length){
  29. if(sum == target){
  30. return 1;
  31. }
  32. return 0;
  33. }
  34. // 把它俩转成字符串才能作为哈希表的键
  35. // 即:前面的计算相同,只有后面不同,前面的就不用再算一遍了
  36. String key = start + "," + sum;
  37. // 避免重复计算
  38. if (cache.containsKey(key)) {
  39. return cache.get(key);
  40. }
  41. // 还是穷举
  42. int res = dp(nums, target, start+1, sum+nums[start]) + dp(nums, target, start+1, sum-nums[start]);
  43. cache.put(key, res);
  44. return res;
  45. }
  46. // 动态规划:把 nums 划分成两个子集A和B,分别代表分配+的数和分配-的数
  47. // sum(A) - sum(B) = target
  48. // sum(A) = target + sum(B)
  49. // sum(A) + sum(A) = target + sum(B) + sum(A)
  50. // 2 * sum(A) = target + sum(nums)
  51. // sum(A) = (target + sum(nums)) / 2
  52. // 把原问题转化成:nums中存在几个子集A,使得A中元素的和为 (target + sum(nums)) / 2
  53. // 这不就是背包问题了嘛
  54. public int findTargetSumWays(int[] nums, int S) {
  55. int n = nums.length;
  56. int sum = 0;
  57. for(int i=0; i<n; i++){
  58. sum += nums[i];
  59. }
  60. // 不可能的情况
  61. if(sum < S || (sum + S) %2 == 1){
  62. return 0;
  63. }
  64. sum = (S + sum) / 2;
  65. // 明确状态:dp[i][j] 当有i个物体时,背包容量为j时,有几种方式能装满背包
  66. int[][] dp = new int[n+1][sum+1];
  67. // base case
  68. for(int i=0; i<=n; i++){
  69. // 有东西可以装 但是背包已经满了 啥也不做是一种情况
  70. dp[i][0] = 1;
  71. }
  72. for(int j=1; j<=sum; j++){
  73. // 没东西了 但背包还没满 没有操作空间
  74. dp[0][j] = 0;
  75. }
  76. // 状态转移:
  77. for(int i=1; i<=n; i++){ // 可选物品的状态转移
  78. for(int j=0; j<=sum; j++){ // 背包容量的状态转移
  79. if(nums[i-1] > j){
  80. // 东西放不进背包,继承之前的状态
  81. dp[i][j] = dp[i-1][j];
  82. }else{
  83. // 能放进去的情况
  84. dp[i][j] = dp[i-1][j-nums[i-1]] + dp[i-1][j];
  85. }
  86. }
  87. }
  88. return dp[n][sum];
  89. }

题目

索引

39. 组合总和

40. 组合总和 II

216. 组合总和 III

47. 全排列 II

90. 子集 II

131. 分割回文串

解题

39. 组合总和

  1. List<List<Integer>> res = new LinkedList<>();
  2. public List<List<Integer>> combinationSum(int[] candidates, int target) {
  3. List<Integer> track = new LinkedList<>();
  4. Arrays.sort(candidates);
  5. backtrack(candidates, target, 0, 0, track);
  6. return res;
  7. }
  8. // param1:可选的数组
  9. // param2: 目标值
  10. // param3: 当前的总和
  11. // param4: 开始的索引,为了避免重复的组合出现多次
  12. // param5:路径
  13. private void backtrack(int[] candidates, int target, int sum, int start, List<Integer> track){
  14. if(sum >= target){
  15. if(sum == target){
  16. res.add(new LinkedList<>(track));
  17. }
  18. return;
  19. }
  20. // 剪枝:可有可无,有了效率高
  21. if(sum+candidates[start] > target){
  22. return;
  23. }
  24. for(int i=start; i<candidates.length; i++){
  25. sum += candidates[i];
  26. track.add(candidates[i]);
  27. backtrack(candidates, target, sum, i, track);
  28. sum -= candidates[i];
  29. track.remove(Integer.valueOf(candidates[i]));
  30. }
  31. }

40. 组合总和 II

  1. // 耗时 917ms
  2. List<List<Integer>> res = new LinkedList<>();
  3. // 用于去重,虽然效率很低,但能实现去重的效果
  4. Set<String> visited = new HashSet<>();
  5. public List<List<Integer>> combinationSum2(int[] candidates, int target) {
  6. List<Integer> track = new LinkedList<>();
  7. Arrays.sort(candidates);
  8. backtrack(candidates, target, 0, 0, track);
  9. return res;
  10. }
  11. private void backtrack(int[] candidates, int target, int sum, int start, List<Integer> track){
  12. if(sum >= target){
  13. if(sum == target && !visited.contains(track.toString())){
  14. res.add(new LinkedList<>(track));
  15. visited.add(track.toString());
  16. }
  17. return;
  18. }
  19. for(int i=start; i<candidates.length; i++){
  20. sum += candidates[i];
  21. track.add(candidates[i]);
  22. backtrack(candidates, target, sum, i+1, track);
  23. sum -= candidates[i];
  24. track.remove(Integer.valueOf(candidates[i]));
  25. }
  26. }
  27. // 优化版本,加入剪枝提高效率,耗时:4ms
  28. List<List<Integer>> res = new LinkedList<>();
  29. boolean[] visited;
  30. public List<List<Integer>> combinationSum2(int[] candidates, int target) {
  31. List<Integer> track = new LinkedList<>();
  32. visited = new boolean[candidates.length];
  33. Arrays.sort(candidates);
  34. backtrack(candidates, target, 0, 0, track);
  35. return res;
  36. }
  37. private void backtrack(int[] candidates, int target, int sum, int start, List<Integer> track){
  38. if(sum >= target){
  39. if(sum == target){
  40. res.add(new LinkedList<>(track));
  41. }
  42. return;
  43. }
  44. // 剪枝1:
  45. if(start<candidates.length && sum + candidates[start] > target){
  46. return;
  47. }
  48. for(int i=start; i<candidates.length; i++){
  49. // 剪枝2:☆☆☆如果数组相连元素相等,没有先访问后面的元素,就不会存在重复
  50. if (visited[i] || (i > 0 && candidates[i] == candidates[i - 1] && !visited[i - 1])) {
  51. continue;
  52. }
  53. sum += candidates[i];
  54. track.add(candidates[i]);
  55. visited[i] = true;
  56. backtrack(candidates, target, sum, i+1, track);
  57. sum -= candidates[i];
  58. track.remove(Integer.valueOf(candidates[i]));
  59. visited[i] = false;
  60. }
  61. }

216. 组合总和 III

  1. List<List<Integer>> res = new LinkedList<>();
  2. public List<List<Integer>> combinationSum3(int k, int n) {
  3. List<Integer> track = new LinkedList<>();
  4. backtrack(k, n, 1, 0, track);
  5. return res;
  6. }
  7. private void backtrack(int k, int n, int start, int sum, List<Integer> track){
  8. if(track.size() == k && sum == n){
  9. res.add(new LinkedList<>(track));
  10. return;
  11. }
  12. // 剪枝: 可有可无,有了提高效率
  13. if(start>9 || sum > n || sum+start > n){
  14. return;
  15. }
  16. for(int i=start; i<=9; i++){
  17. sum += i;
  18. track.add(i);
  19. backtrack(k, n, i+1, sum, track);
  20. sum -= i;
  21. track.remove(Integer.valueOf(i));
  22. }
  23. }

47. 全排列 II

  1. // 方法1:有误 但是我找不出来问题,[2,2,1,1] 少了一个可能的情况
  2. // 通过visited记录出现的排列情况
  3. // 通过indexs记录已经递归下去的index,下次这个index不能再用了
  4. List<List<Integer>> res = new LinkedList<>();
  5. Set<String> visited = new HashSet<>();
  6. boolean[] indexs;
  7. public List<List<Integer>> permuteUnique(int[] nums) {
  8. Arrays.sort(nums);
  9. indexs = new boolean[nums.length];
  10. List<Integer> track = new LinkedList<>();
  11. backtrack(nums, track);
  12. return res;
  13. }
  14. private void backtrack(int[] nums, List<Integer> track){
  15. if(track.size() == nums.length && !visited.contains(track.toString())){
  16. res.add(new LinkedList<>(track));
  17. visited.add(track.toString());
  18. return;
  19. }
  20. for(int i=0; i<nums.length; i++){
  21. if(indexs[i] == true) continue;
  22. indexs[i] = true;
  23. track.add(nums[i]);
  24. backtrack(nums, track);
  25. track.remove(track.size() - 1);
  26. indexs[i] = false;
  27. }
  28. }
  29. // 方法2:通过新建立一个数组,来写出所有的排列组合的可能,后续通过visited来删除重复的情况
  30. List<List<Integer>> res = new LinkedList<>();
  31. Set<String> visited = new HashSet<>(); // 记录已经出现过的情况
  32. public List<List<Integer>> permuteUnique(int[] nums) {
  33. List<Integer> track = new LinkedList<>();
  34. // 建立新数组,其值对应原数组的索引
  35. int[] new_nums = new int[nums.length];
  36. for(int i=0; i<nums.length; i++){
  37. new_nums[i] = i;
  38. }
  39. backtrack(nums, new_nums, track);
  40. return res;
  41. }
  42. private void backtrack(int[] nums, int[] new_nums, List<Integer> track){
  43. if(track.size() == nums.length){
  44. List<Integer> tmp = new LinkedList();
  45. // 根据索引回到原数组
  46. for(int index: track){
  47. tmp.add(nums[index]);
  48. }
  49. // 判断是否已经出现过这种情况
  50. if(!visited.contains(tmp.toString())){
  51. res.add(tmp);
  52. visited.add(tmp.toString());
  53. }
  54. return;
  55. }
  56. for(int i=0; i<nums.length; i++){
  57. if(track.contains(new_nums[i])){
  58. continue;
  59. }
  60. track.add(new_nums[i]);
  61. backtrack(nums, new_nums, track);
  62. track.remove(Integer.valueOf(new_nums[i]));
  63. }
  64. }
  65. // 方法3:优化了去重的方式,效率由5% ---> 99%
  66. boolean[] visited;
  67. public List<List<Integer>> permuteUnique(int[] nums) {
  68. List<List<Integer>> res = new ArrayList<List<Integer>>();
  69. List<Integer> track = new ArrayList<Integer>();
  70. visited = new boolean[nums.length];
  71. Arrays.sort(nums);
  72. backtrack(nums, res, 0, track);
  73. return res;
  74. }
  75. private void backtrack(int[] nums, List<List<Integer>> ans, int idx, List<Integer> track) {
  76. if (idx == nums.length) {
  77. ans.add(new ArrayList<Integer>(track));
  78. return;
  79. }
  80. for (int i = 0; i < nums.length; ++i) {
  81. // ☆☆☆如果数组相连元素相等,没有先访问后面的元素,就不会存在重复
  82. if (visited[i] || (i > 0 && nums[i] == nums[i - 1] && !visited[i - 1])) {
  83. continue;
  84. }
  85. track.add(nums[i]);
  86. visited[i] = true;
  87. backtrack(nums, ans, idx + 1, track);
  88. visited[i] = false;
  89. track.remove(idx);
  90. }
  91. }

90. 子集 II

  1. // 回溯解法:正常的模版解题
  2. List<List<Integer>> res = new LinkedList<>();
  3. Set<String> visited = new HashSet<>();
  4. public List<List<Integer>> subsetsWithDup(int[] nums) {
  5. List<Integer> track = new LinkedList<>();
  6. // 因为集和具有无序性,因此需要排序来排除顺序造成的重复子集
  7. // 即:两个子集如果只是元素的排列顺序不一样,则认为重复
  8. Arrays.sort(nums);
  9. backtrack(nums, 0, track);
  10. return res;
  11. }
  12. private void backtrack(int[] nums, int start, List<Integer> track){
  13. if(!visited.contains(track.toString())){
  14. visited.add(track.toString());
  15. res.add(new LinkedList(track));
  16. }else{
  17. return;
  18. }
  19. for(int i=start; i<nums.length; i++){
  20. // 做选择
  21. track.add(nums[i]);
  22. // 回溯
  23. backtrack(nums, i+1, track);
  24. // 撤销选择
  25. track.remove(Integer.valueOf(nums[i]));
  26. }
  27. }
  28. // 回溯解法:+ 剪枝
  29. List<List<Integer>> res = new LinkedList<>();
  30. boolean[] visited;
  31. public List<List<Integer>> subsetsWithDup(int[] nums) {
  32. visited = new boolean[nums.length];
  33. List<Integer> track = new LinkedList<>();
  34. // 因为集和具有无序性,因此需要排序来排除顺序造成的重复子集
  35. // 即:两个子集如果只是元素的排列顺序不一样,则认为重复
  36. Arrays.sort(nums);
  37. backtrack(nums, 0, track);
  38. return res;
  39. }
  40. private void backtrack(int[] nums, int start, List<Integer> track){
  41. res.add(new LinkedList(track));
  42. for(int i=start; i<nums.length; i++){
  43. // ☆☆☆如果数组相连元素相等,没有先访问后面的元素,就不会存在重复
  44. if (visited[i] || (i > 0 && nums[i] == nums[i - 1] && !visited[i - 1])) {
  45. continue;
  46. }
  47. // 做选择
  48. track.add(nums[i]);
  49. visited[i] = true;
  50. // 回溯
  51. backtrack(nums, i+1, track);
  52. // 撤销选择
  53. track.remove(track.size() - 1);
  54. visited[i] = false;
  55. }
  56. }

131. 分割回文串

  1. List<List<String>> res = new LinkedList<>();
  2. public List<List<String>> partition(String s) {
  3. List<String> track = new LinkedList<>();
  4. backtrack(s, 0, track);
  5. return res;
  6. }
  7. private void backtrack(String s, int start, List<String> track){
  8. if(start == s.length()){
  9. res.add(new LinkedList<>(track));
  10. return;
  11. }
  12. for(int i=start; i<s.length(); i++){
  13. String sub = s.substring(start, i+1);
  14. if(!isPalindrome(sub)){
  15. continue;
  16. }
  17. track.add(sub);
  18. backtrack(s, i+1, track);
  19. track.remove(track.size()-1);
  20. }
  21. }
  22. private boolean isPalindrome(String s){
  23. if(s == null || s.length() <= 1){
  24. return true;
  25. }
  26. int left = 0;
  27. int right = s.length() - 1;
  28. while(left < right){
  29. if(s.charAt(left) == s.charAt(right)){
  30. left++;
  31. right--;
  32. }else{
  33. return false;
  34. }
  35. }
  36. return true;
  37. }

LeetCode:回溯算法的更多相关文章

  1. leetcode回溯算法--基础难度

    都是直接dfs,算是巩固一下 电话号码的字母组合 给定一个仅包含数字 2-9 的字符串,返回所有它能表示的字母组合. 给出数字到字母的映射如下(与电话按键相同).注意 1 不对应任何字母. 思路 一直 ...

  2. LeetCode刷题191203 --回溯算法

    虽然不是每天都刷,但还是不想改标题,(手动狗头 题目及解法来自于力扣(LeetCode),传送门. 算法(78): 给定一组不含重复元素的整数数组 nums,返回该数组所有可能的子集(幂集). 说明: ...

  3. Leetcode——回溯法常考算法整理

    Leetcode--回溯法常考算法整理 Preface Leetcode--回溯法常考算法整理 Definition Why & When to Use Backtrakcing How to ...

  4. LeetCode通关:连刷十四题,回溯算法完全攻略

    刷题路线:https://github.com/youngyangyang04/leetcode-master 大家好,我是被算法题虐到泪流满面的老三,只能靠发发文章给自己打气! 这一节,我们来看看回 ...

  5. 回溯算法 LEETCODE别人的小结 一八皇后问题

    回溯算法实际上是一个类似枚举的搜索尝试过程,主要是在搜索尝试中寻找问题的解,当发现已不满足求解条件时,就回溯返回,尝试别的路径. 回溯法是一种选优搜索法,按选优条件向前搜索,以达到目的.但是当探索到某 ...

  6. C#LeetCode刷题-回溯算法

    回溯算法篇 # 题名 刷题 通过率 难度 10 正则表达式匹配   18.8% 困难 17 电话号码的字母组合   43.8% 中等 22 括号生成   64.9% 中等 37 解数独   45.8% ...

  7. 46. Permutations 回溯算法

    https://leetcode.com/problems/permutations/ 求数列的所有排列组合.思路很清晰,将后面每一个元素依次同第一个元素交换,然后递归求接下来的(n-1)个元素的全排 ...

  8. Gray Code——陈瑶师姐面试时候要用回溯算法

    The gray code is a binary numeral system where two successive values differ in only one bit. Given a ...

  9. 算法刷题--回溯算法与N皇后

    所谓回溯算法,在笔者看来就是一种直接地思想----假设需要很多步操作才能求得最终的解,每一步操作又有很多种选择,那么我们就直接选择其中一种并依次深入下去.直到求得最终的结果,或是遇到明细的错误,回溯到 ...

随机推荐

  1. noip模拟29

    这次终于是早上考试了 早上考试手感不错,这次刷新了以前的最高排名- %%%cyh巨佬 \(rk1\) %%%CT巨佬 \(t2\) 90 纵观前几,似乎我 \(t3\) 是最低的-- 总计挂分10分, ...

  2. junit4 套件测试

    junit4 中的套件可以用来测试一个需要依赖的业务流程,如购买必须依赖与登录成功 代码实现: 测试数据存放 public class BaseTest { protected static Hash ...

  3. Python使用openpyxl模块操作Excel表格

    ''' Excel文件三个对象 workbook: 工作簿,一个excel文件包含多个sheet. sheet:工作表,一个workbook有多个,表名识别,如"sheet1",& ...

  4. Python+Selenium:初步使用Chrome谷歌浏览器

    ·············环境结合··············· 我的环境:window10 64位 Python 3.7 32-bit selenium            3.141.0 Goo ...

  5. centos7 未启用swap导致内存使用率过高。

    情况描述: 朋友在阿里云上有一台系统为CentOS7的VPS,内存为2GB,用于平时开发自己的项目时测试使用: 他在上面运行了5个docker实例,运行java程序:还有一个mysql服务: 上述5个 ...

  6. 替代jquery中的几个函数

    // https://open.alipay.com/developmentAccess/developmentAccess.htm var $ = window.jQuery; (function( ...

  7. Fiddler修改抓包请求

    hi,说到fiddler的用途,第一时间想到抓包,不过还有一个功能是:支持修改请求. 那么问题来了,怎么做呢?很简单,先定下我们需要修改哪个请求. 这里用F12跟fiddler做演示. 首先我们在F1 ...

  8. 『Python』多进程

    Python中的多线程无法利用多核优势,如果想要充分地使用多核CPU的资源(os.cpu_count()查看),在Python中大部分情况需要使用多进程.Python提供了multiprocessin ...

  9. python3之工程中必须依赖的__init__.py

    1.  __init__.py 1.1  什么是__init__.py 在Python3工程里,当python3检测到一个目录下存在__init__.py文件时,Python3就会把它当成一个模块(m ...

  10. maven插件maven-install-plugin

    maven-install-plugin负责将获取的jar包安装到本地仓库 <build> <plugin> <groupId>org.apache.maven.p ...