代码随想录打卡-回溯算法

一、组合问题

1.1 组合问题(剪枝)

77.组合

注意剪枝!i <= n-(k-list.size())+1

class Solution {
    List<List<Integer>> res = new ArrayList<>();
    List<Integer> list = new ArrayList<>();
    public List<List<Integer>> combine(int n, int k) {
        backtracking(n,k,1);
        return res;
    }
    private void backtracking(int n,int k,int startIndex){
        if(list.size()==k){
            res.add(new ArrayList(list));
            return;
        }

        for(int i = startIndex;i<=n-(k-list.size())+1;i++ ){
            list.add(i);
            backtracking(n,k,i+1);
            list.removeLast();
        }
    }
}

1.2 组合总和(一)

216.组合总和III

剪枝:①和已经大于目标值,剪

           ②i<=9-(k-list.size())+1

class Solution {
    List<List<Integer>> res;
    List<Integer> list;
    int sum;
    public List<List<Integer>> combinationSum3(int k, int n) {
        sum = 0;
        res = new ArrayList<>();
        list = new ArrayList<>();
        backTracking(k,n,1);
        return res;
    }
    private void backTracking(int k,int n,int startIndex){
        if(sum>n){
            return;
        }
        if(sum==n){
            if(list.size()==k){
              res.add(new ArrayList(list));  
            }
        }
        
        for(int i = startIndex;i<=9-(k-list.size())+1;i++){
            list.add(i);
            sum += i;
            backTracking(k,n,i+1);
            sum -= i;
            list.removeLast();
        }
    }
}

1.3 组合总和(二)

39.组合总和

区别:没有数字个数的要求,且数字可以无限重复

什么时候需要startIndex:组合问题中,一个集合求组合问题需要,多个集合互不影响则不需要

class Solution {
    List<List<Integer>> res;
    List<Integer> list;
    int sum;
    public List<List<Integer>> combinationSum(int[] candidates, int target) {
        Arrays.sort(candidates);
        res = new ArrayList<>();
        list = new ArrayList<>();
        sum = 0;
        backTracking(candidates,target,0);
        return res;
    }

    private void backTracking(int[] candidates,int target,int startIndex){
        if(sum>target){
            return;
        }
        if(sum==target){
            res.add(new ArrayList(list));
        }
        for(int i = startIndex;i<candidates.length;i++ ){
            if(i>startIndex){
                if(candidates[i]==candidates[i-1]){
                    continue;
                }
            }
            list.add(candidates[i]);
            sum += candidates[i];
            backTracking(candidates,target,i);
            sum -= candidates[i];
            list.removeLast();
        }
    }
}

1.4 组合总和(三)

40.组合总和II

区别:元素会有重复,但要求组合不能重复

used[i - 1] == false,说明同一树层candidates[i - 1]使用过

class Solution {
    List<List<Integer>> res;
    List<Integer> list;
    int sum;
    boolean[] used;
    public List<List<Integer>> combinationSum2(int[] candidates, int target) {
        Arrays.sort(candidates);
        res = new ArrayList<>();
        list = new ArrayList<>();
        used = new boolean[candidates.length];
        sum = 0;
        backTracking(candidates,target,0);
        return res;

    }

    private void backTracking(int[] candidates,int target,int startIndex){
        if(sum>target){
            return;
        }
        if(sum==target){
            res.add(new ArrayList(list));
        }
        for(int i = startIndex;i<candidates.length;i++){
            //相同的数字,并且前一个数字已使用过
            //跳过,避免重复
            if(i>0  && candidates[i]==candidates[i-1] && !used[i-1]){
                continue;
            }
            used[i] = true;
            sum += candidates[i];
            list.add(candidates[i]);
            backTracking(candidates,target,i+1);
            sum -= candidates[i];
            used[i] = false;
            list.removeLast();
        }
    }
}

1.5 多个集合求组合

17.电话号码的字母组合

重点:每一个数字代表的是不同集合,也就是求不同集合之间的组合

