组合问题
1.组合 力扣77
用for循环来确定第一层,也就是第一个数的位置,用回溯来确定第二个数,终止条件就是大小相等时,这样就可以遍历完每一种可能的情况,剪。枝就把for循环中每次循环不需要的地方缩小范围就可以了。换句话说,如果for循环以后元素的个数已经不足我们要的个数了,就没必要搜索了。
class Solution {
List<List<Integer>> ans = new ArrayList<>();
LinkedList<Integer> path = new LinkedList<>();
public List<List<Integer>> combine(int n, int k) {
backtrack(n,k,1);
return ans;
}
public void backtrack(int n,int k,int start) {
//终止条件
if (path.size() == k) {
ans.add(new ArrayList<>(path));
return;
}
for (int i = start;i <= n - (k - path.size()) + 1;i++) {
path.add(i);
backtrack(n,k,i + 1);
path.removeLast();
}
}
}
2.电话号码的字母组合 力扣17
先用哈希表存储起来,然后用stringbuilder记录临时字符串,设置终止条件为要求长度,之后利用回溯法遍历字符串即可。
class Solution {
List<String> ans = new ArrayList<>();
public List<String> letterCombinations(String digits) {
if (digits.length() == 0) return ans;
Map<Character,String> res = new HashMap<>() {{
put('2',"abc");
put('3',"def");
put('4',"ghi");
put('5',"jkl");
put('6',"mno");
put('7',"pqrs");
put('8',"tuv");
put('9',"wxyz");
}};
backtrack(digits,0,new StringBuilder(),ans,res);
return ans;
}
public void backtrack(String digits,int start,StringBuilder com,List<String> ans,Map<Character,String> res) {
if (start == digits.length()) {
ans.add(com.toString());
return;
}
char ch = digits.charAt(start);//数
String str = res.get(ch);//数对应字符串
for (int i = 0;i < str.length();i++) {
com.append(str.charAt(i));
backtrack(digits,start + 1,com,ans,res);
com.deleteCharAt(start);
}
}
}
3.组合总和 力扣39
此题因为是可以重复的,回溯的指针设置本身即可,并且在for循环内也要设置终止条件,当sum大于给定数时,终止循环。因为已经是排好序的了,后面的都要更大,所以立即break
class Solution {
List<List<Integer>> res = new ArrayList<>();
List<Integer> ans = new ArrayList<>();
public List<List<Integer>> combinationSum(int[] candidates, int target) {
Arrays.sort(candidates);
backtrack(candidates,target,0,0);
return res;
}
public void backtrack(int[] candidates, int target,int index,int sum) {
if (sum == target) {
res.add(new ArrayList<>(ans));
return;
}
for (int i = index;i < candidates.length;i++) {
if (sum + candidates[i] > target) {
break;
}
ans.add(candidates[i]);
backtrack(candidates,target,i,sum + candidates[i]);
ans.remove(ans.size() - 1);
}
}
}
4.组合总和2 40
和组合总和1一样,只不过此题不能用自身。另外要求一个数组内不准重复,我们就利用pre来记录已经遍历过的数,然后加上判断条件,看看是否重复。
class Solution {
List<List<Integer>> res = new ArrayList<>();
List<Integer> ans = new ArrayList<>();
public List<List<Integer>> combinationSum2(int[] candidates, int target) {
Arrays.sort(candidates);
backtrack(candidates,target,0,0);
return res;
}
public void backtrack(int[] candidates, int target,int index,int sum) {
if (sum == target) {
res.add(new ArrayList<>(ans));
return;
}
//单个数组不能出现重复
List<Integer> pre = new ArrayList<>();
for (int i = index;i < candidates.length;i++) {
if (sum + candidates[i] > target) break;
if (pre.contains(candidates[i])) continue;
ans.add(candidates[i]);
pre.add(candidates[i]);
backtrack(candidates,target,i + 1,sum + candidates[i]);
ans.remove(ans.size() - 1);
}
}
}
5.组合总和3 216
此题与上两道类似,但是条件变多了。即需要满足k,又需要满足n。所以判断条件多了两项,分别来判断总和与长度。
class Solution {
List<List<Integer>> result = new ArrayList<>();
List<Integer> path = new ArrayList<>();
public List<List<Integer>> combinationSum3(int k, int n) {
backtrack(k,n,1,0);
return result;
}
public void backtrack(int k,int n,int index,int sum) {
if (path.size() > k) {
return;
}
if (sum > n) {
return;
}
if (path.size() == k) {
if (sum == n) {
result.add(new ArrayList<>(path));
}
}
for (int i = index;i <= 9;i++) {
sum = sum + i;
path.add(i);
backtrack(k,n,i + 1,sum);
sum = sum - i;
path.remove(path.size() - 1);
}
}
}
分割问题
1.分割回文串 131
首先,我们要建立一个布尔来判断是否为回文,用while循环双指针判断即可。然后就是要设置回溯函数,回溯的方法跟组合就一样了,只需要一个起始的索引,for循环遍历即可。
class Solution {
List<List<String>> res = new ArrayList<>();
List<String> ans = new ArrayList<>();
public List<List<String>> partition(String s) {
backtrack(s,0);
return res;
}
public void backtrack(String s,int startindex) {
if (startindex == s.length()) {
res.add(new ArrayList<>(ans));
return;
}
for (int i = startindex;i < s.length();i++) {
if (ishui(s,startindex,i)) {
ans.add(s.substring(startindex,i + 1));
} else {
continue;
}
backtrack(s,i + 1);
ans.remove(ans.size() - 1);
}
}
public boolean ishui(String s,int left,int right) {
while (left < right) {
if (s.charAt(left) != s.charAt(right)) {
return false;
}
left++;
right--;
}
return true;
}
}
2.复原ip地址。力扣93
首先我们要编写一个布尔型来判断截取的是否合法,开头是否不为0,不超过255,已经没有非法字符这三方面。然后这道题的终止条件为分成四份,那么就是三个句号。当三个句号终止。最后就是回溯,还是从一个个的分割开始,然后到最后再回溯。另外也需要时刻判断所分的区间是否合法,不合法马上break跳出循环。
class Solution {
List<String> res = new ArrayList<>();
public List<String> restoreIpAddresses(String s) {
backtrack(s,0,0);
return res;
}
public void backtrack(String s,int pointsum,int startindex) {
if (pointsum == 3) {
if (isvaild(s,startindex,s.length() - 1)) {
res.add(s);
}
return;
}
for (int i = startindex;i < s.length();i++) {
if (isvaild(s,startindex,i)) {
s = s.substring(0,i + 1) + '.' + s.substring(i + 1);
pointsum++;
//因为添加了标点,所以是i + 2
backtrack(s,pointsum,i + 2);
pointsum--;
s = s.substring(0,i + 1) + s.substring(i + 2);
}else {
break;
}
}
}
//判断区段数是否合法
public boolean isvaild(String s,int left,int right) {
if (left > right) return false;
//不能有0开头
if (s.charAt(left) == '0' && left != right) {
return false;
}
//不能有非数字
int sum = 0;
for (int i = left;i <= right;i++) {
if (s.charAt(i) > '9' || s.charAt(i) < '0') {
return false;
}
sum = sum * 10 + (s.charAt(i) - '0');
if (sum > 255) return false;
}
return true;
}
}
子集问题
1.子集 力扣78
子集就是把所有符合条件的都写进去,所以把add条件放在开始,只要符合就加进去,终止条件就是索引超出范围时,就停止。剩下的就是for循环回溯的过程。
class Solution {
List<Integer> path = new ArrayList<>();
List<List<Integer>> res = new ArrayList<>();
public List<List<Integer>> subsets(int[] nums) {
backtrack(nums,0);
return res;
}
public void backtrack(int[] nums,int startindex) {
res.add(new ArrayList<>(path));
if (startindex == nums.length) {
return;
}
for (int i = startindex;i < nums.length;i++) {
path.add(nums[i]);
backtrack(nums,i + 1);
path.remove(path.size() - 1);
}
}
}
2.子集二。 力扣90
第一种方法,设置新列表来记录重复。
class Solution {
List<Integer> path = new ArrayList<>();
List<List<Integer>> res = new ArrayList<>();
public List<List<Integer>> subsetsWithDup(int[] nums) {
Arrays.sort(nums);
if (nums.length == 0) {
return res;
}
backtrack(nums,0);
return res;
}
public void backtrack(int[] nums,int startindex) {
res.add(new ArrayList<>(path));
if (startindex == nums.length) {
return;
}
List<Integer> pre = new ArrayList<>();
for (int i = startindex;i < nums.length;i++) {
if (pre.contains(nums[i])) {
continue;
}
path.add(nums[i]);
pre.add(nums[i]);
backtrack(nums,i + 1);
path.remove(path.size() - 1);
}
}
}
时间复杂度过低,要采用设置布尔数组的方法。
布尔数组中used[i -1] = false,证明同一层用过,used[i - 1] = true 证明同一树枝用过,而此题我们就是要排重同一数层。
class Solution {
List<Integer> path = new ArrayList<>();
List<List<Integer>> res = new ArrayList<>();
boolean[] used;
public List<List<Integer>> subsetsWithDup(int[] nums) {
Arrays.sort(nums);
if (nums.length == 0) {
return res;
}
used = new boolean[nums.length];
backtrack(nums,0);
return res;
}
public void backtrack(int[] nums,int startindex) {
res.add(new ArrayList<>(path));
if (startindex == nums.length) {
return;
}
for (int i = startindex;i < nums.length;i++) {
if (i > 0 && !used[i - 1] && nums[i] == nums[i - 1]) {
continue;
}
path.add(nums[i]);
used[i] = true;
backtrack(nums,i + 1);
path.remove(path.size() - 1);
used[i] = false;
}
}
}
排列问题
首先,排列问题中不需要所谓的startindex了,因为1,2 与2,1属于不同的排列,1用到了两次,不需要所谓的startindex来跳出,所以排列问题要用到user布尔数组,来标记用到的数。
1. 全排列 力扣46
我们的for循环,每次都是要遍历每个nums,从头开始,我们发现1,加入,回溯,再发现1,1已经标记为true,所以跳过依次加入了2,3。然后回溯,删除3,2,后添加了3,继续回溯 跳过了1,3加入了2。以此类推,最后回溯了所有情况的全排列输出即可。
class Solution {
List<List<Integer>> res = new ArrayList<>();
List<Integer> ans = new ArrayList<>();
boolean[] used;
public List<List<Integer>> permute(int[] nums) {
if (nums.length == 0) return res;
used = new boolean[nums.length];
backtrack(nums);
return res;
}
public void backtrack(int[] nums) {
if (ans.size() == nums.length) {
res.add(new ArrayList<>(ans));
return;
}
for (int i = 0;i < nums.length;i++) {
if (used[i] == true) {
continue;
}
used[i] = true;
ans.add(nums[i]);
backtrack(nums);
ans.remove(ans.size() - 1);
used[i] = false;
}
}
}
2.全排列2 力扣47
跟上一题一样,只不过加入了排重, 所有的全排列题,都是树层排重更效率高一些,树层排重就是used[i - 1] = false,剩下的都是全排列1的做法即可
class Solution {
List<Integer> res = new ArrayList<>();
List<List<Integer>> ans = new ArrayList<>();
boolean[] used;
public List<List<Integer>> permuteUnique(int[] nums) {
if (nums.length == 0) return ans;
used = new boolean[nums.length];
Arrays.sort(nums);
Arrays.fill(used,false);
backtrack(nums);
return ans;
}
public void backtrack(int[] nums) {
if (res.size() == nums.length) {
ans.add(new ArrayList<>(res));
return;
}
for (int i = 0;i < nums.length;i++) {
if (i > 0 && nums[i] == nums[i - 1] && used[i - 1] == false) {
continue;
}
if (used[i] == false) {
used[i] = true;
res.add(nums[i]);
backtrack(nums);
res.remove(res.size() - 1);
used[i] = false;
}
}
}
}
棋盘问题
1.n皇后,力扣51
首先n皇后三个条件,就是行,列,斜线不能重合,此道题我们是回溯行,所以行一定不会重复,所以只需要检查三个条件,那就是列,45度,135度
首先建立布尔检查,用for循环来检查列,45度,135度是否已经有了q,如果有了q,就直接进入下一次for循环,也就是下一列。
需要建立一个返回列表的arraylist,用于以行的形式存储棋盘里的字符,然后存储到最终返回的res中。
然后就是建立回溯,首先此题终止条件,应该是行与n相等,此时返回。以回溯为行,for循环为列进行backtrack即可。
class Solution {
List<List<String>> res = new ArrayList<>();
char[][] checkboard;
public List<List<String>> solveNQueens(int n) {
checkboard = new char[n][n];
for (char[] c : checkboard) {
Arrays.fill(c,'.');
}
backtrack(n,0,checkboard);
return res;
}
public void backtrack (int n,int row,char[][] checkboard) {
if (row == n) {
res.add(Array2List(checkboard));
return;
}
for (int col = 0;col < n;col++) {
if (check(n,row,col,checkboard)) {
checkboard[row][col] = 'Q';
backtrack(n,row + 1,checkboard);
checkboard[row][col] = '.';
}
}
}
//把棋盘转换为字符串
public List Array2List (char[][] checkboard) {
List<String> ans = new ArrayList<>();
for (char[] c : checkboard) {
ans.add(String.copyValueOf(c));
}
return ans;
}
public boolean check(int n,int row,int col,char[][] checkboard) {
//检查列
for (int i = 0;i < row;i++) {
if (checkboard[i][col] == 'Q') return false;
}
//检查45度
for (int i = row - 1,j = col - 1;i >= 0 && j >= 0;i--,j--) {
if (checkboard[i][j] == 'Q') return false;
}
//检查135度
for (int i = row - 1,j = col + 1;i >= 0 && j <= n - 1;i--,j++) {
if (checkboard[i][j] == 'Q') return false;
}
return true;
}
}