46. 全排列 - 力扣(LeetCode)
用used记录是否访问过
class Solution {
List<List<Integer>> result = new ArrayList<>();
List<Integer> temp = new ArrayList<>();
public List<List<Integer>> permute(int[] nums) {
boolean[] used=new boolean[nums.length];
backtrack(nums,used);
return result;
}
private void backtrack(int[] nums,boolean[] used)
{
//终止条件
if(temp.size()==nums.length){
result.add(new ArrayList<>(temp));
return;
}
for(int i=0;i<nums.length;i++)
{
if(used[i]==false)
{
temp.add(nums[i]);
used[i]=true;
backtrack(nums,used);
//回溯 恢复状态
used[i]=false;
temp.remove(temp.size()-1);
}
}
}
}
78. 子集 - 力扣(LeetCode)
这道题和上一道的区别在于
backtrack中把temp加入到result是无条件的,也就说,状态树上每一个结点的temp都要放入。
组合相比排列是无序的,因此进阶版可能出现需要去重。
class Solution {
List<List<Integer>> result=new ArrayList<>();
List<Integer> temp=new ArrayList<>();
public List<List<Integer>> subsets(int[] nums) {
backtrack(nums,0);
return result;
}
private void backtrack(int[] nums,int start)
{
//start的作用是防止重复
result.add(new ArrayList<>(temp));//每一个节点的temp都放到result
for(int i=start;i<nums.length;i++)
{
temp.add(nums[i]);
backtrack(nums,i+1);//因为要去i之后的树枝 不是start+1哦!
temp.remove(temp.size()-1);
}
}
}
39. 组合总和 - 力扣(LeetCode)
class Solution {
int sum=0;
List<List<Integer>> result=new ArrayList<>();
List<Integer> temp=new ArrayList<>();
public List<List<Integer>> combinationSum(int[] candidates, int target) {
backtrack(candidates,target,0);
return result;
}
private void backtrack(int[] candidates, int target,int start)
{
if(sum==target){
result.add(new ArrayList<>(temp));
return;
}else if(sum>target){
return;
}
for(int i=start;i<candidates.length;i++)
{
sum+=candidates[i];
temp.add(candidates[i]);
backtrack(candidates,target,i);//这里i是限制往下的枝子不能出现前面遍历过的节点 其实是在进行同层的剪枝
temp.remove(temp.size()-1);
sum-=candidates[i];
}
}
}
可以通过提前排序candidates进行剪枝
class Solution {
int sum=0;
List<List<Integer>> result=new ArrayList<>();
List<Integer> temp=new ArrayList<>();
public List<List<Integer>> combinationSum(int[] candidates, int target) {
Arrays.sort(candidates);//设置成有序数组
backtrack(candidates,target,0);
return result;
}
private void backtrack(int[] candidates, int target,int start)
{
if(sum==target){
result.add(new ArrayList<>(temp));
return;
}
for(int i=start;i<candidates.length;i++)
{
if(sum+candidates[i]>target)
{
//判断这里不要sum+=candidates[i]哦,不然忘记减去会导致后面的所有都不满足条件
break;//这一层都不用往下循环了
}
sum+=candidates[i];
temp.add(candidates[i]);
backtrack(candidates,target,i);//这里i是限制往下的枝子不能出现前面遍历过的节点 其实是在进行同层的剪枝
temp.remove(temp.size()-1);
sum-=candidates[i];
}
}
}
17. 电话号码的字母组合 - 力扣(LeetCode)
比较麻烦的就是要通过
1.p去记录遍历的深度 树的每一层都是要在不同的集合里面取值 这个不同的集合就是靠p遍历digits,找到对应的集合。
class Solution {
List<String> result=new ArrayList<>();
StringBuilder tmp=new StringBuilder();
public List<String> letterCombinations(String digits) {
if(digits==null||digits.length()==0) return result;
String[] numString={"","","abc","def","ghi","jkl","mno","pqrs","tuv","wxyz"};
backtracking(digits,numString,0);
return result;
}
private void backtracking(String digits,String[] numString,int p)
{
if(tmp.length()==digits.length()){
result.add(tmp.toString());
return;
}
//p是用来遍历digits的指针 同时也是递归树的深度 取出与digits[p]中数字相对应的字符串
String s=numString[digits.charAt(p)-'0'];
for(int i=0;i<s.length();i++)
{
tmp.append(s.charAt(i));
backtracking(digits,numString,p+1);//因为是两个数字对应的字母总和,这里找到一个数字的了 要去找另一个数字 应该是p+1 而不是i+1
tmp.deleteCharAt(tmp.length()-1);
}
}
}
22. 括号生成 - 力扣(LeetCode)
方法一:深度优先遍历
我们以 n = 2
为例,画树形结构图。方法是 「做减法」。
可以生左枝的条件是左括号剩余数>0
可以生右枝的条件是现存右括号数量>现存左括号数量(有左括号和他匹配
达到叶子节点条件是:左右都剩余0个
剪枝条件是剩余的左括号比右括号多
class Solution {
public List<String> generateParenthesis(int n) {
List<String> res=new ArrayList<>();
if(n==0) return res;
backtrack("",n,n,res);
return res;
}
private void backtrack(String cur,int left,int right,List<String> res)
{
//终止条件是左边剩余括号和右边剩余括号都为0
if(right==0&&left==0)
{
res.add(cur);
return;
}
// 剪枝(如图,左括号可以使用的个数严格大于右括号可以使用的个数,才剪枝,注意这个细节)
if (left > right) {
return ;
}
if(left>0)
{
backtrack(cur+"(",left-1,right,res);
}
if(right>0)
{
backtrack(cur+")",left,right-1,res);
}
}
}
*79. 单词搜索 - 力扣(LeetCode)
算法解析:
递归参数: 当前元素在矩阵 board 中的行列索引 i 和 j ,当前目标字符在 word 中的索引 k 。
终止条件:
返回 false : (1) 行或列索引越界 或 (2) 当前矩阵元素与目标字符不同 或 (3) 当前矩阵元素已访问过 ( (3) 可合并至 (2) ) 。
返回 true : k = len(word) - 1 ,即字符串 word 已全部匹配。
递推工作:
标记当前矩阵元素: 将 board[i][j] 修改为 空字符 '' ,代表此元素已访问过,防止之后搜索时重复访问。
搜索下一单元格: 朝当前元素的 上、下、左、右 四个方向开启下层递归,使用 或 连接 (代表只需找到一条可行路径就直接返回,不再做后续 DFS ),并记录结果至 res 。
还原当前矩阵元素: 将 board[i][j] 元素还原至初始值,即 word[k] 。
返回值: 返回布尔量 res ,代表是否搜索到目标字符串。
class Solution {
List<Character> temp=new ArrayList<>();
List<List<Character>> result=new ArrayList<>();
public boolean exist(char[][] board, String word) {
char[] words = word.toCharArray();
for(int i = 0; i < board.length; i++) {
for(int j = 0; j < board[0].length; j++) {
if (dfs(board, words, i, j, 0)) return true;
}
}
return false;
}
private boolean dfs(char[][] board,char[] words,int i,int j,int k)
{
//判断边界以及当前点不符合words的情况
if (i >= board.length || i < 0 || j >= board[0].length || j < 0 || board[i][j] != words[k]) return false;
//遍历结束的情况
if (k == words.length - 1) return true;
//当前矩阵值与word[k]匹配
board[i][j] = '\0';
//向着四个方向的任意一个方向前进
boolean res = dfs(board, words, i + 1, j, k + 1) || dfs(board, words, i - 1, j, k + 1) || dfs(board, words, i, j + 1, k + 1) || dfs(board, words, i , j - 1, k + 1);
//回溯,恢复数组上的值
board[i][j] = words[k];
return res;
}
}
131. 分割回文串 - 力扣(LeetCode)
红竖杠就是start的位置 横着就是用for循环从start到start+1,start+2,start+3
*51. N 皇后 - 力扣(LeetCode)
类似于 单词搜索和分割回文串的结合
只不过判断条件比分割回文串的判断回文函数更复杂一些
回溯的逻辑和单词搜索一样,经过了,把这个皇后放上去,向深处递归,递归完毕回来把皇后去掉,继续遍历皇后出现的下一种可能。
比较麻烦的是要注意
1.把一维数组转换成字符串
2.横向遍历的是Q可能出现的列位置,纵向深度递归的是每一行。
class Solution {
List<List<String>> res = new ArrayList<>();
public List<List<String>> solveNQueens(int n) {
char[][] chessboard = new char[n][n];
for (char[] c : chessboard) {
Arrays.fill(c, '.');
}
backTrack(n, 0, chessboard);
return res;
}
public void backTrack(int n, int row, char[][] chessboard) {
//遍历到最后一行
if (row == n) {
res.add(Array2List(chessboard));
return;
}
//横向遍历每一列 纵向递归每一行
for (int col = 0;col < n; ++col) {
if (isValid (row, col, n, chessboard)) {
chessboard[row][col] = 'Q';
backTrack(n, row+1, chessboard);
chessboard[row][col] = '.';
}
}
}
//把字符数组转换成字符串
public List Array2List(char[][] chessboard) {
List<String> list = new ArrayList<>();
for (char[] c : chessboard) {
list.add(String.copyValueOf(c));
}
return list;
}
//Valid检查的是在此之前没有是否有过皇后
public boolean isValid(int row, int col, int n, char[][] chessboard) {
// 检查列
for (int i=0; i<row; ++i) { // 相当于剪枝
if (chessboard[i][col] == 'Q') {
return false;
}
}
// 检查45度对角线
for (int i=row-1, j=col-1; i>=0 && j>=0; i--, j--) {
if (chessboard[i][j] == 'Q') {
return false;
}
}
// 检查135度对角线
for (int i=row-1, j=col+1; i>=0 && j<=n-1; i--, j++) {
if (chessboard[i][j] == 'Q') {
return false;
}
}
return true;
}
}