class Solution {
    List<String> res;
    String[] letter;
    StringBuilder sb;
    public List<String> letterCombinations(String digits) {
        res = new ArrayList<>();
        sb = new StringBuilder();
        letter = new String[]{"","","abc","def","ghi","jkl","mno","pqrs","tuv","wxyz"};
        if(digits==null || digits.length()==0){
            return res;
        }
        backTracking(digits,0);
        return res;
    }
    private void backTracking(String digits,int num){
        if(sb.length()==digits.length()){
            res.add(sb.toString());
            return;
        }
        String str = letter[digits.charAt(num)-'0'];
        for(char c:str.toCharArray()){
            sb.append(c);
            backTracking(digits,num+1);
            sb.deleteCharAt(sb.length()-1);
        }
    }
}

二、切割问题

131.分割回文串

class Solution {
    List<List<String>> res = new ArrayList<>();
    List<String> list = new ArrayList<>();
    public List<List<String>> partition(String s) {
        backTracking(s,0);
        return res;
    }
    private void backTracking(String s,int startIndex){
        if(startIndex == s.length()){
            res.add(new ArrayList(list));
        }
        for(int i = startIndex;i<s.length();i++){
            if(isValid(s,startIndex,i)){
                list.add(s.substring(startIndex,i+1));
                backTracking(s,i+1);
                list.removeLast();
            }
        }

    }
    private boolean isValid(String s,int start,int end){
        for(int i = start,j = end;i<j;i++,j--){
            if(s.charAt(i)!=s.charAt(j)){
                return false;
            }
        }
        return true;
    }
}

三、子集问题

3.1 子集问题(一)

78.子集

无重复元素

本题其实可以不需要加终止条件,因为startIndex >= nums.size(),本层for循环本来也结束了,本来我们就要遍历整棵树

class Solution {
    List<List<Integer>> res = new ArrayList<>();
    List<Integer> list = new ArrayList<>();
    public List<List<Integer>> subsets(int[] nums) {
        backTracking(nums,0);
        return res;
    }
    private void backTracking(int[] nums,int startIndex){
        res.add(new ArrayList(list));
        // if(startIndex == nums.length){
        //     return;
        // }
        for(int i = startIndex;i<nums.length;i++){
            list.add(nums[i]);
            backTracking(nums,i+1);
            list.removeLast();
        }
    }
}

3.2 子集问题(二)

有重复元素

90.子集II

方法一:使用used数组

class Solution {
    List<List<Integer>> res = new ArrayList<>();
    List<Integer> list = new ArrayList<>();
    boolean[] used;
    public List<List<Integer>> subsetsWithDup(int[] nums) {
        Arrays.sort(nums);
        used = new boolean[nums.length];
        backtracking(nums,0);
        return res;
    }
    private void backtracking(int[] nums,int startIndex){
        res.add(new ArrayList(list));
    
        
        for(int i = startIndex;i<nums.length;i++){
            //used[i-1]为false说明使用过了!
            if(i>0 && nums[i]==nums[i-1] && !used[i-1]){
                continue;
            }
            used[i] = true;

                list.add(nums[i]);
                backtracking(nums,i+1);
                list.removeLast();
            
            
            used[i] = false;
        }
    }
}

方法二:不使用used数组

class Solution {
    List<List<Integer>> res = new ArrayList<>();
    List<Integer> list = new ArrayList<>();
    public List<List<Integer>> subsetsWithDup(int[] nums) {
        Arrays.sort(nums);
        backtracking(nums,0);
        return res;
    }
    private void backtracking(int[] nums,int startIndex){
        res.add(new ArrayList(list));
        if(startIndex==nums.length){
            return;
        }
        for(int i = startIndex;i<nums.length;i++){
            if(i>startIndex){
                if(nums[i]==nums[i-1]){
                    continue;
                }
            }
            list.add(nums[i]);
            backtracking(nums,i+1);
            list.removeLast();
        }
    }
}

