回溯方法总结

1、排列问题

问题描述
一般是给定一个集合(可能含有重复元素),计算符合条件的所有排列。

注:排列内部可能会出现相同元素,但是不能出现相同排列。

例题
幂集

class Solution {
    List<List<Integer>> res;
    private void backtrack(List<Integer> path,int start,int[] nums){
        //1、停止条件\
        res.add(new ArrayList<>(path));
            //2、for循环遍历同一层的节点、递归遍历子节点
        for(int i=start;i<nums.length;i++){
            path.add(nums[start]);
            backtrack(path,i+1,nums);
            path.remove(path.size()-1);
        }
    }
    public List<List<Integer>> subsets(int[] nums) {
        res=new ArrayList<>();
        List<Integer> path=new ArrayList<>();
        backtrack(path,0,nums);
        return res;
    }
}

全排列

class Solution46 {
    List<List<Integer>> res;
    int[] visited;
    private void backtrack(List<Integer> path,int[]nums){
        if(path.size()==nums.length){
            res.add(new ArrayList<>(path));
            return;
        }
        for(int i=0;i<nums.length;i++){
            if(visited[i]==0){
                visited[i]=1;
                path.add(nums[i]);
                backtrack(path,nums);
                path.remove(path.size()-1);
                visited[i]=0;
            }
        }
    }
    public List<List<Integer>> permute(int[] nums) {
        res=new ArrayList<>();
        List<Integer> path=new ArrayList<>();
        backtrack(path,nums);
        return res;
    }
}

字符串排列

class Solution {
    List<String> res;
    int[] visited;
    private void backtrack(String S,StringBuilder path){
        if(path.length()==S.length()){
            res.add(path.toString());
            return;
        }
        for(int i=0;i<S.length();i++){
            if(visited[i]==0){
                visited[i]=1;
                path.append(S.charAt(i));
                backtrack(S,path);
                path.deleteCharAt(path.length()-1);
                visited[i]=0;
            }
        }
    }
    public String[] permutation(String S) {
        res=new ArrayList<>();
        visited=new int[S.length()];
        StringBuilder path=new StringBuilder();
        backtrack(S,path);
        String[] result=new String[res.size()];
        for(int i=0;i<res.size();i++){
            result[i]=res.get(i);
        }
        return result;
    }
}

优美排列

class Solution {
   int res;
    int[] visited;
    private void backtrack(List<Integer> path,int n){
        if(path.size()==n){
            res++;
            return;
        }
        for(int i=1;i<=n;i++){
            if (visited[i] == 0&&((path.size()+1)%i==0||i%(path.size()+1)==0)) {
                visited[i]=1;
                path.add(i);
                backtrack(path,n);
                path.remove(path.size()-1);
                visited[i]=0;
            }
        }
    }
    public int countArrangement(int n) {
        res=0;
        visited=new int[n+1];
        List<Integer> path=new ArrayList<>();
        backtrack(path,n);
        return res;
    }
}

全排列2:含有重复元素

class Solution {
    List<List<Integer>> res;
    int[] visited;
    private void backtrack(List<Integer> path,int[] nums){
        if(path.size()==nums.length){
            res.add(new ArrayList<>(path));
            return;
        }

        for(int i=0;i<nums.length;i++){
            if(i>0&&nums[i]==nums[i-1]&&visited[i-1]==0)
                continue;
            if(visited[i]==0){
                visited[i]=1;
                path.add(nums[i]);
                backtrack(path,nums);
                path.remove(path.size()-1);
                visited[i]=0;
            }
        }

    }
    public List<List<Integer>> permuteUnique(int[] nums) {
        res=new ArrayList<>();
        Arrays.sort(nums);
        List<Integer> path=new ArrayList<>();
        visited=new int[nums.length];
        backtrack(path,nums);
        return res;
    }
}

