算法笔记01——回溯

1. 基本模板

class Solution {
    public List<List<元素类型>> 方法名(参数列表){
        //返回answer容器集合
        List<List<元素类型>> ans = new ArrayList<>();
        if (返回空集合条件) return ans;
        //返回answer容器集合子集
        List<元素类型> path = new ArrayList<>();
        //标记数组,对使用过的元素进行标记
        boolean[] used = new boolean[数组长度];
        //首先全部标记为未使用false
        Arrays.fill(used);
        backtrack(ans,path,遍历目标,遍历开始位置,used);
        return ans;
    }
    //回溯函数
    public void backtrack(List<List<元素类型>> ans,List<元素类型> path,遍历目标,遍历开始位置,boolean[] used) {
        if (遍历成功条件){
            //向answer容器中添加path结果
            ans.add(new ArrayList<>(path));
            return;
        }
        else {
            for (int i = 开始位置;判断条件;i++) {
                if (!used[i]){
                    path添加操作;
                    //used数组标记已经使用
                    used[i] = true;
                    //沿着这条路径继续遍历
                    backtrack(List<List<元素类型>> ans,List<元素类型> path,遍历目标,遍历开始位置更新,boolean[] used);
                    //回溯时更改used数组使用情况
                    used[i] = false;
                    path移除操作
                }
            }
        }
    }
}

2. 经典例题

2.1 组合

组合

class Solution {
    public List<List<Integer>> combine(int n, int k) {
        List<List<Integer>> res = new ArrayList<>();
        List<Integer> list = new ArrayList<>();
        if(n == 0 || k == 0) return res;
        backtrack(res,list,n,k,0,1);
        return res;
    }
    public void backtrack(List<List<Integer>> res,List<Integer> list,int n,int k,int sum,int start){
        //如果list中的元素数目sum已经达到目标要求k则返回
        if(sum == k){
            res.add(new ArrayList(list));
            return;
        }
        //倘若没有达到则从起始位置start开始往后遍历
        for(int i = start;i <= n;i++){
            list.add(i);
            backtrack(res,list,n,k,sum + 1,i + 1);
            list.remove(list.size() - 1);
        }
    }
}

2.2 组合总和

组合总和

class Solution {
    public List<List<Integer>> combinationSum(int[] candidates, int target) {
        List<List<Integer>> res = new ArrayList<>();
        List<Integer> path = new ArrayList<>();
        if(candidates == null || candidates.length == 0 || target < 0){
            return res;
        }
        //对数组进行排序,从小到大依次遍历
        Arrays.sort(candidates);
        backtrack(res,path,candidates,target,0);
        return res;
    }
    public void backtrack(List<List<Integer>> res,List<Integer> path,int[] candidates,int target,int index){
        //如果target等于0则返回
        if(target == 0){
            res.add(new ArrayList<>(path));
        }
        else{
            //继续向后遍历,从下表index处开始
            for(int i = index;i < candidates.length;i++){
                //如果candidates[i]处的值比target还大,证明i之后的值也一定比target大,则这条路径结束并返回
                if(target - candidates[i] < 0){
                    break;
                }
                //如果candidates[i]处的值比target小,则将candidates[i]加入path
                path.add(candidates[i]);
                //继续沿着这条路径遍历,target更新为target - candidates[i],由于同一个数字可以重复使用,所以index不用更新
                backtrack(res,path,candidates,target - candidates[i],i);
                //回溯返回时需要将path中的最后一个数字移除
                path.remove(path.size() - 1);
            }
        }
    }
}

2.3 组合总和2

组合总和2

class Solution {
    public List<List<Integer>> combinationSum2(int[] candidates, int target) {
        List<List<Integer>> res = new ArrayList<>();
        List<Integer> path = new ArrayList<>();
        if(candidates.length == 0 || candidates == null || target < 0){
            return res;
        }
        //对数组进行排序,从小到大依次遍历
        Arrays.sort(candidates);
        backtrack(res,path,candidates,target,0);
        return res;
    }
    public void backtrack(List<List<Integer>> res,List<Integer> path,int[] candidates,int target,int index){
        //如果target等于0则返回
        if(target == 0){
            res.add(new ArrayList<>(path));
        }
        else{
            //继续向后遍历,从下表index处开始
            for(int i = index;i < candidates.length;i++){
                //如果candidates[i]处的值比target还大,证明i之后的值也一定比target大,则这条路径结束并返回
                if(target - candidates[i] < 0){
                    break;
                }
                //如果candidates[i]处的值比target小,则将candidates[i]加入path
                path.add(candidates[i]);
                //继续沿着这条路径遍历,target更新为target - candidates[i],由于同一个数字不可以重复使用,所以index更新为i+1
                backtrack(res,path,candidates,target - candidates[i],i + 1);
                //如果candidates中有重复元素,则重复元素可以跳过不用再次遍历,
                //即candidates下一个元素和当前路径最后一个元素相同时则跳过candidates的这个元素,进行去重操作
                while(i + 1 < candidates.length && path.get(path.size() - 1) == candidates[i + 1]){
                    i++;
                }
                path.remove(path.size() - 1);
            }
        }
    }
}

2.4 全排列

全排列

