【Java】使用回溯法解决元素的排列组合问题

无重复元素问题

1. 无重复元素组合

leetcode 77. 组合

 public List<List<Integer>> combine(int n, int k) {
    
    
        List<List<Integer>> ans = new LinkedList<>();

        int[] num = new int[n];
        for (int i = 0; i < num.length; i++) {
    
    
            num[i] = i + 1;
        }
        backTracking(ans, new Stack<>(), 0, k, num);
        return ans;
    }

解法1: 拼接法

通过直接模拟思维而来一种方法

 private void backTracking(List<List<Integer>> ans, Stack<Integer> cur, int start, int k, int[] num) {
    
    
        if (cur.size() + (num.length - start) < k)
        	return;
        if (cur.size() == k) {
    
    
            ans.add(new ArrayList<>(cur));
            return;
        }
        for (int i = start; i < num.length; i++) {
    
    
            cur.push(num[i]);
            backTracking(ans, cur, i + 1, k, num);
            cur.pop();
        }
    }

解法2: 01法(更快)

通过解向量思考而来的一种方法,因为组合的解对应一组向量(X1,X2,X3,…Xn)其中Xi = 0或1.

private void backTracking01(List<List<Integer>> ans, Stack<Integer> cur, int start, int k, int[] num) {
    
    
        if (num.length - start + cur.size() < k)
            return;
        if (cur.size() == k) {
    
    
            ans.add(new ArrayList<>(cur));
            return;
        }
        cur.push(num[start]);
        backTracking(ans, cur, start + 1, k, num);
        cur.pop();
        backTracking(ans, cur, start + 1, k, num);
    }

2. 无重复元素排列

leetcode 46. 全排列

解法1: 拼接法

直接模拟构造排列的思维

private void backTracking(List<Integer> res, List<List<Integer>> ans, Stack<Integer> cur) {
    
    
        if (res.size() == 0) {
    
    
            ans.add(new ArrayList<>(cur));
        }
        for (var re : res) {
    
    
            List<Integer> newRes = new ArrayList<>(res);
            newRes.remove(re);
            cur.push(re);
            backTracking(newRes, ans, cur);
            cur.pop();
        }
    }

解法2: 交换法(更快)

通过交换元素达到构造排列的目的

private void backTrackingSwap(int start, List<List<Integer>> ans, int[] num) {
    
    
        if (start == num.length - 1) {
    
    
            List<Integer> c = new LinkedList<>();
            for (int i : num) {
    
    
                c.add(i);
            }
            ans.add(c);
        }
        for (int i = start; i < num.length; i++) {
    
    
            swap(i, start, num);
            backTrackingSwap(start + 1, ans, num);
            swap(i, start, num);
        }
    }

    private void swap(int i, int j, int[] num) {
    
    
        var tmp = num[i];
        num[i] = num[j];
        num[j] = tmp;
    }

重复元素问题

1. 重复元素组合

leetcode 39. 组合总和
原数组无重复元素,结果集中的元素允许重复元素

解法1: 拼接法

直接模拟

解法2: 01法

尽管有重复,仍然可以用。和无重复不同的是,0和1对应的递归调用的参数变一下就行

public List<List<Integer>> combinationSum(int[] candidates, int target) {
    
    
        List<List<Integer>> ans = new LinkedList<>();
        Stack<Integer> cur = new Stack<>();
        backTracking(ans, cur, candidates, 0, target, 0);
        return ans;
    }

    private void backTracking(List<List<Integer>> ans, Stack<Integer> cur, int[] candidates, int sum, int target, int start) {
    
    
        if (start == candidates.length)
            return;
        if (sum > target)
            return;
        if (sum == target) {
    
    
            ans.add(new ArrayList<>(cur));
            return;
        }
        // 对应1,取当前数字;后面还可以重复取这个数字,所以start不变
        cur.push(candidates[start]);
        backTracking(ans, cur, candidates, sum + candidates[start], target, start);
        cur.pop();
        // 对应0,不取当前数字;后面就不取这个数字了,所以start+1
        backTracking(ans, cur, candidates, sum, target, start + 1);
    }

leetcode 40. 组合总合II
原数组元素中有重复,而结果集元素不能有重复

