无重复元素问题
1. 无重复元素组合
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. 无重复元素排列
解法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. 重复元素排列
解法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;
}