3.3 递增子序列

491.递增子序列

方法一:使用hashset

class Solution {
    List<List<Integer>> res = new ArrayList<>();
    List<Integer> list = new ArrayList<>();
    
    public List<List<Integer>> findSubsequences(int[] nums) {
        
        backTracking(nums,0);
        return res;
    }
    private void backTracking(int[] nums,int startIndex){
        if(list.size()>=2){
            res.add(new ArrayList(list));
        }
        Set<Integer> hashset = new HashSet<>();
        for(int i = startIndex;i<nums.length;i++){
            if(!list.isEmpty() && nums[i]<list.getLast() || hashset.contains(nums[i])){
                continue;
            }
            
            hashset.add(nums[i]);
            list.add(nums[i]);
            backTracking(nums,i+1);
            list.removeLast();
            
            
        }
    }
}

方法二:(优化)使用数组

class Solution {
    List<List<Integer>> res = new ArrayList<>();
    List<Integer> list = new ArrayList<>();
    
    public List<List<Integer>> findSubsequences(int[] nums) {
        
        backTracking(nums,0);
        return res;
    }
    private void backTracking(int[] nums,int startIndex){
        if(list.size()>=2){
            res.add(new ArrayList(list));
        }
        // Set<Integer> hashset = new HashSet<>();
        int[] used = new int[201];
        Arrays.fill(used,0);
        for(int i = startIndex;i<nums.length;i++){
            // if(!list.isEmpty() && nums[i]<list.getLast() || hashset.contains(nums[i])){
            //     continue;
            // }
            if(!list.isEmpty() && nums[i]<list.get(list.size()-1) || used[100+nums[i]]==1){
                continue;
            }
            
            //hashset.add(nums[i]);
            used[100+nums[i]] = 1;
            list.add(nums[i]);
            backTracking(nums,i+1);
            list.removeLast();
            
            
        }
    }
}

四、排列问题

4.1 排列问题(一)

46.全排列

排列是有序的

特别点:①每层都是从0开始搜索,而不是从startIndex开始

              ②需要used数组记录list中放了什么元素

class Solution {
    List<List<Integer>> res = new ArrayList<>();
    List<Integer> list = new ArrayList<>();
    boolean[] used;
    public List<List<Integer>> permute(int[] nums) {
        used = new boolean[nums.length];

        backTracking(nums,0);
        return res;
    }
    private void backTracking(int[] nums,int startIndex){
        if(list.size()==nums.length){
            res.add(new ArrayList(list));
        }
        for(int i=0;i<nums.length;i++){
            if(used[i]){
                continue;
            }
            used[i]=true;
            list.add(nums[i]);
            backTracking(nums,i+1);
            used[i]=false;
            list.removeLast();
        }
    }
}

4.2 排列问题(二)

47.全排列II

区别:包含重复元素【去重】

class Solution {
    List<List<Integer>> res;
    List<Integer> list;
    boolean[] used;
    public List<List<Integer>> permuteUnique(int[] nums) {
        Arrays.sort(nums);//注意:因为后面需要判断nums[i]==nums[i-1],故必须先给数组排序
        res = new ArrayList<>();
        list = new ArrayList<>();
        used = new boolean[nums.length];
        Arrays.fill(used,false);
        backTracking(nums,0);
        return res;

    }
    private void backTracking(int[] nums,int startIndex){
        if(list.size()==nums.length){
            res.add(new ArrayList(list));
        }
        for(int i = 0;i<nums.length;i++){
            if(i>0 && nums[i]==nums[i-1] && !used[i-1]){
                continue;
            }
            if(!used[i]){
                used[i]=true;
                list.add(nums[i]);
                backTracking(nums,i+1);
                list.remove(list.size()-1);
                used[i]=false;
            }
        }
    }
}

五、重新安排行程

332.重新安排行程

