回溯(backtrack)

  • 1 回溯法思路
  • 2 常见题目
    • 2.1 (lee-78) 子集
    • 2.2 (lee-90) 子集2
    • 2.3 (lee-46) 全排列
    • 2.4 (lee-47) 全排列2
    • 2.5 (lee-39) 组合总和
    • 2.6 (lee-40) 组合总和2
    • 2.7 (lee-93) 复原IP地址
    • 2.8 (lee-17) 电话号码的字母组合
    • 2.9 (lee-131) 分割回文串
    • 2.10 (lee-79) 单词搜索
    • 2.11 (lee-200) 岛屿数量
    • 2.12 (lee-JZ13)机器人的运动范围
    • 2.13 (lee-JZ38) 字符串的排列
  • 3 练习链接

(JAVA版本)

1 回溯法思路

常用于遍历列表所有子集,是 DFS 深度搜索一种,一般用于全排列,穷尽所有可能,遍历的过程实际上是一个决策树的遍历过程。时间复杂度一般 O(N!),它不像动态规划存在重叠子问题可以优化,回溯算法就是纯暴力穷举,复杂度一般都很高。
思路:核心就是从选择列表里做一个选择,然后一直递归往下搜索答案,如果遇到路径不通,就返回来撤销这次选择。

伪代码结构

     int[] result = new int[size];void backtrack(选择列表,路径): {if 满足结束条件: {result.add(路径)return}for (选择 : 选择列表): {做选择backtrack(选择列表,路径)撤销选择}}

说明
在后面的题目中,我们会频繁的看到visited[]以及start变量,有些朋友可能会疑惑什么时候使用 visited 数组,什么时候使用 start 变量?总结如下:

  • 排列问题,讲究顺序(即 [2, 2, 3] 与 [2, 3, 2] 视为不同列表时),需要记录哪些数字已经使用过,此时用visited 数组;
  • 组合问题,不讲究顺序(即 [2, 2, 3] 与 [2, 3, 2] 视为相同列表时),需要按照某种顺序搜索,此时使用start 变量。

2 常见题目

2.1 (lee-78) 子集

给定一组不含重复元素的整数数组 nums,返回该数组所有可能的子集(幂集)。可以按 任意顺序 返回解集。
思路:回溯法

输入:nums = [1,2,3]
输出:[[],[1],[2],[1,2],[3],[1,3],[2,3],[1,2,3]]

