回溯通用框架解决leetcode排列组合

回溯算法通用框架

回溯法框架
    result = []
    backTry(选择列表,路径){
        if 满足结束条件:
            result.add(路径)
            return
        for 选择 in 选择列表:
            做选择
            递归调用
            撤销选择
    }

路径:已经做的选择
选择列表:当前可选列表

总结:

 如果数组中有重复数字,要求输出的结果不能有重复集合,一般解决办法:

        对数组排序,使得相同的数字挨着

        使用visited数组标记数字是否被访问过。如果nums[i]==nums[i-1]且visited[i-1]=false则nums[i]不再访问,以免重复。

46. 全排列

class Solution {
    List<List<Integer>> all; // 用于存储全部符合条件的结果
    boolean[] visited;
    
    public List<List<Integer>> permute(int[] nums) {
        all = new ArrayList<List<Integer>>();
        visited = new boolean[nums.length];

        List<Integer> list = new ArrayList<Integer>(); // 用于存储当前路径
        backTry(list, nums);
        
        return all;
    }

    public void backTry(List<Integer> list, int[] nums){
        if(list.size() == nums.length){ // 满足结束条件:选了nums中所有的数字
            all.add(new ArrayList<Integer>(list));
            return;
        }
        for(int i=0;i<nums.length;i++){
            if(visited[i]==false){ // 只有没有访问过,才做选择    
                // 做选择
                list.add(nums[i]);
                visited[i] = true;
                // 递归调用
                backTry(list, nums);
                // 撤销选择
                list.remove(list.size()-1);
                visited[i] = false;
            }
        }
    }
}

47. 全排列 II

class Solution {
    // nums中有重复数字,要求输出结果不能有重复    

    List<List<Integer>> all; // 用于存储全部符合条件的结果
    boolean[] visited;
    
    public List<List<Integer>> permuteUnique(int[] nums) {
        all = new ArrayList<List<Integer>>();
        visited = new boolean[nums.length];
        Arrays.sort(nums);
        List<Integer> list = new ArrayList<Integer>(); // 用于存储当前路径
        backTry(list, nums);
        
        return all;
    }

    public void backTry(List<Integer> list, int[] nums){
        if(list.size() == nums.length){ // 满足结束条件:选了nums中所有的数字
            all.add(new ArrayList<Integer>(list));
            return;
        }
        for(int i=0;i<nums.length;i++){
            if(visited[i] || i>0 && nums[i]==nums[i-1] && !visited[i-1]){continue;}
            if(visited[i]==false){ // 只有没有访问过,才做选择    
                // 做选择
                list.add(nums[i]);
                visited[i] = true;
                // 递归调用
                backTry(list, nums);
                // 撤销选择
                list.remove(list.size()-1);
                visited[i] = false;
            }
        }
    }
}

78. 子集

数组nums中不包含重复元素,要求输出结果是没有重复子集的  nums = [1,2,3]

不能有重复子集:那么就是当前数字选择完后,选择后面的数字,这样就会避免出现[1,2]  [2,1]这样的重复情况

class Solution {
    // 幂集:从空到数组长度所有的子集

    List<List<Integer>> all; // 存放所有的路径
    boolean[] visited;
    public List<List<Integer>> subsets(int[] nums) {
        all = new ArrayList<List<Integer>>();
        visited = new boolean[nums.length];

        List<Integer> list = new ArrayList<Integer>(); // 存放当前选择路径
        for(int i = 0;i < nums.length; i++){
            backTry(list, nums, i); // 从空路径、长度为0开始选择
        }
        return all;
    }

    public void backTry(List<Integer> list, int[] nums, int len){
        if(list.size() == len){ // 结束条件:如果当前选择的数字个数等于幂集的长度
            all.add(new ArrayList<Integer>(list));
            return;
        }
        for(int i = 0;i < nums.length; i++){
            if(visited[i] == false){ // 只有当没有被访问过才选择
                // 做选择
                list.add(nums[i]);
                visited[i] = true;
                // 递归调用
                backTry(list, nums, len);
                // 撤回选择
                list.remove(list.size()-1);
                visited[i] = false;
            }
        }
    }
}

怎么在回溯的过程中对子集去重呢??上一版代码可以看出,2选择之后又去选择1,但其实是每个数字都选择他后面的数字就不会出现有[1,2] 和 [2,1]这样的情形了。

class Solution {
    // 幂集:从空到数组长度所有的子集

    List<List<Integer>> all; // 存放所有的路径
    public List<List<Integer>> subsets(int[] nums) {
        all = new ArrayList<List<Integer>>();

        List<Integer> list = new ArrayList<Integer>(); // 存放当前选择路径
        for(int i = 0;i <= nums.length; i++){
            backTry(list, nums, 0, i); // 从空路径、位置0,长度为i开始选择
        }
        return all;
    }

    public void backTry(List<Integer> list, int[] nums, int idx, int len){
        if(list.size() == len){ // 结束条件:如果当前选择的数字个数等于幂集的长度
            all.add(new ArrayList<Integer>(list));
            return;
        }
        for(int i = idx;i < nums.length; i++){
            // 做选择
            list.add(nums[i]);
            // 递归调用,为避免重复,每次选择该数字的下一个数字
            backTry(list, nums, i+1, len);
            // 撤回选择
            list.remove(list.size()-1);
        }
    }
}

90. 子集 II

class Solution {
    // 上一题的数组中不包含重复元素,本题nums中有重复元素,但要求不变
    // 怎么避免[2] [2] [1,2] [1,2] 这样的子集出现?可以发现:如果第一个2 被访问过,第二个2与前一个2相同,且前一个2北方问过,那么第二个2就不能被访问,所以去重,我们可以借助visited数组