class Solution {
    private List<String> res = new ArrayList<>();
    private Map<String,Map<String,Integer>> map;
    public List<String> findItinerary(List<List<String>> tickets) {
        res = new ArrayList<>();
        map = new HashMap<>();
        //t:把每对出发和到达机场取出来
        //tmp:map后半部分的那个map
        //先定义tmp,再在后面进行初始化
            Map<String,Integer> tmp;
        for(List<String> t:tickets){
            
            if(map.containsKey(t.get(0))){
                tmp = map.get(t.get(0));
                tmp.put(t.get(1),tmp.getOrDefault(t.get(1),0)+1);
            }else{
                tmp = new TreeMap<>();
                tmp.put(t.get(1),1);
            }
            map.put(t.get(0),tmp);
        }
        res.add("JFK");
        backTracking(tickets.size());
        return res;
    }
    private boolean backTracking(int ticketNum){
        if(res.size()==ticketNum+1){
            return true;
        }
        String last = res.getLast();
        //注意大前提!出发机场应该是res的最后一个机场
        if(map.containsKey(last)){
            for(Map.Entry<String,Integer> target : map.get(last).entrySet()){
            int count = target.getValue();
            if(count>0){
                //可以用
                res.add(target.getKey());
                target.setValue(count-1);
                if(backTracking(ticketNum)){
                    return true;
                }
                target.setValue(count);
                res.removeLast();
            }
        }
        }
        
        return false;
    }
}

六、棋盘问题

6.1 N皇后问题

51.N皇后

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,'.');
        }
        backTracking(n,0,chessboard);
        return res;
    }
    private List Array2List(char[][] chessboard){
        List<String> list = new ArrayList<>();
        for(char[] c:chessboard){
            //将字符数组转换为字符串
            list.add(String.copyValueOf(c));
        }
        return list;
    }
    private void backTracking(int n,int row,char[][] chessboard){
        if(row==n){
            res.add(Array2List(chessboard));
            //return;
        }
        //列在变,行都是回溯的时候才加1,表示进入下一行
        for(int col = 0;col<n;col++){
            if(isValid(row,col,n,chessboard)){
                chessboard[row][col] = 'Q';
                backTracking(n,row+1,chessboard);
                chessboard[row][col] = '.';
            }
        }
    }

    private boolean isValid(int row,int col,int n,char[][] chessboard){
        //检查列(行不用检查,因为for循环,一次只会选一行的一个元素)
        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;i--,j++){
            if(chessboard[i][j]=='Q'){
                return false;
            }
        }
       
        return true;
    }
}

6.2 解数独问题

37.解数独

class Solution {
    public void solveSudoku(char[][] board) {
        backTracking(board);
    }
    private boolean backTracking(char[][] board){
        for(int i=0;i<9;i++){
            for(int j=0;j<9;j++){
                if(board[i][j]!='.'){
                    continue;//已有数字的,跳过
                }else{
                    for(char k='1';k<='9';k++){
                        //还是要传k的,因为要判断是否已出现过k
                        //没出现过再把k赋值过去
                        if(isValid(board,i,j,k)){
                            board[i][j] = k;
                            if(backTracking(board)){
                                return true;
                            }
                            //回溯
                            board[i][j] = '.';
                        }
                    }
                    //9个试了都不行,返回false
                    return false;
                }
            }
        }
        //遍历完没有返回false,说明找到合适位置了
        return true;
    }

    private boolean isValid(char[][] board,int row,int col,int k){
        //检查行:行不变,列变
        for(int i = 0;i<9;i++){
            if(board[row][i]==k){
                return false;
            }
        }
        //检查列:列不变,行变
        for(int i=0;i<9;i++){
            if(board[i][col]==k){
                return false;
            }
        }
        //九宫格
        int startRow = (row/3)*3;
        int startCol = (col/3)*3;
        for(int i=startRow;i<startRow+3;i++){
            for(int j=startCol;j<startCol+3;j++){
                if(board[i][j]==k){
                    return false;
                }
            }
        }
        return true;
    }
}

  • 6
    点赞
  • 9
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值