解题方法

  • 回溯函数内部for循环每次从0开始遍历,使用visited数组去除已经访问的元素。
  • 若是每一个排列允许出现重复元素,则递归调用时从i开始,否则从i+1开始;
  • 若是集合内有重复元素,为了避免出现重复排列,应该使用if(i>0&&nums[i]==nums[i-1]&&visited[i-1]==0)continue;跳过相同元素。

将回溯问题看作二维的递归,横方向是同一层,竖方向是同一个路径。这里我们一般都是允许同一条路径中出现重复元素,但是同一层不允许出现相同排列,因此这里使用visited[i-1]==0表示同一层中出现了相同元素,因此直接跳过即可。而visited[i-1]==1表示nums[i]和nums[i-1]位于同一条路径中。

2、组合问题

对于一组数据,改变其内部元素顺序就变成了不同排列,但是还是同一个组合。组合只考虑内部元素,而不在乎其顺序。
一般是给定集合,求出满足条件的所有组合。

例题
组合

给定两个整数 n 和 k,返回 1 ... n 中所有可能的 k 个数的组合。
示例:
输入: n = 4, k = 2
输出:
[
  [2,4],
  [3,4],
  [2,3],
  [1,2],
  [1,3],
  [1,4],
]
class Solution77 {
    List<List<Integer>> res;
    private void backtrack(int n,int k,List<Integer> path,int start){
        if(path.size()==k){
            res.add(new ArrayList<>(path));
            return;
        }
        //组合内部不考虑顺序
        for(int i=start;i<=n;i++){
            path.add(i);
            backtrack(n,k,path,i+1);
            path.remove(path.size()-1);
        }
    }
    public List<List<Integer>> combine(int n, int k) {
        res=new ArrayList<>();
        List<Integer> path=new ArrayList<>();
        backtrack(n,k,path,1);
        return  res;
    }
}

组合总和

给定一个无重复元素的数组 candidates 和一个目标数 target ,找出 candidates 中所有可以使数字和为 target 的组合。
candidates 中的数字可以无限制重复被选取。
class Solution {
    List<List<Integer>> res;
    private void backtrack(int start,List<Integer> path,int[] candidates,int target,int sum){
        if(sum==target){
            res.add(new ArrayList<>(path));
            return;
        }
        if(sum>target)return;
        for(int i=start;i<candidates.length;i++){
            path.add(candidates[i]);
            backtrack(i,path,candidates,target,sum+candidates[i]);
            path.remove(path.size()-1);
        }
    }
    public List<List<Integer>> combinationSum(int[] candidates, int target) {
        res=new ArrayList<>();
        List<Integer> path=new ArrayList<>();
        backtrack(0,path,candidates,target,0);
        return res;
    }
}

组合总和2

给定一个数组 candidates 和一个目标数 target ,找出 candidates 中所有可以使数字和为 target 的组合。
candidates 中的每个数字在每个组合中只能使用一次。
class Solution {
    List<List<Integer>> res;
    int[] visited;
    private void backtrack(int[] candidates,int start,List<Integer> path,int sum,int target){
        if(sum==target){
            res.add(new ArrayList<>(path));
            return;
        }
        if(sum>target)
            return;
        for(int i=start;i<candidates.length;i++){
            if(i>0&&candidates[i]==candidates[i-1]&&visited[i-1]==0)
                continue;
            path.add(candidates[i]);
            visited[i]=1;
            sum+=candidates[i];
            backtrack(candidates,i+1,path,sum,target);
            sum-=candidates[i];
            visited[i]=0;
            path.remove(path.size()-1);
        }
    }
    public List<List<Integer>> combinationSum2(int[] candidates, int target) {
        res=new ArrayList<>();
        visited=new int[candidates.length];
        Arrays.sort(candidates);
        List<Integer> path=new ArrayList<>();
        backtrack(candidates,0,path,0,target);
        return res;
    }
}

组合总和3

