深度优先搜索算法(回溯算法):
我的理解就是:树(递归树)的深度优先搜索,进入下一层时前做选择,从下一层回来时撤销选择
1 核心要关注 路径(已经做出的选择)、选择列表(当前可以做的选择)、结束条件(无法再做选择的条件)
2 结构上来说是:循环里面套递归,递归前先剪枝不可选择的情况后做选择,递归后撤销选择
例题1:全排列
解法1:
因为要知道还没被用过的元素有哪些,比较好的办法有:利用本身给的nums数组,交换位置。
class Solution {
List<List<Integer>> result;
public List<List<Integer>> permute(int[] nums) {
result=new ArrayList<>();
dfs(nums,0);
return result;
}
public void dfs(int[] nums,int index){
if(index==nums.length){
List<Integer> temp=new ArrayList<>();
for(int i=0;i<nums.length;++i){
temp.add(nums[i]);
}
result.add(temp);
return;
}
for(int i=index;i<nums.length;++i){ //循环
swap(nums,index,i); //改变状态
dfs(nums,index+1); //深度优先遍历
swap(nums,index,i); //改回状态
}
}
public void swap(int[] nums,int index1,int index2){
int temp=nums[index1];
nums[index1]=nums[index2];
nums[index2]=temp;
}
}
解法2:
根据这题数字不重复的特性,可以直接循环nums所有元素,判断是否已经被使用也是可以的。
1 首先注意result添加temp时要new一个新的
2 remove可以删除int类型的下标,或者Integer对象类型。如这里可以remove(index)也可以remove(Integer.valueOf(item))
class Solution {
List<List<Integer>> result;
int[] nums;
public List<List<Integer>> permute(int[] nums) {
result=new ArrayList<>();
this.nums=nums;
List<Integer> temp=new ArrayList<>();
dfs(0,temp);
return result;
}
public void dfs(int index,List<Integer> temp){
if(index==nums.length){
result.add(new ArrayList<>(temp));
return;
}
for(int item:nums){
if(!temp.contains(item)){
temp.add(item);
dfs(index+1,temp);
temp.remove(index);
}
}
}
}
例题2:全排列 II
解法1:
在例题1解法1的基础上加一个set,保证[index,nums.length)的区间上也没有重复情况
class Solution {
List<List<Integer>> result;
public List<List<Integer>> permuteUnique(int[] nums) {
result=new ArrayList<>();
dfs(nums,0);
return result;
}
public void dfs(int[] nums,int index){
if(index==nums.length){
List<Integer> temp=new ArrayList<>();
for(int i=0;i<nums.length;++i){
temp.add(nums[i]);
}
result.add(temp);
return;
}
HashSet<Integer> set=new HashSet<>();
for(int i=index;i<nums.length;++i){ //循环
if(set.contains(nums[i]))continue;
set.add(nums[i]);
swap(nums,index,i); //改变状态
dfs(nums,index+1); //深度优先遍历
swap(nums,index,i); //改回状态
}
}
public void swap(int[] nums,int index1,int index2){
int temp=nums[index1];
nums[index1]=nums[index2];
nums[index2]=temp;
}
}
解法2:
在例题1解法2的基础上
1 这里要保存每个数字是否被使用,然后根据情况剪枝。
2 为了方便剪枝必须先对array排序。
3 剪枝详细说明见:力扣大佬题解
class Solution {
List<List<Integer>> result;
boolean[] visited;
int[] nums;
public List<List<Integer>> permuteUnique(int[] nums) {
result=new ArrayList<>();
visited=new boolean[nums.length];
Arrays.sort(nums);
this.nums=nums;
List<Integer> temp=new ArrayList<>();
dfs(0,temp);
return result;
}
public void dfs(int index,List<Integer> temp){
if(index==nums.length){
result.add(new ArrayList<>(temp));
}
for(int i=0;i<nums.length;++i){
if(visited[i]){
continue;
}
if(i>0&&nums[i]==nums[i-1]&&visited[i-1]==false){
continue;
}
temp.add(nums[i]);
visited[i]=true;
dfs(index+1,temp);
temp.remove(index);
visited[i]=false;
}
}
}
例题3:字符串的排列
就是 例题2:全排列 II 的字符版
解法1
class Solution {
List<String> result;
public String[] permutation(String s) {
result=new ArrayList<String>();
char[] c=s.toCharArray();
dfs(0,c);
return result.toArray(new String[result.size()]);
}
//dfs函数用来确定c[index]的值选哪个
void dfs(int index,char[] c){
if(index==c.length){
result.add(String.valueOf(c));
}
Set<Character> set=new HashSet<>();
for(int i=index;i<c.length;++i){
//这之前的就是确定c[index]可以选择的下标范围为[index,c.length)
//这个剪枝就是确保c[index]没有和之前选过的值一样,一样就跳到下个数字的判断
if(set.contains(c[i])){
continue;
}
//c[i]没有和之前选过的值一样时,就将其添加到set中,并交换c[index]和c[i]的位置
set.add(c[i]);
swap(c,i,index);
//再进行下一个位置index+1的确定
dfs(index+1,c);
swap(c,i,index);
}
}
void swap(char[] c,int index1,int index2){
char temp=c[index1];
c[index1]=c[index2];
c[index2]=temp;
}
}
解法2
class Solution {
List<String> result;
public String[] permutation(String s) {
result=new ArrayList<String>();
char[] c=s.toCharArray();
boolean[] visited=new boolean[s.length()];
Arrays.sort(c);
StringBuilder temp=new StringBuilder("");
dfs(0,c,temp,visited);
return result.toArray(new String[result.size()]);
}
void dfs(int index,char[] c,StringBuilder temp,boolean[] visited){
if(index==c.length){
result.add(temp.toString());
}
for(int i=0;i<c.length;++i){
if(visited[i]){
continue;
}
if(i>0&&c[i]==c[i-1]&&visited[i-1]==false){
continue;
}
temp.append(c[i]);
visited[i]=true;
dfs(index+1,c,temp,visited);
temp.deleteCharAt(temp.length()-1);
visited[i]=false;
}
}
}
例题4:下一个排列
思路如下:
1 我们需要将一个左边的「较小数」与一个右边的「较大数」交换,以能够让当前排列变大,从而得到下一个排列。
2 同时我们要让这个「较小数」尽量靠右,而「较大数」尽可能小。当交换完成后,「较大数」(已换到了左侧)右边的数需要按照升序重新排列。这样可以在保证新排列大于原来排列的情况下,使变大的幅度尽可能小。
具体实现如下:
1 首先从后向前查找第一个顺序对 (i,i+1),满足 a[i] < a[i+1]。这样「较小数」即为 a[i]。此时 [i+1,n)必然是下降序列。
2 如果找到了顺序对,那么在区间 [i+1,n) 中从后向前查找第一个元素 j满足 a[i] < a[j]。这样「较大数」即为 a[j]。
3 交换 a[i] 与 a[j],此时可以证明区间 [i+1,n) 必为降序。我们可以直接使用双指针反转区间 [i+1,n)使其变为升序,而无需对该区间进行排序。
class Solution {
public void nextPermutation(int[] nums) {
int smallNumIndex=-1;
int bigNumIndex=-1;
//找到尽量靠右的「较小数」
for(int i=nums.length-2;i>=0;--i){
if(nums[i]<nums[i+1]){
smallNumIndex=i;
break;
}
}
//找到尽可能小「较大数」,且在「较小数」右侧
if (smallNumIndex >= 0) { //已经是最大序列(递减)的情况,就直接反转
for(int i=nums.length-1;i>smallNumIndex;--i){
if(nums[i]>nums[smallNumIndex]){
bigNumIndex=i;
break;
}
}
swap(nums,smallNumIndex,bigNumIndex);
}
reverse(nums,smallNumIndex+1);
}
public void swap(int[] nums,int index1,int index2){
int temp=nums[index1];
nums[index1]=nums[index2];
nums[index2]=temp;
}
public void reverse(int[] nums,int start){
int left=start,right=nums.length-1;
while(left<right){
swap(nums,left,right);
left++;
right--;
}
}
}
而这题也给例题2和例题3带来了解法3
例题2的解法3:
class Solution {
public List<List<Integer>> permuteUnique(int[] nums) {
List<List<Integer>> result=new ArrayList<>();
Arrays.sort(nums);
do{
List<Integer> temp=new ArrayList<>();
for(int i=0;i<nums.length;++i){
temp.add(nums[i]);
}
result.add(temp);
}while(nextPermute(nums));
return result;
}
public boolean nextPermute(int[] nums){
int smallNumIndex=-1;
int bigNumIndex=-1;
for(int i=nums.length-2;i>=0;--i){
if(nums[i]<nums[i+1]){
smallNumIndex=i;
break;
}
}
if(smallNumIndex==-1){
return false;
}
for(int i=nums.length-1;i>smallNumIndex;--i){
if(nums[i]>nums[smallNumIndex]){
bigNumIndex=i;
break;
}
}
swap(nums,smallNumIndex,bigNumIndex);
reverse(nums,smallNumIndex+1);
return true;
}
public void swap(int[] nums,int index1,int index2){
int temp=nums[index1];
nums[index1]=nums[index2];
nums[index2]=temp;
}
public void reverse(int[] nums,int index){
int left=index,right=nums.length-1;
while(left<right){
swap(nums,left,right);
++left;
--right;
}
}
}
例题3的解法3:
class Solution {
public String[] permutation(String s) {
List<String> ret = new ArrayList<String>();
char[] arr = s.toCharArray();
Arrays.sort(arr);
do {
ret.add(new String(arr));
} while (nextPermutation(arr));
return ret.toArray(new String[ret.size()]);
}
public boolean nextPermutation(char[] arr) {
int i = arr.length - 2;
while (i >= 0 && arr[i] >= arr[i + 1]) {
i--;
}
if (i < 0) {
return false;
}
int j = arr.length - 1;
while (j >= 0 && arr[i] >= arr[j]) {
j--;
}
swap(arr, i, j);
reverse(arr, i + 1);
return true;
}
public void swap(char[] arr, int i, int j) {
char temp = arr[i];
arr[i] = arr[j];
arr[j] = temp;
}
public void reverse(char[] arr, int start) {
int left = start, right = arr.length - 1;
while (left < right) {
swap(arr, left, right);
left++;
right--;
}
}
}
例题5:排列序列
解法1:
如果想当做 例题1 的衍生来做,铁定超时。如果作为 例题4 的衍生来做就可以了
class Solution {
public String getPermutation(int n, int k) {
int[] nums=new int[n];
for(int i=0;i<n;++i){
nums[i]=i+1;
}
int count=1;
while(count<k&&nextPermute(nums)){
++count;
}
StringBuilder result=new StringBuilder();
for(int i=0;i<n;++i){
result.append((char)(nums[i]+'0'));
}
return result.toString();
}
public boolean nextPermute(int[] nums){
int smallNumIndex=-1;
int bigNumIndex=-1;
for(int i=nums.length-2;i>=0;--i){
if(nums[i]<nums[i+1]){
smallNumIndex=i;
break;
}
}
if(smallNumIndex==-1){
return false;
}
for(int i=nums.length-1;i>smallNumIndex;--i){
if(nums[i]>nums[smallNumIndex]){
bigNumIndex=i;
break;
}
}
swap(nums,smallNumIndex,bigNumIndex);
reverse(nums,smallNumIndex+1);
return true;
}
public void swap(int[] nums,int index1,int index2){
int temp=nums[index1];
nums[index1]=nums[index2];
nums[index2]=temp;
}
public void reverse(int[] nums,int index){
int left=index,right=nums.length-1;
while(left<right){
swap(nums,left,right);
++left;
--right;
}
}
}
解法2:
还有数学的方法来做,对于我来说基本就是理解大佬题解,然后背下来,面试的时候默下来。但目前还没理解
例题6: 子集
解法1:
class Solution {
List<List<Integer>> result=new ArrayList<>();
public List<List<Integer>> subsets(int[] nums) {
List<Integer> temp=new ArrayList<>();
dfs(nums,temp,0);
return result;
}
public void dfs(int[] nums,List<Integer> temp,int index){
result.add(new ArrayList<>(temp));
for(int i=index;i<nums.length;++i){
temp.add(nums[i]);
dfs(nums,temp,i+1);
temp.remove(temp.size()-1);
}
}
}
解法2:选cur位置或者不选
class Solution {
List<Integer> t = new ArrayList<Integer>();
List<List<Integer>> ans = new ArrayList<List<Integer>>();
public List<List<Integer>> subsets(int[] nums) {
dfs(0, nums);
return ans;
}
public void dfs(int cur, int[] nums) {
if (cur == nums.length) {
ans.add(new ArrayList<Integer>(t));
return;
}
// 考虑选择当前位置
t.add(nums[cur]);
dfs(cur + 1, nums);
t.remove(t.size() - 1);
// 考虑不选择当前位置
dfs(cur + 1, nums);
}
}
例题7:组合
class Solution {
List<List<Integer>> result=new ArrayList<>();
public List<List<Integer>> combine(int n, int k) {
List<Integer> temp=new ArrayList<>();
dfs(n,k,1,temp);
return result;
}
public void dfs(int n, int k,int start,List<Integer> temp){
if(temp.size()==k){
result.add(new ArrayList<>(temp));
return;
}
for(int i=start;i<=n;++i){
temp.add(i);
dfs(n,k,i+1,temp); //这里注意是传过去的是i+1,而不是start+1
temp.remove(temp.size()-1);
}
return;
}
}
例题8:组合总和
题解1:写法1
class Solution {
List<List<Integer>> result=new ArrayList<>();
List<Integer> temp=new ArrayList<>();
public List<List<Integer>> combinationSum(int[] candidates, int target) {
dfs(candidates,0,target);
return result;
}
public void dfs(int[] candidates,int index, int target){
if(target<0){
return;
}
if(target==0){
result.add(new ArrayList<>(temp));
return;
}
for(int i=index;i<candidates.length;++i){
temp.add(candidates[i]);
dfs(candidates,i,target-candidates[i]);
temp.remove(temp.size()-1);
}
}
}
题解2:写法2
class Solution {
List<List<Integer>> result=new ArrayList<>();
List<Integer> temp=new ArrayList<>();
public List<List<Integer>> combinationSum(int[] candidates, int target) {
dfs(candidates,0,target);
return result;
}
public void dfs(int[] candidates,int index, int target){
if(index==candidates.length){
return;
}
if(target==0){
result.add(new ArrayList<>(temp));
return;
}
//不选择candites[index]
dfs(candidates,index+1,target);
//选择candites[index]
if(target-candidates[index]>=0){
temp.add(candidates[index]);
dfs(candidates,index,target-candidates[index]);
temp.remove(temp.size()-1);
}
}
}
例题8:矩阵中的路径
class Solution {
public boolean exist(char[][] board, String word) {
char[] words=word.toCharArray();
if(board.length==0){
return false;
}
for(int i=0;i<board.length;++i){
for(int j=0;j<board[0].length;++j){
if(dfs(board,i,j,words,0)){
return true;
}
}
}
return false;
}
public boolean dfs(char[][] board,int row,int col,char[] word,int index){
if(row<0||row>=board.length||col<0||col>=board[0].length||board[row][col]!=word[index]){
return false;
}
if(index==word.length-1){
return true;
}
board[row][col]='\0';
boolean result=dfs(board,row-1,col,word,index+1)||dfs(board,row+1,col,word,index+1)||dfs(board,row,col-1,word,index+1)||dfs(board,row,col+1,word,index+1);
board[row][col]=word[index];
return result;
}
}
例题9:剑指 Offer 13. 机器人的运动范围
解法1:回溯方法
class Solution {
boolean[][] board;
public int movingCount(int m, int n, int k) {
board=new boolean[m][n];
dfs(0,0,m,n,k);
int result=0;
for(int i=0;i<m;++i){
for(int j=0;j<n;++j){
if(board[i][j]){
++result;
}
}
}
return result;
}
public void dfs(int row,int col,int m, int n, int k){
if(row<0||row>=m||col<0||col>=n||countNum(row)+countNum(col)>k||board[row][col]){
return;
}
board[row][col]=true;
dfs(row-1,col,m,n,k);
dfs(row+1,col,m,n,k);
dfs(row,col-1,m,n,k);
dfs(row,col+1,m,n,k);
}
public int countNum(int num){
int sum=0;
while(num!=0){
int temp=num%10;
sum+=temp;
num/=10;
}
return sum;
}
}
解法2:递推方法
其实这题是只能向下和向右的,因此 (i, j) 的格子只会从 (i - 1, j) 或者 (i, j - 1) 两个格子走过来(不考虑边界条件)。注意这里是或者的关系。
class Solution {
boolean[][] board;
public int movingCount(int m, int n, int k) {
board=new boolean[m][n];
int result=1;
board[0][0]=true;
for(int i=0;i<m;++i){
for(int j=0;j<n;++j){
if((i==0&&j==0)||(countNum(i)+countNum(j)>k)){
continue;
}
if(i>0){
board[i][j]|=board[i-1][j];
}
if(j>0){
board[i][j]|=board[i][j-1];
}
if(board[i][j]){
++result;
}
}
}
return result;
}
public int countNum(int num){
int sum=0;
while(num!=0){
int temp=num%10;
sum+=temp;
num/=10;
}
return sum;
}
}
例题10:N 皇后
在这里插入代码片
例题11:N皇后 II
在这里插入代码片
例题12:路径组成的最大数字
如输入[0,1,5,0,0],返回1500
import java.util.*;
class Solution {
int result=0;
public int solution(int[][] Board) {
if(Board.length==0){
return 0;
}
for(int i=0;i<Board.length;++i){
for(int j=0;j<Board[0].length;++j){
dfs(Board,i,j,0,0);
}
}
return result;
}
public void dfs(int[][] board,int row,int col,int index,int sum){
if(index==4){
if(result<sum){
result=sum;
}
return;
}
if(row<0||row>=board.length||col<0||col>=board[0].length||board[row][col]==-1){
return;
}
// 试图剪枝,但是失败了。无法解决上面的用例
// 记录上下左右最大值
// int max=0;
// Map<Integer,Integer> map=new HashMap<Integer,Integer>();
// if(row-1>=0 &&board[row-1][col]>max){
// map.clear();
// map.put(row-1,col);
// max=board[row-1][col];
// }else if(row-1 >=0&&board[row-1][col]==max){
// map.put(row-1,col);
// }
// if(row+1<board.length &&board[row+1][col]>max){
// map.clear();
// map.put(row+1,col);
// max=board[row+1][col];
// }else if(row+1<board.length&&board[row+1][col]==max){
// map.put(row+1,col);
// }
// if(col-1>=0 &&board[row][col-1]>max){
// map.clear();
// map.put(row,col-1);
// max=board[row][col-1];
// }else if(col-1 >=0&&board[row][col-1]==max){
// map.put(row,col-1);
// }
// if(col+1<board[0].length &&board[row][col+1]>max){
// map.clear();
// map.put(row,col+1);
// max=board[row][col+1];
// }else if(col+1<board[0].length&&board[row][col+1]==max){
// map.put(row,col+1);
// }
int temp=board[row][col];
int temp=board[row][col];
sum=sum*10+board[row][col];
board[row][col]=-1;
dfs(board,row-1,col,index+1,sum);
dfs(board,row+1,col,index+1,sum);
dfs(board,row,col-1,index+1,sum);
dfs(board,row,col+1,index+1,sum);
// for(Map.Entry<Integer, Integer> entr:map.entrySet()){
// dfs(board,entr.getKey(),entr.getValue(),index+1,sum);
// }
board[row][col]=temp;
return;
}
}
例题13:打印从1到最大的n位数
力扣这题返回是int[],故是可以不考虑大数的。但如果要考虑大数的答案,可以转化为数字排列问题。
class Solution {
int[] result;
int count=0;
public int[] printNumbers(int n) {
result=new int[(int)Math.pow(10,n)-1];
char[] num=new char[n];
dfs(0,num);
return result;
}
public void dfs(int index,char[] num){
if(index==num.length){
int temp=Integer.valueOf(String.valueOf(num));
if(temp!=0){
result[count++]=temp;
}
return;
}
for(char i='0';i<='9';++i){
num[index]=i;
dfs(index+1,num);
}
}
}
例题14:电话号码的字母组合
class Solution {
Map<Character,Character[]> map=new HashMap<>();
public List<String> letterCombinations(String digits) {
//初始化映射
map.put('2',new Character[]{'a','b','c'});
map.put('3',new Character[]{'d','e','f'});
map.put('4',new Character[]{'g','h','i'});
map.put('5',new Character[]{'j','k','l'});
map.put('6',new Character[]{'m','n','o'});
map.put('7',new Character[]{'p','q','r','s'});
map.put('8',new Character[]{'t','u','v'});
map.put('9',new Character[]{'w','x','y','z'});
//开始dfs
List<String> result=new ArrayList<>();
StringBuilder str=new StringBuilder("");
if(digits.length()==0){
return result;
}
dfs(digits,0,result,str);
return result;
}
void dfs(String digits,int index,List<String> result,StringBuilder str){
if(index==digits.length()){
result.add(str.toString());
return;
}
Character[] chars=map.get(digits.charAt(index));
for(int i=0;i<chars.length;++i){
str.append(chars[i]);
dfs(digits,index+1,result,str);
str.deleteCharAt(index);
}
}
}
例题15:括号生成
class Solution {
List<String> result=new ArrayList<>();
public List<String> generateParenthesis(int n) {
dfs(new StringBuilder(),0,0,n);
return result;
}
void dfs(StringBuilder temp,int leftB,int rightB,int n){
if(temp.length()==n*2){
result.add(temp.toString());
return;
}
if(leftB<n){
temp.append("(");
dfs(temp,leftB+1,rightB,n);
temp.deleteCharAt(temp.length()-1);
}
if(rightB<leftB){
temp.append(")");
dfs(temp,leftB,rightB+1,n);
temp.deleteCharAt(temp.length()-1);
}
}
}