解法1: 拼接法

 public List<List<Integer>> combinationSum2(int[] candidates, int target) {
    
    
        Arrays.sort(candidates);
        List<List<Integer>> ans = new LinkedList<>();
        Stack<Integer> cur = new Stack<>();
        backTracking(ans, cur, candidates, 0, target, 0);
        return ans;
    }

    private void backTracking(List<List<Integer>> ans, Stack<Integer> cur, int[] candidates, int sum, int target, int start) {
    
    
        if (sum  > target)
            return;
        if (sum == target) {
    
    
            ans.add(new ArrayList<>(cur));
            return;
        }

        HashMap<Integer, Boolean> map = new HashMap<>();
        for (int i = start; i < candidates.length; i++) {
    
    
            var c = candidates[i];

            if (map.get(c) != null || c == Integer.MIN_VALUE)
                continue;
            map.put(c, true);
            candidates[i] = Integer.MIN_VALUE;
            cur.push(c);
            backTracking(ans, cur, candidates, sum + c, target, start + 1);
            cur.pop();
            candidates[i] = c;
        }
    }
优化剪枝

if (sum + c > target) return;放到for里面剪更有效减少栈的调用

 public List<List<Integer>> combinationSum2(int[] candidates, int target) {
    
    
        Arrays.sort(candidates);
        List<List<Integer>> ans = new LinkedList<>();
        Stack<Integer> cur = new Stack<>();
        backTracking(ans, cur, candidates, 0, target, 0);
        return ans;
    }

    private void backTracking(List<List<Integer>> ans, Stack<Integer> cur, int[] candidates, int sum, int target, int start) {
    
    
    
        if (sum == target) {
    
    
            ans.add(new ArrayList<>(cur));
            return;
        }

        HashMap<Integer, Boolean> map = new HashMap<>();
        for (int i = start; i < candidates.length; i++) {
    
    
            var c = candidates[i];
            if (sum + c > target)
                return;
                
            if (map.get(c) != null || c == Integer.MIN_VALUE)
                continue;
                
            map.put(c, true);
            candidates[i] = Integer.MIN_VALUE;
            cur.push(c);
            backTracking(ans, cur, candidates, sum + c, target, i + 1);
            cur.pop();
            candidates[i] = c;
        }
    }

解法2: 01法

要用HashMap去重效率不高

2. 重复元素排列

leetcode 47. 全排列

解法1: 拼接法

没有多开一个List来放剩余的元素,这里使用的数组,但只对这种整数类型元素有效。无重复节点,剪枝剪得很完全

 public List<List<Integer>> permuteUnique(int[] nums) {
    
    
        List<List<Integer>> ans = new LinkedList<>();
        backTracking(ans, new Stack<>(), nums);
        return ans;
    }

    private void backTracking(List<List<Integer>> ans, Stack<Integer> cur, int[] num) {
    
    
        if (cur.size() == num.length) {
    
    
            ans.add(new ArrayList<>(cur));
            return;
        }
        boolean[] used = new boolean[22];
        for (int i = 0; i < num.length; i++) {
    
    
            int tmp = num[i];
            if (tmp == Integer.MIN_VALUE || used[tmp + 10])
                continue;
            used[tmp + 10] = true;
            cur.push(tmp);
            num[i] = Integer.MIN_VALUE;
            backTracking(ans, cur, num);
            num[i] = tmp;
            cur.pop();
        }
    }

解法2: 交换法(更慢)

即时剪枝也会出现重复的叶节点元素,在叶节点用HashMap判断重复效率不太高

  public List<List<Integer>> permuteUnique(int[] nums) {
    
    
        List<List<Integer>> ans = new LinkedList<>();
        HashMap<List<Integer>, Boolean> map = new HashMap<>();
        backTracking(0, ans, nums, map);
        return ans;
    }

    private void backTracking(int start, List<List<Integer>> ans, int[] num, HashMap<List<Integer>, Boolean> map) {
    
    
        if (start == num.length - 1) {
    
    
            List<Integer> cur = new LinkedList<>();
            for (int i : num) {
    
    
                cur.add(i);
            }
            if (map.get(cur) != null)
                return;
            map.put(cur, true);
            ans.add(cur);
            return;
        }

        for (int i = start; i < num.length; i++) {
    
    
            if (i != start && num[i] == num[start])
                continue;
            swap(i, start, num);
            backTracking(start + 1, ans, num, map);
            swap(i, start, num);
        }
    }

    private void swap(int i, int j, int[] num) {
    
    
        var tmp = num[i];
        num[i] = num[j];
        num[j] = tmp;
    }

猜你喜欢

转载自blog.csdn.net/qq_43709922/article/details/109999024
今日推荐