Summary

回溯算法中的排列与组合

框架模板

回溯算法在解决排列和组合问题时,两者的主要区别在于元素的顺序是否重要,以及结果中是否允许重复选取相同元素。

  1. 排列 (Permutation)
  2. 顺序重要:在排列中,元素的顺序会影响结果。例如,[1, 2] 和 [2, 1] 被视为不同的排列。
  3. 典型问题:求数组的全排列、给定数列的不同排列方式等。
  4. 特点:需要考虑每个元素在每个位置的可能性,通常通过标记来避免重复选取已使用的元素。
  5. 组合 (Combination)
  6. 顺序不重要:在组合中,元素的顺序不影响结果。例如,[1, 2] 和 [2, 1] 被视为相同的组合。
  7. 典型问题:求数组的所有子集、选出若干个元素的组合等。
  8. 特点:只需要关心某个元素是否被选中,不需要考虑顺序,通常可以通过限制递归的起始位置来避免重复组合。

排列代码模板

void backtrack(List<List<Integer>> res, List<Integer> tempList, int[] nums, boolean[] used) {
    if (tempList.size() == nums.length) {
        res.add(new ArrayList<>(tempList));
    } else {
        for (int i = 0; i < nums.length; i++) {
            if (used[i]) continue; // 跳过已使用的元素
            used[i] = true;
            tempList.add(nums[i]);
            backtrack(res, tempList, nums, used);
            used[i] = false;
            tempList.remove(tempList.size() - 1);
        }
    }
}

组合框架模板

void backtrack(List<List<Integer>> res, List<Integer> tempList, int[] nums, int start) {
    res.add(new ArrayList<>(tempList));
    for (int i = start; i < nums.length; i++) {
        tempList.add(nums[i]);
        backtrack(res, tempList, nums, i + 1); // 从下一个元素开始
        tempList.remove(tempList.size() - 1);
    }
}
  • 排列中元素的顺序重要,通常使用一个 boolean[] used 数组来跟踪已选择的元素。
  • 组合中元素的顺序不重要,使用 start 参数控制递归的起始位置,以避免重复的组合。

解题

46. 全排列 - 力扣(LeetCode)

class Solution {

    private List<List<Integer>> res;

    private List<Integer> tmp;

    public List<List<Integer>> permute(int[] nums) {
        res = new ArrayList<>();
        tmp = new ArrayList<>();
        int n = nums.length;
        boolean[] used = new boolean[n];
        backtrack(n,nums,used);
        return res;
    }

    private void backtrack(int n ,int[] nums,boolean[] used){
        if(tmp.size() == n){
            res.add(new ArrayList<>(tmp));
        }
        for(int i = 0 ; i < nums.length; i++){
            if(!used[i]){
                used[i] = true;
                tmp.add(nums[i]);
                backtrack(n,nums,used);
                used[i] = false;
                tmp.remove(tmp.size()-1);
            }
        }
    }
}

77. 组合 - 力扣(LeetCode)

class Solution {

    private List<List<Integer>> res;

    private List<Integer> tmp;

    public List<List<Integer>> combine(int n, int k) {
        res = new ArrayList<>();
        tmp = new ArrayList<>();

        backtrack(n,k,1);
        return res;
    }

    private void backtrack(int n , int k ,int start){
        //剪枝操作
        if(tmp.size() + (n-start+1) < k){
            return;
        }
        if(tmp.size() == k){
            res.add(new ArrayList<>(tmp));
        }
        for(int i = start ; i <= n ; i++){
            tmp.add(i);
            backtrack(n,k,i+1);
            tmp.remove(tmp.size() - 1);
        }
    }
}

216. 组合总和 III - 力扣(LeetCode)

class Solution {

    private List<Integer> path;

    private List<List<Integer>> res;

    public List<List<Integer>> combinationSum3(int k, int n) {
        path = new ArrayList<>();
        res = new ArrayList<>();
        backtrack(k,n,1,0);
        return res;
    }

    private void backtrack( int k , int n,int cur, int sum ){
        if (sum > n) { // 剪枝操作
            return;
        }
        if(path.size() == k && sum == n){
            res.add(new ArrayList<>(path));
        }

        for(int i = cur ; i <= 9 - (k-path.size()) + 1; i++){
            path.add(i);
            backtrack(k,n,i+1,sum + i);
            path.remove(path.size()-1);
        }

    }
}