DFS和回溯专题

dfs:状态庞大方案很少。搜索是思想,可以用递归实现也可以用迭代实现。

例 leetcode17 电话号码的字母组合

递归

    //对于每个字母遍历他包含的字符c,当前str=str+c,然后递归的遍历下一个字符。
    //传参:全局变量字母表,当前str,当前索引,传入的字符串
    //递归出口:index等于digits.length(index可以用temp的长度计算出来)
    List<String> output = new ArrayList<String>();
    String []stringList={"","abc","def","ghi","jkl","mno","pqrs","tuv","wxyz"};
    public List<String> letterCombinations(String digits) {
        if(digits.length()!=0)
            helper("",digits);
        return output;
    }
    public void helper(String tempStr,String digits){
        int index=tempStr.length();
        if(index==digits.length())
            output.add(tempStr);
        else{
            for(int i=0;i<stringList[digits.charAt(index)-'1'].length();i++)
            helper(tempStr+stringList[digits.charAt(index)-'1'].charAt(i),digits);
        }
    }

循环

样例2345678
state={}
for 每个数字
for c =每个数字的备选字母
for s= state中的所有字符串
s+=c;
state.add(s);

在一个数组里存储

    List<String> output = new ArrayList<String>();
    String []stringList={"","abc","def","ghi","jkl","mno","pqrs","tuv","wxyz"};
    public List<String> letterCombinations(String digits) {
        if(digits.length()==0)
            return output;
        else{
            for(int i=0;i<stringList[digits.charAt(0)-'1'].length();i++)
                output.add(String.valueOf(stringList[digits.charAt(0)-'1'].charAt(i)));
            for(int j= 1;j<digits.length();j++){
                int length = output.size();
                 for(int i=0;i<stringList[digits.charAt(j)-'1'].length();i++){
                     for(int k = 0;k<length;k++){
                         String temp = output.get(k);
                         if(temp.length()==j){
                            temp+=stringList[digits.charAt(j)-'1'].charAt(i);
                            output.add(temp);
                         }
                     }
                 }
            }
        }
        List<String> res = new ArrayList<String>();
        for(String s:output){
            if(s.length()==digits.length())
                res.add(s);
        }
        return res;
    }

不断创建新数组存储

    List<String> output = new ArrayList<String>();
    String []stringList={"","abc","def","ghi","jkl","mno","pqrs","tuv","wxyz"};
    public List<String> letterCombinations(String digits) {
        if(digits.length()==0)
            return output;
        else{
            for(int i=0;i<stringList[digits.charAt(0)-'1'].length();i++)
                output.add(String.valueOf(stringList[digits.charAt(0)-'1'].charAt(i)));
            for(int j= 1;j<digits.length();j++){
                List<String>now = new ArrayList<String>();;
                for(int i=0;i<stringList[digits.charAt(j)-'1'].length();i++){
                    for(String s:output){
                        s+=stringList[digits.charAt(j)-'1'].charAt(i);
                        now.add(s);
                     }
                }
                output=now;
            }
        }
        return output;
    }

例 leetcode79 单词搜索

1.枚举起点
2.从起点开始搜索下一个点的位置(不能往回走,用标记数组标一下)
3.在枚举过程中,要保证和目标单词匹配
4.需要有一个回溯的步骤,恢复操作前的初始状态。这里就是标记数组的复原。(恢复现场)

    boolean res=false;
    public boolean exist(char[][] board, String word) {
        char [][]flag =new char[board.length][board[0].length];
        for(int i=0;i<board.length;i++){
            for(int j=0;j<board[0].length;j++){
                if(word.charAt(0)==board[i][j]){
                    flag[i][j]=1;
                    helper(board,word,1,i,j,flag);
                    flag[i][j]=0;
                }
            }
        }
        return res;
    }
    public boolean helper(char[][]board,String word, int index,int i,int j,char[][]flag){
        if(res==true)
            return res;
        if(index == word.length()){
            res=true;
            return res;
        }
        if(i-1>=0&&board[i-1][j]==word.charAt(index)&&flag[i-1][j]==0){
            flag[i-1][j]=1;
            helper(board,word,index+1,i-1,j,flag);
            flag[i-1][j]=0;
        }
        if(i+1<board.length&&board[i+1][j]==word.charAt(index)&&flag[i+1][j]==0){
            flag[i+1][j]=1;
            helper(board,word,index+1,i+1,j,flag);
            flag[i+1][j]=0;
        }
        if(j-1>=0&&board[i][j-1]==word.charAt(index)&&flag[i][j-1]==0){
            flag[i][j-1]=1;
            helper(board,word,index+1,i,j-1,flag);
            flag[i][j-1]=0;
        }
        if(j+1<board[0].length&&board[i][j+1]==word.charAt(index)&&flag[i][j+1]==0){
            flag[i][j+1]=1;
            helper(board,word,index+1,i,j+1,flag);
            flag[i][j+1]=0;
        }
        return res;
    }