找出所有相加之和为 n 的 k 个数的组合。组合中只允许含有 1 - 9 的正整数,并且每种组合中不存在重复的数字。
class Solution {
    List<List<Integer>> res;
    private boolean backtrack(int start,List<Integer> path,int k,int n,int sum){
        if(path.size()==k&&sum==n){
            res.add(new ArrayList<>(path));
            return true;
        }
        if(path.size()<k&&sum<n){
            for(int i=start;i<=9;i++){
                path.add(i);
                sum+=i;
                backtrack(i+1,path,k,n,sum);
                sum-=i;
                path.remove(path.size()-1);
            }
        }
        return false;
    }
    public List<List<Integer>> combinationSum3(int k, int n) {
        res=new ArrayList<>();
        List<Integer> path=new ArrayList<>();
        backtrack(1,path,k,n,0);
        return res;
    }
}

无重复字符串的排列组合

class Solution {
    List<String> res;
    int[] visited;
    private void backtrack(String S,StringBuilder path){
        if(path.length()==S.length()){
            res.add(path.toString());
            return;
        }
        for(int i=0;i<S.length();i++){
            if(visited[i]==0){
                visited[i]=1;
                path.append(S.charAt(i));
                backtrack(S,path);
                path.deleteCharAt(path.length()-1);
                visited[i]=0;
            }
        }
    }
    public String[] permutation(String S) {
        res=new ArrayList<>();
        visited=new int[S.length()];
        StringBuilder path=new StringBuilder();
        backtrack(S,path);
        String[] result=new String[res.size()];
        for(int i=0;i<res.size();i++){
            result[i]=res.get(i);
        }
        return result;
    }
}

有重复字符串的排列组合

class Solution {
    public String[] permutation(String S) {

        List<String> list = new ArrayList<>();
        char[] arr = S.toCharArray();
        Arrays.sort(arr);
        boolean[] book = new boolean[arr.length];
        dfs(list, new StringBuilder(), book, arr);

        String[] res = new String[list.size()];
        for (int i = 0; i < res.length; i++)
            res[i] = list.get(i);

        return res;
    }
    
    public void dfs(List<String> res, StringBuilder sb, boolean[] book, char[] arr) {
        
        if (sb.length() == arr.length) {
            res.add(sb.toString());
            return;
        }
        for (int i = 0; i < arr.length; i++) {
            if (!book[i]) {
                if (i > 0 && arr[i] == arr[i - 1] && !book[i - 1])
                    continue;
                else {
                    sb.append(arr[i]);
                    book[i] = true;
                    dfs(res, sb, book, arr);
                    book[i] = false;
                    sb.deleteCharAt(sb.length() - 1);
                }
            }
        }
    }
}

解题方法总结

回溯函数使用start
若是允许组合内部出现重复元素,则递归时从i开始,否则从i+1开始;
使用if(i>0&&candidates[i]==candidates[i-1]&&visited[i-1]==0) continue;去除重复组合。

3、子集问题

例题
子集

给你一个整数数组 nums ,数组中的元素 互不相同 。返回该数组所有可能的子集(幂集)。
解集 不能 包含重复的子集。你可以按 任意顺序 返回解集。
示例 1:

输入:nums = [1,2,3]
输出:[[],[1],[2],[1,2],[3],[1,3],[2,3],[1,2,3]]
class Solution {
    private List<List<Integer>> res = new ArrayList<List<Integer>>();
    public List<List<Integer>> subsets(int[] nums) {
        dfs(0,nums,new ArrayList<>());
        return res;
    }

    public void dfs(int start, int[] nums, ArrayList<Integer> list){
        if(!res.contains(new ArrayList<>(list))){
            res.add(new ArrayList<>(list));
        }
        for(int i=start;i<nums.length;i++){
            list.add(nums[i]);
            dfs(i+1, nums, list);
            list.remove(list.size()-1);
        }
    }
}

子集2

