【面试算法题总结11】深度优先搜索算法(回溯算法)

深度优先搜索算法(回溯算法):

我的理解就是:树(递归树)的深度优先搜索,进入下一层时前做选择,从下一层回来时撤销选择
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);
        }
    }
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值