例 leetcode46 全排列I

1.搜索顺序,不重复且不遗漏
样例 1 2 3 。没有重复数字。
枚举每个位置上放哪个数。(或者枚举每个数放到哪个位置上)。

    LinkedList<List<Integer>> res = new LinkedList<List<Integer>>();
    ArrayList<Integer> temp= new ArrayList<Integer>();
    int []nums;
    boolean []flag;
    public List<List<Integer>> permute(int[] nums) {
        if(nums == null || nums.length == 0) 
            return res;
        this.nums=nums;
        flag=new boolean[nums.length];
        helper();
        return res;
    }
    public void helper() {
        if(temp.size()==nums.length) 
        	res.add((ArrayList<Integer>)temp.clone());
        	//这里一定要用clone()浅拷贝,不然后续修改就修改掉了。
        	//这里还有一个坑,就是声明时一定要写成ArrayList而不是List,否则无法使用clone()函数            		
        else{
            for(int i=0;i<nums.length;i++)
                if(flag[i]==false){
                    flag[i]=true;
                    temp.add(nums[i]);
                    helper();
                    temp.remove(Integer.valueOf(nums[i]));
                    flag[i]=false;
                }
        }
    }

例 leetcode47全排列II

存在重复数。
1.通过排序把相同的数放在一起
2.在遍历过程中重复数只使用一次
3.DFS的状态用temp的size隐含。temp的size代表了枚举到第几个数
4.暂时不考虑用set去重的方法。

    public LinkedList<List<Integer>> res = new LinkedList<List<Integer>>();
    public ArrayList<Integer> temp= new ArrayList<Integer>();
    public int []nums;
    public boolean []flag;
    public List<List<Integer>> permuteUnique(int[] nums) {
        if(nums == null || nums.length == 0) 
            return res;
        this.nums=nums;
        flag=new boolean[nums.length];
        Arrays.sort(nums);
        helper();
        return res;
    }
    public void helper() {
        int last=nums[0]-1;
        if(temp.size()==nums.length) 
            res.add((ArrayList<Integer>)temp.clone());//浅拷贝
        else{
            for(int i=0;i<nums.length;i++)
            //这里要保证的是每一轮对每个位置选出的数不重复。
                if(flag[i]==false&&nums[i]!=last){
                    flag[i]=true;
                    temp.add(nums[i]);
                    last=nums[i];
                    int tail=temp.size();
                    helper();
                    //恢复现场
                    temp.remove(temp.size()-1);
                    flag[i]=false;
                }
        }
    }

例 leetcode78 子集

枚举每个元素,选择进入子集或者不选择,更新状态继续DFS。通过索引记录状态,退出递归。

递归写法

    public ArrayList<Integer>curr= new ArrayList<>();
    public ArrayList<List<Integer>>result = new ArrayList<List<Integer>>();
    public List<List<Integer>> subsets(int[] nums) {
        if(nums==null||nums.length==0)
            return result;
        helper(nums,0);
        return result;
    }
    public void helper(int []nums,int currIdx){
        if(currIdx==nums.length){
            result.add((ArrayList<Integer>)curr.clone());
        }else{
            helper(nums,currIdx+1);
            curr.add(nums[currIdx]);
            helper(nums,currIdx+1);
            curr.remove(curr.size()-1);
        }
    }

