前言
回溯是我的薄弱点,所以写点杀卡尔的题目
首先来看杀卡写的模板吧,有一说一还挺有用
void backtracking(参数) {
if (终止条件) {
存放结果;
return;
}
for (选择:本层集合中元素(树中节点孩子的数量就是集合的大小)) {
处理节点;
backtracking(路径,选择列表); // 递归
回溯,撤销处理结果
}
}
77. 组合 - 力扣(LeetCode)
这个还是简单的,但我还是没剪枝!
class Solution {
//组合问题呢,就得有starindex了,因为不能重复啊
ArrayList<Integer>path=new ArrayList<>();
List<List<Integer>>res=new ArrayList<>();
public List<List<Integer>> combine(int n, int k) {
back(n, k, 1);
return res;
}
void back(int n,int k,int starIndex)
{
if(path.size()==k)
{
res.add(new ArrayList<>(path));
return;
}
for(int i=starIndex;i<=n;i++)
{
path.add(i);
back(n, k, i+1);
path.remove(path.size()-1);
}
}
}
剪枝:
class Solution {
//组合问题呢,就得有starindex了,因为不能重复啊
ArrayList<Integer>path=new ArrayList<>();
List<List<Integer>>res=new ArrayList<>();
public List<List<Integer>> combine(int n, int k) {
back(n, k, 1);
return res;
}
void back(int n,int k,int starIndex)
{
if(path.size()==k)
{
res.add(new ArrayList<>(path));
return;
}
for(int i=starIndex;i<=n-(k-path.size())+1;i++)//就是这里变了,
{
path.add(i);
back(n, k, i+1);
path.remove(path.size()-1);
}
}
}
17. 电话号码的字母组合 - 力扣(LeetCode)
我一开始的误区是什么
我一开始写成了
void back(String digits,String[]numString,int starIndex,int num)
{
if(num==digits.length())
{
list.add(temp.toString());
return;
}
for(int i=starIndex;i<numString[i].length();i++)
{
temp.append(numString[num].charAt(i));
back(digits, numString, , num);
}
}
我想的是,”ab”,”cd”,然后每个里面也需要去重,
但是其实是不需要的,因为你ac,ae,agxxxxx都是算的,所以不需要starindex
正解:
class Solution {
List<String> list = new ArrayList<>();
StringBuilder temp = new StringBuilder();
public List<String> letterCombinations(String digits) {
if (digits == null || digits.length() == 0) {
return list;
}
String[] numString = {"", "", "abc", "def", "ghi", "jkl", "mno", "pqrs", "tuv", "wxyz"};//建立一个一一对应关系
back(digits, numString, 0);
return list;
}
//一个组合问题,这边不用starIndx,因为你ad,ag,aj都算的啊
void back(String digits,String[]numString,int num)
{
if(num==digits.length())
{
list.add(temp.toString());
return;
}
String str=numString[digits.charAt(num)-'0'];
for(int i=0;i<str.length();i++)
{
temp.append(str.charAt(i));
back(digits, numString, num+1);
temp.deleteCharAt(temp.length()-1);
}
}
}
39. 组合总和 - 力扣(LeetCode)
自己写居然一遍过,太屌了
class Solution {
ArrayList<Integer>path=new ArrayList<>();
List<List<Integer>>res=new ArrayList<>();
public List<List<Integer>> combinationSum(int[] candidates, int target) {
back(candidates, target, 0,0);
return res;
}
//有一说一这个里面隐藏了剪枝问题,写到了再说
//然后可以重复选取,那么就可以从0开始
void back(int []candidates,int target,int sum,int startIndex)//sum是当前和
{
//返回条件
if(sum>target)
{
return;
}
else if(sum==target)
{
res.add(new ArrayList<>(path));
return;
}
for(int i=startIndex;i<candidates.length;i++)
{
sum+=candidates[i];
path.add(candidates[i]);
back(candidates, target, sum,i);
sum-=candidates[i];
path.remove(path.size()-1);
}
}
}
但我知道是可以剪枝的,如何剪枝呢
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, 0);
return res;
}
public void backtracking(List<List<Integer>> res, List<Integer> path, int[] candidates, int target, int sum, int idx) {
// 找到了数字和为 target 的组合
if (sum == target) {
res.add(new ArrayList<>(path));
return;
}
for (int i = idx; i < candidates.length; i++) {
// 如果 sum + candidates[i] > target 就终止遍历
if (sum + candidates[i] > target) break;
path.add(candidates[i]);
backtracking(res, path, candidates, target, sum + candidates[i], i);
path.remove(path.size() - 1); // 回溯,移除路径 path 最后一个元素
}
}
}
如果大于了就直接终止遍历,注意这样得先排序
40. 组合总和 II - 力扣(LeetCode)
这个去重已经是套路了
class Solution {
ArrayList<Integer>path=new ArrayList<>();
List<List<Integer>>res=new ArrayList<>();
int sum=0;
boolean[]isvisited;
public List<List<Integer>> combinationSum2(int[] candidates, int target) {
Arrays.sort(candidates);
isvisited=new boolean[candidates.length];
back(candidates, target, 0);
return res;
}
void back(int []candidates,int target,int startIndex)
{
if(sum==target)
{
res.add(new ArrayList<>(path));
return;
}
for(int i=startIndex;i<candidates.length;i++)
{
if(sum+candidates[i]>target)
{
break;
}
if(i>0&&candidates[i]==candidates[i-1]&&!isvisited[i-1])
{
continue;
}
isvisited[i]=true;
sum+=candidates[i];
path.add(candidates[i]);
back(candidates, target, i+1);
isvisited[i]=false;
sum-=candidates[i];
path.remove(path.size()-1);
}
}
}
131. 分割回文串 - 力扣(LeetCode)
我一开始的误区是,没有看清题,没看到是要分割,还以为是找到所有的回文字符串
然后分割的具体,是startIndex作为第一刀,i作为第二刀,这个思路要会
class Solution {
List<List<String>> lists = new ArrayList<>();
Deque<String> deque = new LinkedList<>();
public List<List<String>> partition(String s) {
back(s, 0);
return lists;
}
private boolean isPalindrome(String s, int startIndex, int end) {
for (int i = startIndex, j = end; i < j; i++, j--) {
if (s.charAt(i) != s.charAt(j)) {
return false;
}
}
return true;
}
void back(String s ,int startIndex)//以starx开始分割,i作为分割的右边界
{
if(startIndex>=s.length())
{
lists.add(new ArrayList<>(deque));
return;
}
for(int i=startIndex;i<s.length();i++)
{
if(isPalindrome(s, startIndex, i))
{
String str=s.substring(startIndex,i+1);
deque.add(str);
}else
{
continue;
}
back(s, i+1);
deque.removeLast();
}
}
}
78. 子集 - 力扣(LeetCode)
这题比较简单,注意了,为什么add刀res的操作不在if里面呢
因为我们本来就是要无条件的把所有的情况全加进去,所以直接add就行了
class Solution {
//子集问题,这个就比较简单了,只要记得starIndex就行了
ArrayList<Integer>path=new ArrayList<>();
List<List<Integer>>res=new ArrayList<>();
public List<List<Integer>> subsets(int[] nums) {
back(nums, 0);
return res;
}
void back(int []nums,int starIndex)
{
res.add(new ArrayList<>(path));
if(starIndex>=nums.length)
{
return;
}
for(int i=starIndex;i<nums.length;i++)
{
path.add(nums[i]);
back(nums, i+1);
path.remove(path.size()-1);
}
}
}
90. 子集 II - 力扣(LeetCode)
也是去重问题,和组合总和II一个套路
class Solution {
ArrayList<Integer>path=new ArrayList<>();
List<List<Integer>>res=new ArrayList<>();
boolean []isused;
public List<List<Integer>> subsetsWithDup(int[] nums) {
isused=new boolean[nums.length];
Arrays.sort(nums);
back(nums, 0);
return res;
}
void back(int []nums,int starIndex)
{
res.add(new ArrayList<>(path));//但是这里也是一样的,子集问题存所有的情况,所以先add就行了
if(starIndex>=nums.length)
{
return;
}
for(int i=starIndex;i<nums.length;i++)
{
if(i>0&&nums[i]==nums[i-1]&&!isused[i-1])
{
continue;
}
path.add(nums[i]);
isused[i]=true;
back(nums, i+1);
path.remove(path.size()-1);
isused[i]=false;
}
}
}
491. 递增子序列 - 力扣(LeetCode)
我一开始写的:
if(startIndex>=nums.length)
{
res.add(new ArrayList<>(path));
return;
}
这样对吗?这样不对啊,因为你是要存入[4,7],[4,7,7],[4,7,7,8],所以你不需要返回,而是路径上的所有节点,都得被存入res里
所以按道理来说不写终止条件是可以的,就和子集问题一样
正确的情况应该是:
if(path.size()>1)
{
res.add(path);
}
然后这里还有一个hs不remove的为什么
class Solution {
//很明显要starIndex
ArrayList<Integer>path=new ArrayList<>();
List<List<Integer>> res=new ArrayList<>();
public List<List<Integer>> findSubsequences(int[] nums) {
back(nums, 0);
return res;
}
void back(int []nums,int startIndex)
{
if(path.size()>1)
{
res.add(new ArrayList<>(path));
}
HashSet<Integer>hs=new HashSet<>();
for(int i=startIndex;i<nums.length;i++)
{
if(!path.isEmpty()&&path.get(path.size()-1)>nums[i]||hs.contains(nums[i]))
{
continue;
}
hs.add(nums[i]);
path.add(nums[i]);
back(nums, i+1);
path.remove(path.size()-1);//为什么hs这里没有remove呢,因为你hs并不是全局变量,而是只记录每一层,相当于每一层一个hs
}
}
}
46. 全排列 - 力扣(LeetCode)
排列吗,那就不用starIndex了,每次从头开始,但是你得跳过已经在path里面的,所以需要used
鉴定为易啊
class Solution {
List<List<Integer>> result = new ArrayList<>();// 存放符合条件结果的集合
LinkedList<Integer> path = new LinkedList<>();// 用来存放符合条件结果
boolean[] used;
public List<List<Integer>> permute(int[] nums) {
used=new boolean[nums.length];
back(nums);
return result;
}
void back(int []nums)
{
if(path.size()==nums.length)
{
result.add(new ArrayList<>(path));
return;
}
for(int i=0;i<nums.length;i++)
{
if(used[i])
{
continue;
}
used[i]=true;
path.add(nums[i]);
back(nums);
used[i]=false;
path.remove(path.size()-1);
}
}
}
47. 全排列 II - 力扣(LeetCode)
怎么去重和子集问题是一个套路
class Solution {
//存放结果
List<List<Integer>> result = new ArrayList<>();
//暂存结果
List<Integer> path = new ArrayList<>();
public List<List<Integer>> permuteUnique(int[] nums) {
boolean []used=new boolean[nums.length];
Arrays.sort(nums);
back(nums, used);
return result;
}
void back(int[]nums,boolean[]used)
{
if(path.size()==nums.length)
{
result.add(new ArrayList<>(path));
return;
}
for(int i=0;i<nums.length;i++)
{
if(i>0&&nums[i]==nums[i-1]&&!used[i-1])
{
continue;
}
//这里还得判断自己用没用过
if(used[i]==false)
{
used[i]=true;
path.add(nums[i]);
back(nums, used);
path.remove(path.size()-1);
used[i]=false;
}
}
}
}
51. N 皇后 - 力扣(LeetCode)
有一说一难点不在思考本身,而是在处理二维数组和check
class Solution {
//思路:首先棋盘是二维数组,但是输出形式是每个棋盘是一维数组,所以需要二维转一维String数组的函数
//然后需要检查斜线,所以还需要check函数
//然后back,应该是一行一行放
List<List<String>> res = new ArrayList<>();
public List<List<String>> solveNQueens(int n) {
char[][] board = new char[n][n];
for (char[] c : board) {
Arrays.fill(c, '.');
}
back(n, 0, board);
return res;
}
void back(int n,int row,char[][]board)
{
if(row==n)
{
res.add(Array2List(board));
return;
}
for(int i=0;i<n;i++)
{
if(isValid(row, i, n, board))
{
board[row][i]='Q';
back(n, row+1, board);
board[row][i]='.';//回溯
}
}
}
public boolean isValid(int row,int col,int n,char[][]board)
{
for(int i=0;i<row;i++)
{
if(board[i][col]=='Q')
return false;
}
for(int j=0;j<row;j++)
{
if(board[row][j]=='Q')
return false;
}
for(int i=row-1,j=col-1;i>=0&&j>=0;i--,j--)
{
if(board[i][j]=='Q')
return false;
}
for(int i=row-1,j=col+1;i>=0&&j<=n-1;i--,j++)
{
if(board[i][j]=='Q')
return false;
}
return true;
}
public List Array2List(char[][]board)
{
List<String> list=new ArrayList<>();
for(char[]c:board)
{
list.add(String.copyValueOf(c));
}
return list;
}
}