    List<List<Integer>> all; // 存放所有的幂集
    boolean[] visited;
    public List<List<Integer>> subsetsWithDup(int[] nums) {
        all = new ArrayList<List<Integer>>();
        visited = new boolean[nums.length];
        List<Integer> list = new ArrayList<Integer>(); // 存放当前选择路径
        Arrays.sort(nums);
        for(int i=0;i<=nums.length;i++){ // 幂集的长度从0到数组长度
            backTry(list, nums, i, 0); // 仍旧从空选择、幂集长度为i,位置0开始选择
        }
        return all;
    }
    public void backTry(List<Integer> list, int[] nums, int len, int idx){
        if(list.size() == len){ // 结束条件:仍旧是list中数字的个数等于幂集的长度
            all.add(new ArrayList<Integer>(list));
            return;
        }
        for(int i=idx;i<nums.length;i++){ // 遍历所有的选择
            // 如果当前数组访问过,或者当前数字等于前一个数字,且前一个数字没有被访问过(因为是从前往后访问的的,入股哦前一个数字是false,说明已经访问过并且回溯了的,那么这个数字就不再访问了)
            if(visited[i] || i>0 && nums[i] == nums[i-1] && !visited[i-1]){continue;}
            // 做选择
            list.add(nums[i]);
            visited[i] = true;
            // 递归调用
            backTry(list, nums, len, i+1);
            // 撤回选择
            list.remove(list.size()-1);
            visited[i] = false;
        }
    }
}

77. 组合

class Solution {

    List<List<Integer>> all; // 存储所有的选择
    public List<List<Integer>> combine(int n, int k) {
        all = new ArrayList<List<Integer>>();
        List<Integer> list = new ArrayList<Integer>(); // 存储当前选择的路径
        backTry(list, n, 1, k);// 选择从1到n
        
        return all;
    }
    public void backTry(List<Integer> list, int n, int idx, int k){
        if(list.size() == k){ // 结束条件
            all.add(new ArrayList<Integer>(list));
            return;
        }
        for(int i=idx;i<=n;i++){ // 遍历所有的选择
            // 做选择
            list.add(i);
            // 递归调用
            backTry(list, n, i+1, k);
            // 撤回选择
            list.remove(list.size()-1);
        }
    }
}

39. 组合总和

class Solution {
    // 数组无重复元素,同一个数字可以重复选择,选出组合和为target的所有组合
    
    List<List<Integer>> all;
    public List<List<Integer>> combinationSum(int[] candidates, int target) {
        all = new ArrayList<List<Integer>>();
        List<Integer> list = new ArrayList<Integer>();
        backTry(list, candidates, target, 0); // 从位置0,空list开始选择
        return all;
    }

    public void backTry(List<Integer> list, int[] candidates, int target, int idx){
        if(target == 0){ // 结束条件:如果目标和等于0了,说明已经选择数字和等于target了
            all.add(new ArrayList<Integer>(list));
            return;
        }
        for(int i=idx; i<candidates.length;i++){
            if((target - candidates[i])>=0){ // 是否满足选择条件
                // 做选择
                list.add(candidates[i]);
                target -= candidates[i];
                // 递归调用
                backTry(list, candidates, target, i);
                // 撤回选择
                list.remove(list.size()-1);
                target += candidates[i];
            }
        }
    }
}

40. 组合总和 II

class Solution {
    // 数组有重复元素,同一个数字只能选择一次,选出组合和为target的所有组合
    
    List<List<Integer>> all;
    boolean[] visited;
    public List<List<Integer>> combinationSum2(int[] candidates, int target) {
        all = new ArrayList<List<Integer>>();
        visited = new boolean[candidates.length];
        List<Integer> list = new ArrayList<Integer>();

        Arrays.sort(candidates);

        backTry(list, candidates, target, 0); // 从位置0,空list开始选择
        return all;
    }

    public void backTry(List<Integer> list, int[] candidates, int target, int idx){
        if(target == 0){ // 结束条件:如果目标和等于0了,说明已经选择数字和等于target了
            all.add(new ArrayList<Integer>(list));
            return;
        }
        for(int i=idx; i<candidates.length;i++){
            // 是否满足选择条件
            // 如果该数字大于target,或者已经被访问过,或者等于前一个数字,且前一个数字没有被访问过
            if(candidates[i]>target || visited[i] || i>0 && candidates[i]==candidates[i-1] && !visited[i-1]){continue;}

            // 做选择
            list.add(candidates[i]);
            target -= candidates[i];
            visited[i] = true;
            // 递归调用
            backTry(list, candidates, target, i+1);
            // 撤回选择
            list.remove(list.size()-1);
            target += candidates[i];
            visited[i] = false;
            
        }
    }
}

216. 组合总和 III

class Solution {
    // 从数字1~9中选择k个和为n的组合,每个数字最多使用一次

    List<List<Integer>> all;
    public List<List<Integer>> combinationSum3(int k, int n) {
        all = new ArrayList<List<Integer>>();
        List<Integer> list = new ArrayList<Integer>();

        backTry(list, 1, n, k); // 从数字1,空list开始选择
        return all;
    }

    public void backTry(List<Integer> list, int idx, int n, int k){
        if(n == 0 && list.size() == k){ // 结束条件:如果目标和等于0了,说明已经选择数字和等于n了
            all.add(new ArrayList<Integer>(list));
            return;
        }
        for(int i=idx;i<=9;i++){
            // 是否满足选择条件: 如果该数字大于n
            if(i>n){continue;}
            // 做选择
            list.add(i);
            n -= i;
            // 递归调用
            backTry(list, i+1, n, k);
            // 撤回选择
            list.remove(list.size()-1);
            n += i;
            
        }
    }
}

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值