给你一个整数数组 nums ,其中可能包含重复元素,请你返回该数组所有可能的子集(幂集)。
解集 不能 包含重复的子集。返回的解集中,子集可以按 任意顺序 排列。
class Solution {
    List<List<Integer>> res;
    private void backtrack(List<Integer> path,int start,int[] nums){
        res.add(new ArrayList<>(path));
        for(int i=start;i<nums.length;i++){
            if(i>start&&nums[i]==nums[i-1])continue;
            path.add(nums[i]);
            backtrack(path,i+1,nums);
            path.remove(path.size()-1);
        }
    }
    public List<List<Integer>> subsetsWithDup(int[] nums) {
        res=new ArrayList<>();
        Arrays.sort(nums);
        List<Integer> path=new ArrayList<>();
        backtrack(path,0,nums);
        return res;
    }
}

4、路径问题

例题

黄金矿工

你要开发一座金矿,地质勘测学家已经探明了这座金矿中的资源分布,并用大小为 m * n 的网格 grid 进行了标注。每个单元格中的整数就表示这一单元格中的黄金数量;如果该单元格是空的,那么就是 0。

为了使收益最大化,矿工需要按以下规则来开采黄金:

每当矿工进入一个单元,就会收集该单元格中的所有黄金。
矿工每次可以从当前位置向上下左右四个方向走。
每个单元格只能被开采(进入)一次。
不得开采(进入)黄金数目为 0 的单元格。
矿工可以从网格中 任意一个 有黄金的单元格出发或者是停止。
 class Solution {
    int res;
    int[][] direction={{-1,0},{1,0},{0,1},{0,-1}};
    int[][] visited;
    private void backtrack(int sum,int[][] grid,int r,int c,int m,int n){
        if(r>=m||r<0||c<0||c>=n||grid[r][c]==0||visited[r][c]==1)
        {
            res=Math.max(res,sum);
            return;
        }
        sum+=grid[r][c];
        visited[r][c]=1;
        for(int i=0;i<4;i++){
            backtrack(sum,grid,r+direction[i][0],c+direction[i][1],m,n);
        }
        visited[r][c]=0;
        sum-=grid[r][c];
    }
    public int getMaximumGold(int[][] grid) {
        res=0;
        int m=grid.length;
        int n=grid[0].length;
        visited=new int[m][n];
        for(int i=0;i<m;i++){
            for(int j=0;j<n;j++){
                if(grid[i][j]>0){
                    backtrack(0,grid,i,j,m,n);
                }
            }
        }
        return res;
    }
}

单词搜索

给定一个 m x n 二维字符网格 board 和一个字符串单词 word 。如果 word 存在于网格中,返回 true ;否则,返回 false 。

单词必须按照字母顺序,通过相邻的单元格内的字母构成,其中“相邻”单元格是那些水平相邻或垂直相邻的单元格。同一个单元格内的字母不允许被重复使用。
class Solution {
    int[][] visited;
    int[][] direction={{-1,0},{1,0},{0,-1},{0,1}};
    private boolean backtrack(char[][] board, String word, int index,int r,int c,int m,int n){
        if(index==word.length()){
            return true;
        }
        if(r<0||r>=m||c<0||c>=n||visited[r][c]==1||board[r][c]!=word.charAt(index))
            return false;
        visited[r][c]=1;
        for(int i=0;i<4;i++){
            if(backtrack(board,word,index+1,r+direction[i][0],c+direction[i][1],m,n))
                return true;
        }
        visited[r][c]=0;
        return false;
    }

    public boolean exist(char[][] board, String word) {
        int m=board.length;
        int n=board[0].length;
        visited=new int[m][n];
        for(int i=0;i<m;i++){
            for(int j=0;j<n;j++){
                if(backtrack(board,word,0,i,j,m,n))
                    return true;
            }
        }
        return false;
    }
}

N皇后