迭代写法

二进制优化。对索引进行二进制表示。
样例 1 2 3
000表示空集,001表示第一个元素…111表示全集{1 2 3}。
1.遍历0~2的N次方,对应各个子集。
2.利用移位和逻辑运算,把二进制位为1对应位置的nums[j]填入curr。
3.把curr写入ress

    public ArrayList<List<Integer>>result = new ArrayList<List<Integer>>();
    public List<List<Integer>> subsets(int[] nums) {
        if(nums==null||nums.length==0)
            return result;
        for(int i=0;i< 1<<nums.length;i++){
            ArrayList<Integer>curr= new ArrayList<>();
            for(int j=0;j<nums.length;j++)
                if(((i>>j)&1)==1)
                    curr.add(nums[j]);
            result.add(curr);
        }
        return result;
    }

例 leetcode 90 子集II

存在重复数字

    public ArrayList<Integer>curr= new ArrayList<>();
    public ArrayList<List<Integer>>result = new ArrayList<List<Integer>>();
    
    public List<List<Integer>> subsetsWithDup(int[] nums) {
        if(nums==null||nums.length==0)
            return result;
        Arrays.sort(nums);//排序 相同的放在一起
        helper(nums,0);//用索引表示状态
        return result;
    }
    public void helper(int []nums,int currIdx){
        if(currIdx==nums.length)
            result.add((ArrayList<Integer>)curr.clone());
        else{
            int k=0;
            //双指针计数当前索引处值的个数(最少是1)
            while(currIdx+k<nums.length&&nums[currIdx+k]==nums[currIdx])
                k++;
            //放入0--k个
            for(int i=0;i<=k;i++){
                helper(nums,currIdx+k);//先递归进去,可以把空包含在内。
                curr.add(nums[currIdx]);
            }
            for(int i=0;i<=k;i++)
                curr.remove(curr.size()-1);
        }
    }

例 leetcode216组合总和III

找出所有相加之和为 n 的 k 个数的组合。组合中只允许含有 1 - 9 的正整数,并且每种组合中不存在重复的数字。
代码

    public List<List<Integer>> res=new ArrayList<>();
    public ArrayList<Integer> temp = new ArrayList<>();
    public List<List<Integer>> combinationSum3(int k, int n) {
        int sum=0;
        helper(k,1,n,sum);
        return res;
    }
    public void helper(int k,int index,int n,int sum){
        if(k==0||index>9){
            if(sum==n&&k==0)
                res.add((ArrayList<Integer>)temp.clone());
            return ;
        }
        for(int i=index;i<=9;i++){
            temp.add(i);
            helper(k-1,i+1,n,sum+i);
            temp.remove(temp.size()-1);
        }
    }

例 leetcode52 N皇后II

核心还是枚举顺序!
枚举每行的皇后在每列的情况,行默认不同,则值考虑列和对角线是否可用。
2N-1条对角线,2N-1条反对角线。(y=x+b,y=-x+b)
转换为数组索引x+y x-y+n。避免出现负数。

    public int n,res=0;
    public boolean [] d,ud,col;
    public int totalNQueens(int n) {
        //用递归次数代表行
        //还需要记录列的情况和两个对角线的情况。
        this.n=n;
        d=new boolean[2*n];
        ud= new boolean[2*n];
        col = new boolean[n];
        helper(0);
        return res;
    }
    public void helper(int row){
        if(row ==n){
            res++;
            return ;
        }
        for(int i=0;i<n;i++){
            if(col[i]==false&&d[i+row]==false&&ud[row -i+n]==false){
                col[i]=true;
                d[i+row]=true;
                ud[row -i+n]=true;
                helper(row +1);
                col[i]=false;
                d[i+row]=false;
                ud[row -i+n]=false; 
            }
        }
    }

例 leetcode 解数独

八皇后和解数独都是精确优化问题(目前已经被十字链表完美解决,但是写起来十分麻烦)
每一行1-9是否已经使用row[9][9](前一个代表行,后一个代表数字)
每一列、每个九宫格同上cell[3][3][9]。

猜你喜欢

转载自blog.csdn.net/tianyouououou/article/details/105086857