class Solution {
    public List<List<Integer>> permute(int[] nums) {
        List<List<Integer>> res = new ArrayList<>();
        if(nums.length == 0) return res;
        List<Integer> path = new ArrayList<>();
        //使用used数组记录nums中对应元素的使用情况
        boolean[] used = new boolean[nums.length];
        //首先将所有元素标记为未使用
        Arrays.fill(used,false);
        backtrack(res,path,nums,0,used);
        return res;
    }
    public void backtrack(List<List<Integer>> res,List<Integer> path,int[] nums,int index,boolean[] used){
        //如果path中的元素数目等于nums数组大小,则将此条路径加入结果res中
        if(path.size() == nums.length){
            res.add(new ArrayList<>(path));
            return;
        }
        else{
            //遍历nums数组
            for(int i = 0;i < nums.length;i++){
                //如果当前位置元素没有使用过,则将当前元素加入path,并且将使用情况更新为已经使用
                if(!used[i]){
                    path.add(nums[i]);
                    used[i] = true;
                    //继续遍历nums数组下一个元素
                    backtrack(res,path,nums,index + 1,used);
                    //回溯返回时将used[i]使用情况重新更新为未使用
                    used[i] = false;
                    //删除path路径中最后一个元素
                    path.remove(path.size() - 1);
                }
            }
        }
    }
}

2.5 全排列2

class Solution {
    public List<List<Integer>> permuteUnique(int[] nums) {
        List<List<Integer>> res = new ArrayList<>();
        if(nums.length == 0) return res;
        Arrays.sort(nums);
        List<Integer> path = new ArrayList<>();
        boolean[] used = new boolean[nums.length];
        Arrays.fill(used,false);
        backtrack(res,path,nums,0,used);
        return res;
    }
    public void backtrack(List<List<Integer>> res,List<Integer> path,int[] nums,int index,boolean[] used){
        if(path.size() == nums.length){
            res.add(new ArrayList<>(path));
            return;
        }
        else{
            for(int i = 0;i < nums.length;i++){
                if(!used[i]){
                    path.add(nums[i]);
                    used[i] = true;
                    backtrack(res,path,nums,index + 1,used);
                    used[i] = false;
                    //唯一与全排列1不同的位置,需要进行去重操作
                    while(i + 1 < nums.length && path.get(path.size() - 1) == nums[i + 1]){
                        i++;
                    }
                    path.remove(path.size() - 1);
                }
            }
        }
    }
}

2.6 单词搜索

单词搜索

class Solution {
    //使用flag标记矩阵中是否存在这个单词
    boolean flag = false;
    public boolean exist(char[][] board, String word) {
        if(board.length == 0 || board[0].length == 0 || word.length() == 0) return false;
        //使用used数组标记矩阵中每个元素的使用情况
        boolean[][] used = new boolean[board.length][board[0].length];
        //遍历矩阵中每个元素,判断该元素是否等于word中的第一个元素
        for(int i = 0;i < board.length;i++){
            for(int j = 0;j < board[0].length;j++){
                if(board[i][j] != word.charAt(0)) continue;
                //如果board[i][j]处的元素等于word的第一个元素,则开始进行回溯
                backtrack(board,word,i,j,0,used);
                //如果此时flag已经等于true,则结束回溯
                if(flag == true) break;
            }
            if(flag == true) break;
        }
        return flag;
    }
    public void backtrack(char[][] board,String word,int i,int j,int index,boolean[][] used){
        //如果此时flag已经等于true,则结束回溯
        if(flag) return;
        //如果board[i][j]处的元素等于word的index下标处的元素,则将board[i][j]处的元素标记为true使用过
        if(board[i][j] == word.charAt(index)){
            used[i][j] = true;
            //如果index长度已经等于word的长度,则证明已经找到这么一条路径
            if(index + 1 == word.length()){
                flag = true;
                return;
            }
            //如果index长度还未到word长度,则还需要进行遍历
            else{
                //向左边进行遍历
                if(i - 1 >= 0){
                    //如果board[i - 1][j]的元素还未使用
                    if(!used[i - 1][j]){
                        used[i - 1][j] = true;
                        backtrack(board,word,i - 1,j,index + 1,used);
                        used[i - 1][j] = false;
                    }
                }
                //向上边进行遍历
                if(j - 1 >= 0){
                    //如果board[i][j - 1]的元素还未使用
                    if(!used[i][j - 1]){
                        used[i][j - 1] = true;
                        backtrack(board,word,i,j - 1,index + 1,used);
                        used[i][j - 1] = false;
                    }
                }
                //向右边进行遍历
                if(i + 1 < board.length){
                    //如果board[i + 1][j]的元素还未使用
                    if(!used[i + 1][j]){
                        used[i + 1][j] = true;
                        backtrack(board,word,i + 1,j,index + 1,used);
                        used[i + 1][j] = false;
                    }
                }
                //向下边进行遍历
                if(j + 1 < board[0].length){
                    //如果board[i][j + 1]的元素还未使用
                    if(!used[i][j + 1]){
                        used[i][j + 1] = true;
                        backtrack(board,word,i,j + 1,index + 1,used);
                        used[i][j + 1] = false;
                    }
                }
                //如果四个方向的元素都被使用,则证明当前元素不可行,返回上一层并将此元素标记为未使用
                used[i][j] = false;
            }
        }
        //如果当前元素不等于word的index下标处的元素,则board[i][j]处的元素不可行,返回上一层并将此元素标记为未使用
        else{
            used[i][j] = false;
            return;
        }
    }
}
上一篇:40. 组合总和 II


下一篇:回溯算法