(1)按任意顺序返回

 /*** 1.按任意顺序返回*/public ArrayList<ArrayList<Integer>> subsets1(int[] S) {  ArrayList<ArrayList<Integer>> res  = new ArrayList<>();LinkedList<Integer> track = new LinkedList<>();back(res,S,0,track);return res;        }  private void back(ArrayList<ArrayList<Integer>> res,int[] S, int start, LinkedList<Integer> track) {res.add(new ArrayList<>(track));for(int i = start;i < S.length;i++) {track.add(S[i]); //更新状态back(res,S,i+1,track); //回溯track.removeLast(); //回退状态}       }

(2)按一定顺序返回

 /*** 2.按一定顺序返回*/public List<List<Integer>> subsets(int[] nums) {List<List<Integer>> res = new ArrayList<>();for(int i = 0;i <= nums.length ;i++) {  //i是某个子集内的元素个数backtrack(res,0,new ArrayList<Integer>(),nums,i); }return res;    }private void backtrack(List<List<Integer>> res, int first, ArrayList<Integer> cur, int[] nums,int k) {if(cur.size()== k) {res.add(new ArrayList<>(cur));return ;}for(int i = first;i < nums.length;i++) {cur.add(nums[i]);backtrack(res,i+1, cur, nums,k);cur.remove(cur.size()-1); // 删除最后添加的一个数}}public static void main(String[] args) {Subsets s = new Subsets();Scanner in = new Scanner(System.in);String[] str = in.nextLine().split(" ");int[] nums  = new int[str.length];for(int i = 0;i <nums.length;i++) {nums[i] = Integer.parseInt(str[i]);}List<List<Integer>> res = s.subsets(nums);System.out.println(res);}

2.2 (lee-90) 子集2

给你一个整数数组 nums ,其中可能包含重复元素,请你返回该数组所有可能的子集(幂集)。解集 不能 包含重复的子集。返回的解集中,子集可以按 任意顺序 排列。

输入:nums = [1,2,2]
输出:[[],[1],[1,2],[1,2,2],[2],[2,2]]

import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.Scanner;public class Subsets2 {/*** 思路:重复的解不添加进去,并用一个数组visited标记某个元素是否被访问过*/public List<List<Integer>> subsetsWithDup(int[] nums) {List<List<Integer>> res = new ArrayList<>();Arrays.sort(nums); //先将数组排序List<Integer> temp = new ArrayList<>();for(int i= 0;i <= nums.length;i++) {backtrack(res,temp,new boolean[nums.length],nums,0,i);}return res;}/** * @param res 所有子集* @param temp * @param visited 标记元素* @param nums * @param start* @param size*/private void backtrack(List<List<Integer>> res, List<Integer> temp, boolean[] visited, int[] nums, int start, int size) {if(temp.size() == size) {res.add(new ArrayList<>(temp));return;}for(int i = start; i <nums.length;i++) {if(i != 0  && nums[i] == nums[i-1] && !visited[i-1]) {  //若发现没有选择上一个数,且当前数字与上一个数相同且前一个数没有被标记过,则可以跳过当前生成的子集。。continue;}temp.add(nums[i]);visited[i] = true;backtrack(res, temp, visited, nums, i+1, size); //根据与前面元素是否重复,来决定start的取值,也就是开始遍历的位置visited[i] = false;temp.remove(temp.size()-1);} }public static void main(String[] args) {Subsets2 s = new Subsets2();Scanner in = new Scanner(System.in);String[] str = in.nextLine().split(" ");int[] nums  = new int[str.length];for(int i = 0;i <nums.length;i++) {nums[i] = Integer.parseInt(str[i]);}List<List<Integer>> res = s.subsetsWithDup(nums);System.out.println(res);}
}

2.3 (lee-46) 全排列

给定一个没有重复数字的序列,返回其所有可能的全排列。 可以 按任意顺序 返回答案。

输入:nums = [1,2,3]
输出:[[1,2,3],[1,3,2],[2,1,3],[2,3,1],[3,1,2],[3,2,1]]

 /*** 思路:需要用一个visited数组记录已经选择过的元素,满足条件的结果才进行返回* @param nums* @return*/public List<List<Integer>> permute(int[] nums) {List<List<Integer>> res = new ArrayList<>();List<Integer> permute = new ArrayList<>();boolean[] visited = new boolean[nums.length];backtrack(res,permute,visited,nums);return res;}/*** @param res 返回结果* @param permute * @param visited 记录以及选择过的元素* @param nums 数组*/private void backtrack(List<List<Integer>> res, List<Integer> permute, boolean[] visited, int[] nums) {if(permute.size() == nums.length) { //满足结束条件res.add(new ArrayList<>(permute));return ;}for(int i = 0;i < visited.length;i++) {if(visited[i]) {continue;}visited[i] = true; //做选择permute.add(nums[i]); backtrack(res, permute, visited, nums);visited[i] = false;//撤销选择  permute.remove(permute.size() - 1);         }}

2.4 (lee-47) 全排列2

给定一个可包含重复数字的序列,返回所有不重复的全排列。
时间复杂度为 O(n×n!)
空间复杂度 O(n)

输入:nums = [1,1,2]
输出:[[1,1,2], [1,2,1], [2,1,1]]

 public List<List<Integer>> permuteUnique(int[] nums) {List<List<Integer>> res = new ArrayList<>();List<Integer> permute = new ArrayList<>();Arrays.sort(nums);boolean[] visited  = new boolean[nums.length];backtrack(res,permute,visited,nums);return res;}private void backtrack(List<List<Integer>> res, List<Integer> permute, boolean[] visited, int[] nums) {if(permute.size() == nums.length) {res.add(new ArrayList<>(permute));return;}for(int i = 0;i <nums.length;i++) {if(i != 0 && nums[i] == nums[i-1] && !visited[i-1]) { //若发现没有选择上一个数,且当前数字与上一个数相同且前一个数没有被标记过,则可以跳过当前生成的子集。continue;}if(visited[i]) {continue;}visited[i] = true; //做选择permute.add(nums[i]);backtrack(res, permute, visited, nums);visited[i] = false;//撤销选择permute.remove(permute.size() - 1);}}

2.5 (lee-39) 组合总和

给定一个无重复元素的数组 candidates 和一个目标数 target ,找出 candidates 中所有可以使数字和为 target 的组合。
candidates 中的数字可以无限制重复被选取。
所有数字(包括 target)都是正整数。
解集不能包含重复的组合。

输入:candidates = [2,3,6,7], target = 7,
所求解集为:[ [7], [2,2,3] ]

(1)写法一

 /*** (-------------写法一 -------------)* [2,3,6,7],7, res = [[7],[2,2,3]]* 思路:画树形图,深度优先遍历,回溯剪枝* 1.以 target 根结点 ,创建一个分支时做减;* 2.每一个箭头表示:从父亲结点的数值减去边上的数值,得到孩子结点的数值。边的值就是题目中给出的 candidate 数组的每个元素的值;* 3.减到 0 或者负数的时候停止,即:结点 0 和负数结点成为叶子结点;* 4.所有从根结点到结点 0 的路径(只能从上往下,没有回路)就是题目要找的一个结果列表。* * 以target=7为例,结果集应该为{{2,2,3},{7}},但是以上步骤会出现重复路径,比如{2,3,2},{3,2,2},由于题目不要求顺序输出,因此需要去重,也就是剪枝。* 去重思路:在搜索的过程中去重,每一次搜索的时候设置下一轮搜索的起点start;* * @param candidates* @param target* @return*/public List<List<Integer>> combinationSum(int[] candidates, int target) {List<List<Integer>> res = new ArrayList<>();List<Integer> combinPath = new ArrayList<>();backtrack(res,combinPath,target,candidates,0);return res;}/*** * @param res 结果集* @param combin 从根节点到叶子节点的路径列表* @param target 每减去一个元素,目标值变小* @param candidates 候选数组* @param start 搜索起点*/private void backtrack(List<List<Integer>> res, List<Integer> combinPath, int target, int[] candidates, int start) {// target 为负数和 0 的时候不再产生新的叶子结点if(target < 0 ) {return;}if(target == 0) {res.add(new ArrayList<>(combinPath));return;}for(int i = start;i < candidates.length;i++) {//从 start 开始搜索combinPath.add(candidates[i]); System.out.println("递归之前 => " + combinPath + ",剩余 = " + (target - candidates[i]));backtrack(res, combinPath, target-candidates[i], candidates,i);// 注意:由于每一个元素可以重复使用,下一轮搜索的起点依然是 i,这里非常容易弄错combinPath.remove(combinPath.size() - 1);System.out.println("递归之后 => " + combinPath);}}

(2)写法二

 /***  (-------------写法二 -------------)*  剪枝提速:如果 target 减去一个数得到负数,那么减去一个更大的树依然是负数,同样搜索不到结果;*  基于这个想法,我们可以对输入数组进行排序,排序是为了提高搜索速度,添加相关逻辑达到进一步剪枝的目的。*  可以打印递归前后的输出查看**** @param candidates* @param target* @return*/public List<List<Integer>> combinationSum1(int[] candidates, int target) {List<List<Integer>> res = new ArrayList<>();List<Integer> combinPath = new ArrayList<>();Arrays.sort(candidates); // 排序是剪枝的前提backtrack1(res,combinPath,candidates,target,0);return res;}private void backtrack1(List<List<Integer>> res, List<Integer> combinPath, int[] candidates, int target, int start) {// 由于进入更深层的时候,小于 0 的部分被剪枝,因此递归终止条件值只判断等于 0 的情况if(target == 0) {res.add(new ArrayList<>(combinPath));return;}for(int i = start ;i < candidates.length;i++) {if(target < candidates[i]) { // 重点理解这里剪枝,前提是候选数组已经有序break;}combinPath.add(candidates[i]);System.out.println("递归之前 => " + combinPath + ",剩余 = " + (target - candidates[i]));backtrack1(res, combinPath, candidates, target-candidates[i], i);combinPath.remove(combinPath.size() - 1);System.out.println("递归之后 => " + combinPath);}}

2.6 (lee-40) 组合总和2

给定一个数组 candidates 和一个目标数 target ,找出 candidates 中所有可以使数字和为 target 的组合。
candidates 中的 每个数字在每个组合中只能使用一次 。
说明
所有数字(包括目标数)都是正整数。
解集不能包含重复的组合。

输入: candidates = [2,5,2,1,2], target = 5,
输出: [ [1,2,2], [5] ]

时间复杂度:O(n*2^n)
空间复杂度:O(n)

 /*** 思路:画树形图,深度遍历,剪枝去重* 去重的思路:由于题目要求每个数字在每个组合中只能使用一次并且解集不能包含重复组合,* [2,5,2,1,2] T = 5, res = [[1,2,2],[5]]* @param candidates* @param target* @return*/public List<List<Integer>> combinationSum2(int[] candidates, int target) {List<List<Integer>> res = new ArrayList<>();List<Integer> path = new ArrayList<>();Arrays.sort(candidates); //排序避免重复解backtrack(res,path,candidates,target,0);return res;}/*** * @param res* @param path* @param candidates* @param target* @param start*/private void backtrack(List<List<Integer>> res, List<Integer> path, int[] candidates, int target, int start) {if(target == 0) {res.add(new ArrayList<>(path));return;}for(int i = start;i < candidates.length;i++) { //for循环遍历从start开始的数,选一个数进入下一层递归。 if(target < candidates[i]) { // 剪枝:如果当前candidates数组的和已经大于目标target,没必要枚举了,直接returnbreak;}if(i > start && candidates[i] == candidates[i-1]){ //避免子路径重复,如出现两个[1,2,2]:如果从start开始的数有连续出现的重复数字,跳过该数字continuecontinue;}path.add(candidates[i]);backtrack(res, path, candidates, target - candidates[i], i+1); //注意为避免一个组合里出现重复使用的情况所以在进入下一层递归时要用i+1,从i之后的数中选择接下来的数,避免出现[1,1,1,1,1]类似这样path.remove(path.size() - 1);          }}

2.7 (lee-93) 复原IP地址

给定一个只包含数字的字符串,用以表示一个 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 地址。

输入:s = “25525511135”
输出:[“255.255.11.135”,“255.255.111.35”]
输入:s = “010010”
输出:[“0.10.0.10”,“0.100.1.0”]

 /*** 思路:由于要找出所有可能复原出的 IP 地址,使用回溯的方法,对所有可能的字符串分隔方式进行搜索,并筛选出满足要求的作为答案。* 回溯套路* @param s* @return*/public List<String> restoreIpAddresses(String s) {List<String> res = new ArrayList<>();List<String> path = new ArrayList<>();backtrack(res,path,s,0);return res;}private void backtrack(List<String> res, List<String> path, String s, int start) {//终止条件if(path.size() > 4) { //IP超过四段终止return;}if(path.size() >= 4 && start != s.length()) { //如果等于4段但不是原字符串也终止return;}if(path.size() == 4) { //如果等于4段并且组合起来是原字符串则添加到结果集里面res.add(String.join(".", path));return;}for(int i = start; i < s.length();i++) {//选择条件:如果字符串在0-255之外则跳过;如果字符串存在01这种情况也跳过继续String str = s.substring(start,i+1);if(str.length() > 1 && str.startsWith("0") || str.length() > 3) {continue;} int val = Integer.valueOf(str);if(val < 0 || val > 255) {continue;}path.add(str); //做选择backtrack(res, path, s, i+1);path.remove(path.size() - 1); //撤销选择}}

2.8 (lee-17) 电话号码的字母组合

给定一个仅包含数字 2-9 的字符串,返回所有它能表示的字母组合。答案可以按 任意顺序 返回。给出数字到字母的映射如下(与电话按键相同)。注意 1 不对应任何字母。

输入:digits = “23”
输出:[“ad”,“ae”,“af”,“bd”,“be”,“bf”,“cd”,“ce”,“cf”]
输入:digits = “2”
输出:[“a”,“b”,“c”]
输入:digits = “”
输出:[ ]

 public List<String> letterCombinations(String digits) {List<String> res = new ArrayList<>();         if(digits.length() == 0) {return res;}Map<Character,String> map = new HashMap<>();map.put('2', "abc");map.put('3', "def");map.put('4', "ghi");map.put('5', "jkl");map.put('6', "mno");map.put('7', "pqrs");map.put('8', "tuv");map.put('9', "wxyz");backtrack(res,digits,map,new StringBuilder(),0);return res;}/*** * @param res 结果集* @param digits 输入的String数字串* @param map 存储字符和字符串的映射* @param cur 当前读入的内容* @param start */private void backtrack(List<String> res, String digits,Map<Character, String> map, StringBuilder cur ,int start) {if(cur.length() == digits.length()) { //结束条件:当长度够了就添加结果res.add(cur.toString());return;}char c = digits.charAt(start); //根据当前数字选择字符String letters = map.get(c);//字符对应的字符串for(int i  = 0;i <letters.length();i++) {cur.append(letters.charAt(i));backtrack(res, digits,map, cur ,start+1);cur.deleteCharAt(start);}}

2.9 (lee-131) 分割回文串

给你一个字符串 s,请你将 s 分割成一些子串,使每个子串都是 回文串 。返回 s 所有可能的分割方案。回文串 是正着读和反着读都一样的字符串。

输入:s = “aab”
输出:[[“a”,“a”,“b”],[“aa”,“b”]]
输入:s = “a”
输出:[[“a”]]

 public List<List<String>> partition(String s) {List<List<String>> res = new ArrayList<>();List<String> path = new ArrayList<>();backtrack(res,path,s,0);return res;}/*** 回溯* @param res* @param path* @param s* @param start*/private void backtrack(List<List<String>> res, List<String> path, String s, int start) {if(start == s.length()) { //注意是startres.add(new ArrayList<>(path));return;}for(int i = start;i < s.length();i++) {String str = s.substring(start,i+1);if(!isPalindrome(str)) {continue;}path.add(str);backtrack(res, path, s, i+1);  //注意path.remove(path.size() - 1);}}/*** 判断是否是回文串* @param s* @return*/private boolean isPalindrome(String s) {if(s == null || s.length() <= 1) {return true;}int left = 0;int right = s.length() - 1;while(left < right) {if(s.charAt(left) != s.charAt(right)) {return false;}left++;right--;}return true;}

2.10 (lee-79) 单词搜索

(lee-JZ12)矩阵中的路径
请设计一个函数,用来判断在一个矩阵中是否存在一条包含某字符串所有字符的路径。路径可以从矩阵中的任意一格开始,每一步可以在矩阵中向左、右、上、下移动一格。
如果一条路径经过了矩阵的某一格,那么该路径不能再次进入该格子。例如,在下面的3×4的矩阵中包含一条字符串“abcced”的路径(路径中的字母用加粗标出)。

但矩阵中不包含字符串“abfb”的路径,因为字符串的第一个字符b占据了矩阵中的第一行第二个格子之后,路径不能再次进入这个格子。

输入:board = [[“a”,“b”],[“c”,“d”]], word = “abcd”
输出:false
输入:board = [[“A”,“B”,“C”,“E”],[“S”,“F”,“C”,“S”],[“A”,“D”,“E”,“E”]], word = “ABCCED”
输出:true

 /*** dfs + 回溯(重要)*/private int m,n;private int[][] direction  = {{1,0},{-1,0},{0,1},{0,-1}};public boolean exist(char[][] board,String word) {m = board.length;n = board[0].length;boolean[][] visited = new boolean[m][n];for(int i = 0;i < m;i++) {for(int j = 0;j < n;j++) {if(dfs(0,i,j,visited,board,word)){return true;}}}return false;}private boolean dfs(int start, int r, int c, boolean[][] visited, char[][] board, String word) {//递归结束条件:返回true ---- 字符串以全部匹配if(start == word.length()) { return true;}//递归结束条件:返回false ---- 如果行或列索引越界 || 当前矩阵元素与目标字符不同 || 当前矩阵元素已访问过   说明此路不通if(r < 0 || r >= m || c < 0 || c >= n || board[r][c] != word.charAt(start) || visited[r][c]) {return false;}visited[r][c] = true; for(int[] d : direction) {if(dfs(start + 1, r + d[0], c + d[1], visited, board, word)) {return true;}}        visited[r][c] = false;     return false;}

2.11 (lee-200) 岛屿数量

给你一个由 ‘1’(陆地)和 ‘0’(水)组成的的二维网格,请你计算网格中岛屿的数量。岛屿总是被水包围,并且每座岛屿只能由水平方向和/或竖直方向上相邻的陆地连接形成。此外,你可以假设该网格的四条边均被水包围。
思路:DFS 通过深度搜索遍历可能性

输入:grid = [
[“1”,“1”,“1”,“1”,“0”],
[“1”,“1”,“0”,“1”,“0”],
[“1”,“1”,“0”,“0”,“0”],
[“0”,“0”,“0”,“0”,“0”]
]
输出:1

 public int numIslands(char[][] grid) {if(grid.length==0) {return 0;}int total = 0;int row = grid.length;int col = grid[0].length;for(int i = 0;i < row;i++) {for(int j = 0;j < col;j++) {if(grid[i][j]=='1') {dfs(grid,i,j);total++;             }}}return total;}/*** 深度搜索DFS* @param grid 二维网格-岛屿* @param x * @param y* @return*/private void dfs(char[][] grid, int x, int y) {if(x < 0 || x >= grid.length || y < 0 || y >= grid[0].length || grid[x][y] != '1' ) {return;}grid[x][y] = '0';dfs(grid,x-1,y);dfs(grid,x+1,y);dfs(grid,x,y-1);dfs(grid,x,y+1);}

2.12 (lee-JZ13)机器人的运动范围

地上有一个m行n列的方格,从坐标 [0,0] 到坐标 [m-1,n-1] 。一个机器人从坐标 [0, 0] 的格子开始移动,它每次可以向左、右、上、下移动一格(不能移动到方格外),也不能进入行坐标和列坐标的数位之和大于k的格子。例如,当k为18时,机器人能够进入方格 [35, 37] ,因为3+5+3+7=18。但它不能进入方格 [35, 38],因为3+5+3+8=19。请问该机器人能够到达多少个格子?

输入:m = 2, n = 3, k = 1
输出:3
输入:m = 3, n = 1, k = 0
输出:1

思路:DFS
首先,我们可以把行坐标和列坐标的数位之和大于k的格子看成是障碍物,标注为1,其他可达的地方标注为0。
其次,因为机器人可以向上下左右移动一格,我们可以将其缩减为向下和向右两个方向移动,因为当我们从起点向这两个方向移动时,则新加入的空方格都可以由上方或左方的格子移动一步得到,而且当k值越大,连通的空方格的区域越多。
因此状态转移方程可以写成:visited[i][j] = visited[i-1][j] || visited[i][j-1] visited[i][j]表示可达或者不可达
最后,使用一个变量res来记录可达的格子的数量
初始条件:visited[i][j] = 0 可达
再说,如何计算数位和?
我们每次将数 X%10 取余的操作得到X的个位数,然后再将X/10,相当于>>一位,删除个位数,不断重复直到 X = 0 时结束。
时间复杂度:O(mn) 其中 m 为方格的行数, n 为方格的列数。一共有 O(mn)个状态需要计算,每个状态递推计算的时间复杂度为 O(1),所以总时间复杂度为 O(mn).
空间复杂度:O(m
n) 需要 O(mn)大小的结构来记录每个位置是否可达。

public class RobotMovingCount {      // DFS  int res = 0;public int movingCount(int m, int n, int k) {if(k == 0) {return 1;}boolean[][] visited = new boolean[m][n]; //初始化默认为false       dfs(visited,0,0,k);return res;}private void dfs(boolean[][] visited, int x, int y, int k) {int m = visited.length;int n = visited[0].length;//越界情况和遇到障碍的情况if(x >= m || x < 0 || y >= n || y < 0 || get(x)+get(y) > k || visited[x][y]) {return ;}visited[x][y] = true; //未遍历res++;dfs(visited, x+1, y, k); //往下走一格dfs(visited, x, y+1, k); //往右走一格}//位数和private int get(int x) {int res = 0;while(x != 0) {res += x % 10;x /= 10;}return res;}public static void main(String[] args) {Scanner in = new Scanner(System.in);int m = in.nextInt();int n = in.nextInt();int k = in.nextInt();RobotMovingCount rmc = new RobotMovingCount();int res = rmc.movingCount(m, n, k);System.out.print(res);}
}

2.13 (lee-JZ38) 字符串的排列

(lee-JZ38)字符串的排列
返回String[]类型:输入一个字符串,打印出该字符串中字符的所有排列。你可以以任意顺序返回这个字符串数组,但里面不能有重复元素。
(NC-JZ27)字符串的排列:返回ArrayList类型。输入一个字符串,长度不超过9(可能有字符重复),字符只包括大小写字母。

输入:s = “abc”
输出:[“abc”,“acb”,“bac”,“bca”,“cab”,“cba”]

public class Permutation {/** 思路:回溯法* 与lee-46和47类似题目,都是求全排列* 要求不能重复,因此使用47中无重复的排列*/public ArrayList<String> permutation(String s) {   ArrayList<String> res = new ArrayList<>();StringBuilder sb = new StringBuilder();Arrays.sort(s.toCharArray());    //按字典序时boolean[] visited  = new boolean[s.length()];backtrack(res,sb,visited,s);return res;}private void backtrack(ArrayList<String> res, StringBuilder sb, boolean[] visited, String s) {if(sb.length() == s.length()) {res.add(sb.toString());return;}for(int i = 0;i < visited.length;i++) {if(i != 0 && s.charAt(i)==s.charAt(i-1) && !visited[i-1]) { //若发现没有选择上一个字符,且当前字符与上一个字符相同且前一个字符没有被标记过,则可以跳过当前生成的子集。continue;}if(visited[i]) {continue;}visited[i] = true; //做选择sb.append(s.charAt(i));backtrack(res, sb, visited, s);visited[i] = false;//撤销选择sb.deleteCharAt(sb.length() - 1);}}public static void main(String args[]) {Permutation p = new Permutation();Scanner in = new Scanner(System.in);String s = in.next();ArrayList<String>  res = p.permutation(s);System.out.print(res);}
}

3 练习链接

  • lee-78 子集
  • lee-90 子集2
  • lee-46 全排列
  • lee-47 全排列2
  • lee-39 组合总和
  • lee-40 组合总和2
  • lee-93 复原IP地址
  • lee-17 电话号码的字母组合
  • lee-131 分割回文串
  • (lee-79) 单词搜索
  • lee-200 岛屿数量
  • (lee-JZ13)机器人的运动范围
  • (lee-JZ38)字符串的排列

回溯(backtrack)相关推荐

  1. 算法-回溯backtrack

    回溯:决策树的遍历过程 思考三个问题: 1.路径:也就是已经做出的选择. 2.选择列表:也就是你当前可以做的选择. 3.结束条件:也就是到达决策树底层,无法再做选择的条件. for 循环里面的递归,在 ...

  2. 135. Leetcode 46. 全排列 (回溯算法-排列问题)

    class Solution:def permute(self, nums: List[int]) -> List[List[int]]:# 方法一# res = [] # 存放符合条件结果的集 ...

  3. 回溯算法团灭子集、排列、组合问题

    回溯算法团灭子集.排列.组合问题 一.子集 给定一组不含重复元素的整数数组 nums,返回该数组所有可能的子集(幂集). 说明:解集不能包含重复的子集. 示例: 输入: nums = [1,2,3] ...

  4. 回溯和递归的区别(简述)

    回溯和递归的区别(简述) 前言 递归和回溯 最后 前言 最近在LeetCode上刷题刷到了递归实现的回溯算法,产生疑惑:这两者有什么区别呢?在网络上阅读了一些相关的文章,总结了一些: 递归和回溯 递归 ...

  5. 漫话算法[回溯]:从《大话西游》到掌握回溯思想!

    快来和叮当学算法吧! From All CV to No CV!我的梦想是编程不再CV,算法不再死记硬背! 漫话算法[看电影学回溯算法]从<大话西游>到掌握回溯算法 回溯算法简介 回溯算法 ...

  6. 回溯算法解子集、组合、排序

    转载labuladong:力扣 代码方面,回溯算法的框架: result = [] def backtrack(路径, 选择列表):if 满足结束条件:result.add(路径)returnfor ...

  7. 【Leetcode】17回溯(电话号码的字母组合)

    1.题目详情 题目分析 自己的想法:需要创建哈希表来存储字母和数字的结构.一开始考虑将输入的digits的长度作为变量,发现自己不会回溯.又看到了提示中限定了长度最多有4个. 0 <= digi ...

  8. python回溯算法

    回溯算法:一种优先搜索算法(试探法):按优条件向前搜索,以达目标:当试探到某步,发现原来选择并不好(走不通),就退回重新选择. 回溯算法的一般步骤:1:定义问题的解空间(搜索中动态生成):2:确定易搜 ...

  9. 数据结构与算法(一)回溯

    数据结构与算法(一)回溯(backtrack) 回溯算法是对树形或者图形结构执行一次深度优先遍历,实际上类似枚举的搜索尝试过程,在遍历的过程中寻找问题的解. 深度优先遍历有个特点:当发现已不满足求解条 ...

  10. 【Leetcode】刷题之路4(python版)

    接上章回溯专题,本章挑选了分割问题.子集问题.排列问题. 分割问题 131.分割回文串 93.复原IP地址 子集问题 78.子集 90.子集II 排列问题 46.全排列 47.全排列II 分割问题 我 ...

最新文章

  1. 安卓收取费用_作为自由职业者应收取的费用:以价值为基础的定价是否能达到炒作的目的?...
  2. 程序员面试题精选100题(06)-二元查找树的后序遍历结果[数据结构]
  3. 中国通货膨胀率2.8%,数据分析买房风险直线上升
  4. 使用计算机的好处,电脑的好处
  5. php foreach循环中unset后续的键值问题
  6. 使用通用mapper实现条件查询_使用dsum轻松搞定多条件查询,学会它,再也想用sumifs了...
  7. mysql 优化 修复原理_mysql下表的修复与优化
  8. 拼多多数据全面解析报告
  9. Google推出了Python最牛逼的编辑器,不看后悔一辈子!
  10. java中如何创建对话框_Java – 如何创建自定义对话框?
  11. SpringMVC开发框架中使用@ResponseBody注解后返回的json字符串中文乱码问题
  12. ST电机库5.4.5FUL版本(ST MC SDK)
  13. 复化科特斯公式matlab_基于牛顿—科特斯积分的误差分析
  14. 【C语言趣味编程100题】
  15. 重新开博,短期目标,工作计划
  16. 认识二进制安全与漏洞攻防技术 (Windows平台)
  17. 二级域名的子域名查看方法及工具
  18. windows通过双网卡双网络(本地网卡和无线网卡双网络同时工作)
  19. ax的范数最大_向量无穷范数为什么是分量绝对值最大者?
  20. Echarts合并多省市地图JSON文件方法

热门文章

  1. python 爬虫视频教学
  2. Android SDK怎么救砖,Android手机刷机变砖后无法开机的修复方法
  3. Spring新手教程
  4. 安卓扫描周围基站信息,获取邻小区频段频点
  5. 怎么查看自己java程序的源码
  6. 编写DBUtil及测试
  7. python提取cad坐标_cad中坐标提取方法(一)
  8. 加密软件VMProtect入门教程
  9. 网易云音乐的所有歌手列表
  10. 日记 - idea中的中文注释出现乱码的解决方案