给定一个候选人编号的集合 candidates 和一个目标数 target ,找出 candidates 中所有可以使数字和为 target 的组合。

candidates 中的每个数字在每个组合中只能使用 一次

注意:解集不能包含重复的组合。

示例 1:

输入: candidates = [10,1,2,7,6,1,5], target = 8,
输出:
[
[1,1,6],
[1,2,5],
[1,7],
[2,6]
]

示例 2:

输入: candidates = [2,5,2,1,2], target = 5,
输出:
[
[1,2,2],
[5]
]

提示:

  • 1 <= candidates.length <= 100
  • 1 <= candidates[i] <= 50
  • 1 <= target <= 30

思路

本题的思路与组合总数大抵一致,但由于本题存在重复元素,所以要先进行排序,然后剪枝,否则会超时。

如果当前搜索的序列中的前缀存在重复元素,只需将前缀中的重复元素去除即可。

如序列1,1,1,2,3的搜索结果中一定包含了1,1,2,3的搜索结果。

class Solution {

public static int[] nums;
public static Map<String, List<Integer>> ans;
public static List<Integer> tmp;

public List<List<Integer>> combinationSum2(int[] candidates, int target) {
this.nums = candidates;
this.ans = new HashMap<>();
this.tmp = new ArrayList<>();
// 排序一下
sort(0, nums.length);
System.out.println(Arrays.toString(nums));
dfs(target, 0);
return ans.values().stream().collect(Collectors.toList());
}

// quick sort in [low, hgih)
public static void sort(int low, int high) {
if (high - low < 2) return;
// 优化
swap(low, (int)(low + Math.random() * (high - low - 1)));
int i = low , j = high - 1;
boolean direction = true;
while (i < j) {
if (nums[i] > nums[j]) {
swap(i, j);
direction = !direction;
}
int tmp = direction ? i++ : j--;
}
sort(low, i); sort(i+1, high);
}

// 两个元素不能相等
public static void swap(int x, int y) {
if (nums[x] == nums[y]) return;
nums[x] ^= nums[y];
nums[y] ^= nums[x];
nums[x] ^= nums[y];
}

public static void dfs(int target, int start) {
if (target < 0) return;

if (target == 0) {
List<Integer> l = new ArrayList<>(tmp);
l.sort(
new Comparator<Integer>() {
public int compare(Integer o1, Integer o2) {
return o1 - o2;
}
}
);
ans.put(l.toString(), l);

return;
}

for (int i = start; i < nums.length; i++) {
// 当前将要搜索的数字和前一个数字相同 则可以跳过 即剪枝
// 注意,第一个元素一定是被加入搜索区间搜索过了的
if (i -1 >= start && nums[i] == nums[i-1]) continue;
tmp.add(nums[i]);
dfs(target - nums[i], i+1);
tmp.remove(tmp.size() - 1);

}
}


}

复杂度

与组合总数一样