输入:4
 输出:[[".Q..","...Q","Q...","..Q."],["..Q.","Q...","...Q",".Q.."]]
 List<List<String>> res;
    char[][] grid;
    private boolean isValid(int r,int c,int n){
        for(int i=r-1;i>=0;i--){
            if(grid[i][c]=='Q')return false;
        }
        for(int i=r-1,j=c-1;i>=0&&j>=0;i--,j--){
            if(grid[i][j]=='Q')return false;
        }
        for(int i=r-1,j=c+1;i>=0&&j<n;i--,j++){
            if(grid[i][j]=='Q')return false;
        }
        return true;
    }
    private void backtrack(int n,int r){
        if(r==n){
            List<String> tmp=new ArrayList<>();
            for(int i=0;i<n;i++){
                tmp.add(new String(grid[i]));
            }
            res.add(tmp);
            return;
        }
        for(int c=0;c<n;c++){
            if(isValid(r,c,n)){
                grid[r][c]='Q';
                backtrack(n,r+1);
                grid[r][c]='.';
            }
        }
    }
    public List<List<String>> solveNQueens(int n) {
        grid=new char[n][n];
        for (int i = 0; i < n; i++) {
            for (int j = 0; j < n; j++) {
                grid[i][j] = '.';
            }
        }
        res=new ArrayList<>(n);
        backtrack(n,0);
        return res;
    }

N皇后2

n 皇后问题 研究的是如何将 n 个皇后放置在 n×n 的棋盘上,并且使皇后彼此之间不能相互攻击。

给你一个整数 n ,返回 n 皇后问题 不同的解决方案的数量。
class Solution {
    int res;
    int[][] visited;
    private void backtrack(int n,int r){
        if(r==n){
            res++;
            return;
        }
        for(int c=0;c<n;++c){
            if(isValid(r,c,n)){
                visited[r][c]=1;
                backtrack(n,r+1);
                visited[r][c]=0;
            }
        }
    }
    private boolean isValid(int r,int c,int n){
        for(int i=r-1;i>=0;i--){
            if(visited[i][c]==1)return false;
        }
        
        for(int i=r-1,j=c+1;i>=0&&j<n;--i,++j){
            if(visited[i][j]==1)return false;
        }
        for(int i=r-1,j=c-1;i>=0&&j>=0;--i,--j){
            if(visited[i][j]==1)return false;
        }
        return true;
    }
    public int totalNQueens(int n) {
        visited=new int[n][n];
        res=0;
        backtrack(n,0);
        return res;
    }
}

5、分割问题

例题

复原IP地址

class Solution {
    List<List<String>> res;
    private void backtrack(List<String> path,String s,int l,int r){
        if(path.size()==4&&l==r) {
            res.add(new ArrayList<>(path));
            return;
        }
        for(int i=l+1;i<=Math.min(l+3,r);i++){
            if(s.charAt(l)=='0'&&i>l+1)
                break;
            int t=Integer.valueOf(s.substring(l,i));
            if(t>=0&&t<=255){
                path.add(s.substring(l,i));
                backtrack(path,s,i,r);
                path.remove(path.size()-1);
            }
        }
    }
    public List<String> restoreIpAddresses(String s) {
        res=new ArrayList<>();
        List<String> rr=new ArrayList<>();
        if(s.length()>12)
            return rr;
        List<String> path=new ArrayList<>();
        backtrack(path,s,0,s.length());
        StringBuilder tmp=new StringBuilder();
        for(int i=0;i<res.size();i++){
            for(int j=0;j<res.get(i).size();j++){
                tmp.append(res.get(i).get(j));
                tmp.append('.');
            }
            rr.add(tmp.deleteCharAt(tmp.length()-1).toString());
            tmp.delete(0,tmp.length());
        }
        //System.out.println(rr);
        return rr;
    }
}

拆分字符串使唯一子字符串的数目最大

class Solution {
    int res;
    Set<String> set;
    private void backtrack(String s,int start){
        if(start==s.length()){
            res=Math.max(res,set.size());
            return;
        }
        for(int i=start+1;i<=s.length();i++){
            String sub=s.substring(start,i);
            if(set.contains(sub)){
                continue;
            }
            set.add(sub);
            backtrack(s,i);
            set.remove(sub);
        }
    }
    public int maxUniqueSplit(String s) {
        res=0;
        set=new HashSet<>();
        backtrack(s,0);
        return res;
    }
}
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值