最近把LeetCode上的backTracking的题目做了一下,发现都是一个套路~
backTracking链接:https://leetcode.com/tag/backtracking/
还有几道比较难的Medium的题和Hard的题没做出来,后面会继续更新和加详细解法解释~
文章目录
- [78. Subsets 回溯的入门之子集](https://leetcode.com/problems/subsets/)
- [90. Subsets II](https://leetcode.com/problems/subsets-ii/)
- [46. Permutations 排列 ](https://leetcode.com/problems/permutations/)
- [77. Combinations](https://leetcode.com/problems/combinations/)
- [39. Combination Sum](https://leetcode.com/problems/combination-sum/)
- [40. Combination Sum II](https://leetcode.com/problems/combination-sum-ii/)
- [216. Combination Sum III](https://leetcode.com/problems/combination-sum-iii/)
- [784. Letter Case Permutation](https://leetcode.com/problems/letter-case-permutation/)
- [17. Letter Combinations of a Phone Number](https://leetcode.com/problems/letter-combinations-of-a-phone-number/)
- [22. Generate Parentheses 括号生成器](https://leetcode.com/problems/generate-parentheses/)
- [131. Palindrome Partitioning 分割回文字串](https://leetcode.com/problems/palindrome-partitioning/)
- [52. N-Queens II N皇后](https://leetcode.com/problems/n-queens-ii/)
- [51. N-Queens n皇后](https://leetcode.com/problems/n-queens/)
- [526. Beautiful Arrangement 漂亮排列](https://leetcode.com/problems/beautiful-arrangement/description/)
78. Subsets 回溯的入门之子集
返回数组的所有子集。
这种题目都是使用这个套路,就是用一个循环去枚举当前所有情况,然后把元素加入,递归,再把元素移除
按照这个套路来做,可以解决backTracking的问题
list.add(nums[i]); // 第三步 元素加入临时集合
backTracking(res, list, nums, i + 1); // 第四步 回溯
list.remove(list.size() - 1); // 第五步 元素从临时集合移除
这三句的意思是:
假设结果需要cand [i],然后继续使用另一个调用backTracking, 然后我么你不需要cand [i]了,只有两种情况,要或者不要,可以尝试。
public class Solution {
public List<List<Integer>> subsets(int[] nums) {
List<List<Integer>> res = new ArrayList<>();
backTracking(res, new ArrayList<>(), nums, 0);
return res;
}
private void backTracking(List<List<Integer>> res, List<Integer> list, int[] nums, int start){
// 第0步,加入停止条件
res.add(new ArrayList<>(list)); // 第一步 满足条件的临时集合加入结果集
for(int i = start; i < nums.length; ++i){ // 第二步for循环 遍历所有的元素
list.add(nums[i]); // 第三步 元素加入临时集合
backTracking(res, list, nums, i + 1); // 第四步 回溯
list.remove(list.size() - 1); // 第五步 元素从临时集合移除
}
}
}
90. Subsets II
返回数组(里面有重复的数字)的不含重复结果的子集
public class Solution {
public List<List<Integer>> subsetsWithDup(int[] nums) {
List<List<Integer>> res = new ArrayList<>();
Arrays.sort(nums);
backTracking(res, new ArrayList<>(), nums, 0);
return res;
}
private void backTracking(List<List<Integer>> res, List<Integer> list, int[] nums, int start){
if(!res.contains(list)) res.add(new ArrayList<>(list)); //检查是否包含结果,不包含才加入
for(int i = start; i < nums.length; ++i){
list.add(nums[i]);
backTracking(res, list, nums, i + 1);
list.remove(list.size() - 1);
}
}
}
46. Permutations 排列
给你一个数字不重复的数组,求这个数组的所有排列
public class Solution {
public List<List<Integer>> permute(int[] nums) {
List<List<Integer>> res = new ArrayList<>();
backTracking(res, new ArrayList<>(), nums);
return res;
}
private void backTracking(List<List<Integer>> res, List<Integer> list, int[] nums){
if(list.size() == nums.length) res.add(new ArrayList<>(list));
for(int i = 0; i < nums.length; ++i){
if(list.contains(nums[i])) continue; // 跳过重复的,不然会出现[nums[0], nums[0], nums[0]] 这种情况
list.add(nums[i]);
backTracking(res, list, nums);
list.remove(list.size() - 1);
}
}
}
77. Combinations
给你两个整数n和k,返回在1到n个数里面所有可能的k个数字的组合。
public class Solution {
public List<List<Integer>> combine(int n, int k) {
List<List<Integer>> res = new ArrayList<>();
backTracking(res, new ArrayList<>(), n, k, 1);
return res;
}
private void backTracking(List<List<Integer>> res, List<Integer> list, int n, int k, int start){
if(list.size() == k) res.add(new ArrayList(list));
for(int i = start; i <= n; ++i){
list.add(i);
backTracking(res, list, n, k, i + 1);
list.remove(list.size() - 1);
}
}
}
39. Combination Sum
给定一个数组candidates,和一个整数target,返回所有candidates里面元素的组合的和等于target。对candidates里面的元素出现的次数无限制。
例如:
candidate : [2, 3, 6, 7] | target: 7
返回:
public class Solution {
public List<List<Integer>> combinationSum(int[] candidates, int target) {
List<List<Integer>> res = new ArrayList<>();
Arrays.sort(candidates);
backTracking(res, new ArrayList<>(), candidates, target, 0);
return res;
}
private void backTracking(List<List<Integer>> res, List<Integer> list, int[] candidate, int target, int start){
if(target < 0) return;
if(target == 0) res.add(new ArrayList<>(list));
else {
for(int i = start; i < candidate.length; ++i){
list.add(candidate[i]);
backTracking(res, list, candidate, target - candidate[i], i);
list.remove(list.size() - 1);
}
}
}
}
40. Combination Sum II
给定一个数组candidates,和一个整数target,返回所有candidates里面元素的组合的和等于target。对candidates里面的元素最多只能出现一次。
意思是数组里面的数字智只能用一次,而不是不能用里面重复的数字。
例如:
candidate : [10, 1, 2, 7, 6, 1, 5]
target: 8
返回:
if(i > start && candidate[i] == candidate[i - 1]) continue;
这句是用来跳过重复的数字的。当然也可以通过在将符合条件的候选数组加入结果的时候判断是否已经含有重复的,但是这样很慢。
public class Solution {
public List<List<Integer>> combinationSum2(int[] candidates, int target) {
List<List<Integer>> res = new ArrayList<>();
Arrays.sort(candidates);
backTracking(res, new ArrayList<>(), candidates, target, 0);
return res;
}
private void backTracking(List<List<Integer>> res, List<Integer> list, int[] candidate, int target, int start){
if(target < 0) return;
if(target == 0) res.add(new ArrayList<>(list));
else {
for(int i = start; i < candidate.length; ++i){
if(i > start && candidate[i] == candidate[i - 1]) continue;
list.add(candidate[i]);
backTracking(res, list, candidate, target - candidate[i], i + 1);
list.remove(list.size() - 1);
}
}
}
}
216. Combination Sum III
找到所有可能的k个数字组合,它们加起来为n,假设只能使用1到9的数字,并且每个组合应该是一组唯一的数字。
public class Solution {
public List<List<Integer>> combinationSum3(int k, int n) {
List<List<Integer>> res = new ArrayList<>();
backTracking(res, new ArrayList<>(), k, n, 1);
return res;
}
private void backTracking(List<List<Integer>> res, List<Integer> list, int k, int n, int start){
if(n < 0) return;
if(n == 0 && list.size() == k) res.add(new ArrayList<>(list));
for(int i = start; i <= 9; i++){
list.add(i);
backTracking(res, list, k, n-i, i+1);
list.remove(list.size()-1);
}
}
}
784. Letter Case Permutation
给定一个字符串s,我们可以将这个字符串里面的每一个字符转换成大写或者小写,返回所有我们可能生成的字符。
拿s=abc举例,backtracking如下图所示:
class Solution {
public List<String> letterCasePermutation(String s) {
if(s == null || s == "") return new ArrayList<String>();
List<String> res = new ArrayList<String>();
backTracking(s, res, 0);
return res;
}
public void backTracking(String s, List<String> res, int i){
// 第0步:停止条件
// 第1步:满足条件的临时集合加入结果集
if(i == s.length()){
res.add(s);
return;
}
if(s.charAt(i) >= '0' && s.charAt(i) <= '9'){
backTracking(s, res, i+1);
return; // 下面的就不要执行了
}
// 下面两个相当于for循环
char[] cs = s.toCharArray();
cs[i] = Character.toLowerCase(cs[i]);
backTracking(String.valueOf(cs), res, i+1); // 继续回溯
cs[i] = Character.toUpperCase(cs[i]);
backTracking(String.valueOf(cs), res, i+1);
}
}
faster than 79.59% of Java online submissions for Letter Case Permutation.
17. Letter Combinations of a Phone Number
手机键盘拨号,给你一串数字,问对应手机九宫格的输入法有可能输出那些字符串。
手机键盘:
输入:
“23”
输出:
[“ad”, “ae”, “af”, “bd”, “be”, “bf”, “cd”, “ce”, “cf”]
public class Solution {
private String[] letter = new String[] {" ", "1", "abc", "def", "ghi", "jkl", "mno", "pqrs", "tuv", "wxyz"};
public List<String> letterCombinations(String digits) {
List<String> res = new ArrayList<>();
if(digits == null || digits.length() == 0) return res;
char[] nums = digits.toCharArray();
backTracking(res, new StringBuilder(), nums, 0);
return res;
}
private void backTracking(List<String> res, StringBuilder build, char[] nums, int start){
if(build.length() == nums.length) res.add(build.toString());
for(int i = start; i < nums.length; ++i){
int index = Integer.parseInt(String.valueOf(nums[i]));
for(int j = 0; j < letter[index].length(); ++j){
build.append(letter[index].charAt(j));
backTracking(res, build, nums, i + 1);
build.deleteCharAt(build.length() - 1);
}
}
}
}
22. Generate Parentheses 括号生成器
public class Solution {
public List<String> generateParenthesis(int n) {
List<String> res = new ArrayList<>();
backTracking(res, new StringBuilder(), 0, 0, n);
return res;
}
private void backTracking(List<String> res, StringBuilder builder, int left, int right, int n){
if(builder.length() == n*2) res.add(builder.toString());
else {
if(left < n){
builder.append("(");
backTracking(res, builder, left+1, right, n);
builder.deleteCharAt(builder.length()-1);
}
if(right < n && left > right){
builder.append(")");
backTracking(res, builder, left, right+1, n);
builder.deleteCharAt(builder.length()-1);
}
}
}
}
131. Palindrome Partitioning 分割回文字串
给你一个字符串s,分割字符串s使得每个子字符串都是回文,返回所有的分割结果。
如s = “aab”
返回:
[
[“aa”,“b”],
[“a”,“a”,“b”]
]
public class Solution {
public List<List<String>> partition(String s) {
List<List<String>> res = new ArrayList<>();
backTracking(res, new ArrayList<>(), s, 0);
return res;
}
private void backTracking(List<List<String>> res, List<String> list, String s, int start){
if(start == s.length()) res.add(new ArrayList<>(list)); //start == s.length()的时候,说明已经带s的最后一个字符了
else {
for(int i = start; i < s.length(); i++){
if(isPalindrome(s, start, i)){
list.add(s.substring(start, i+1));
backTracking(res, list, s, i+1);
list.remove(list.size() - 1);
}
}
}
}
/*
* 判断是否是回文
*/
private boolean isPalindrome(String s, int low, int high){
while(low < high){
if(s.charAt(low++) != s.charAt(high--)) return false;
}
return true;
}
}
52. N-Queens II N皇后
返回n皇后有多少种摆法。
n皇后问题是指在
的棋盘上要摆n个皇后,要求任何皇后不同行,不同列,不在一条直线上。
有了 Palindrome Partitioning 分割回文字串 的基础,我们就可以解决经典的 N-皇后 的问题了:
用一个数组queen保存已经摆放皇后的位置,queen[i]的值表示第i行皇后所在的列数。
class Solution {
int count = 0;
public int totalNQueens(int n) {
if(n < 1) return 0;
int[] queen = new int[n];
backTracking(queen, n, 0);
return count;
}
public void backTracking(int[] queen, int n, int start){
if(start == n){
count++;
return;
}
for(int j=0; j < n; j++){
if(check(queen, start, j)){
queen[start] = j;
backTracking(queen, n, start+1);
}
}
}
public boolean check(int[] queen, int row, int col){
for(int i = 0; i < row; i++){
int queenRow = queen[i];
if(queenRow == col) return false;
if(Math.abs(queenRow-col) == Math.abs(i-row)) return false;
}
return true;
}
}
faster than 52.25% of Java online submissions for N-Queens II.
51. N-Queens n皇后
设计一种算法,打印n皇后在n*n棋盘上的各种摆法,其中每个皇后都不同行,不同列,也不在对角线上。
public class Solution {
public List<List<String>> solveNQueens(int n) {
List<Integer[]> res = new ArrayList<>();
backTracking(res, new Integer[n], n, 0);
//以下只是把皇后放置在棋盘上
List<List<String>> resList = new ArrayList<>();
for(Integer[] nums: res){
List<String> list = new ArrayList<>();
for(int i = 0; i < nums.length; i++){
StringBuilder sb = new StringBuilder();
for(int j = 0; j < n; j++){
if(j == nums[i]) sb.append("Q");
else sb.append(".");
}
list.add(sb.toString());
}
resList.add(list);
}
return resList;
}
/*
* queen[] 是表示:下表i代表第i行的第queen[i]列放置一个皇后
*/
private void backTracking(List<Integer[]> res, Integer[] queen, int n, int start){
if(start == n) res.add(queen.clone());
else {
for(int i = 0; i < n; i++){ //不是从start开始
if(checkValid(queen, start, i)){
queen[start] = i;
backTracking(res, queen, n, start+1); //不是i+1,i只是和列有关
}
}
}
}
/*
* 检查在位置(row1,col1)是否可以放置皇后
*/
private boolean checkValid(Integer[] queen, int row1, int col1){
for(int row2 = 0; row2 < row1; row2++){
int col2 = queen[row2];
if(col2 == col1) return false; //两个皇后放置在同一列上
int colSize = Math.abs(col1 - col2);
int rowSize = row1 - row2;
if(colSize == rowSize) return false; //两个皇后放置在同一对角线上
}
return true;
}
}
526. Beautiful Arrangement 漂亮排列
假设你有从1到N的N个整数。如果在这个数组中第i个位置(1 <= i <= N)的下列之一为真,我们将这个N个数字成功构造的数组定义为一个漂亮的排列:
- 第i个位置的数字可以被i整除。
- i可以被第i个位置的数字整除。
现在给出N,你可以建造多少漂亮排列?
一开始的时候我是穷举所有的排列,再去看这些排列是否是漂亮排列,结果超时了,因为时间复杂度是
Time Limit Exceeded
超时的代码:
class Solution {
public int countArrangement(int n) {
List<List<Integer>> res = new ArrayList<>();
backTracking(res, new ArrayList<>(), n);
return res.size();
}
public void backTracking(List<List<Integer>> res, List<Integer> temp, int n){
if(temp.size() == n){
if(isBeautiful(temp)){
res.add(new ArrayList<>(temp));
}
return;
}
for(int i = 1; i <= n; i++){
if(temp.contains(i)) continue;
temp.add(i);
backTracking(res, temp, n);
temp.remove(temp.size()-1);
}
}
public boolean isBeautiful(List<Integer> list){
for(int i = 1; i <= list.size(); i++){
if(i%list.get(i-1) != 0 && list.get(i-1)%i != 0) return false;
}
return true;
}
}
然后稍微修改一下,在把每一个元素加进排列的时候就去检查时候满足漂亮排列的要求,这样就能减少时间了:
class Solution {
public int countArrangement(int n) {
List<List<Integer>> res = new ArrayList<>();
int[] nums = new int[n];
for(int i = 1; i <= n; i++){
nums[i-1] = i;
}
backTracking(res, new ArrayList<>(), nums, 1);
return res.size();
}
public void backTracking(List<List<Integer>> res, List<Integer> temp, int[] nums, int index){
if(temp.size() == nums.length){
res.add(new ArrayList<>(temp));
return;
}
for(int i = 0; i < nums.length; i++){
if(temp.contains(nums[i])) continue;
if(index%nums[i] == 0 || nums[i]%index == 0){
temp.add(nums[i]);
backTracking(res, temp, nums, index+1);
temp.remove(temp.size()-1);
}
}
}
}
faster than 2.82% of Java online submissions for Beautiful Arrangement.不过这也太慢了一点。后面还要看看其他方法。