Pick Each Number At Most Once to Reach a Target Sum
Solve this Problemcandidates, which may hold repeated values, and a target. Find every distinct combination of values that sums exactly to target — this time each position in the array can be used at most once, and even though positions repeat values, the result must not contain the same value-combination twice.
Since each position is used 0 or 1 times, every combination is really a subset — brute force can enumerate all 2ⁿ of them and filter by sum, but has to actively watch for and discard duplicates that arise from picking different positions that happen to share a value. Sorting first turns that after-the-fact cleanup into a cheap, built-in check: equal values end up adjacent, so a single neighbor comparison at each step is enough to skip a duplicate before it's ever explored.
Test Case 1:
Test Case 2:
Test Case 3:
Constraints
- ◆
1 ≤ candidates.length ≤ 15 - ◆
1 ≤ candidates[i] ≤ 25, and values may repeat within candidates - ◆
1 ≤ target ≤ 35 - ◆
Each position in candidates may be used at most once per combination
Try the Dry Run
Don't just read the solution — watch it execute, one step at a time.
Approach & Solutions
Brute Force — Enumerate Every Selection, Deduplicate by Value
BruteSince every position is either picked or not, this is a subset problem: try all 2ⁿ position-selections via bitmask, sum each one, and keep the ones that hit target. Repeated values create the real cost here — two different position-selections that happen to carry the same values (e.g. picking the first 4 versus picking the second 4) produce the identical combination, so each match's values are sorted into a canonical key and checked against everything already recorded before being kept. Correct, but the full 2ⁿ selections are built and checked regardless of how many of them turn out to be redundant.
O(2ⁿ · n log n)O(2ⁿ · n)1class Solution {
2 public int[][] combinationsUsingEachOnce(int[] candidates, int target) {
3 int n = candidates.length;
4 Set<String> seen = new HashSet<>();
5 List<int[]> result = new ArrayList<>();
6 for (int mask = 0; mask < (1 << n); mask++) {
7 List<Integer> subset = new ArrayList<>();
8 int sum = 0;
9 for (int i = 0; i < n; i++) {
10 if ((mask & (1 << i)) != 0) {
11 subset.add(candidates[i]);
12 sum += candidates[i];
13 }
14 }
15 if (sum == target) {
16 Collections.sort(subset);
17 String key = subset.toString();
18 if (seen.add(key)) {
19 int[] row = new int[subset.size()];
20 for (int j = 0; j < row.length; j++) row[j] = subset.get(j);
21 result.add(row);
22 }
23 }
24 }
25 return result.toArray(new int[0][]);
26 }
27}Optimal — Sorted Backtracking, Skip Same-Level Duplicates
OptimalSort first, then build combinations one position at a time with two cooperating cuts. The prune cut is the same one as the unrestricted version: once a candidate would push the running sum past target, every candidate after it (sorted ascending) would too, so break immediately. The new cut handles duplicate values directly: within one call's loop, skip any candidate equal to the one tried just before it at that same position — trying it would only rebuild a combination already produced by the identical value picked a moment ago, never something new. No hashing or after-the-fact comparison is needed; duplicates are recognized and skipped the instant they'd be considered.
O(2ⁿ)O(n)1class Solution {
2 public int[][] combinationsUsingEachOnce(int[] candidates, int target) {
3 int[] sorted = candidates.clone();
4 Arrays.sort(sorted);
5 List<int[]> result = new ArrayList<>();
6 List<Integer> path = new ArrayList<>();
7 backtrack(sorted, target, 0, 0, path, result);
8 return result.toArray(new int[0][]);
9 }
10
11 private void backtrack(int[] sorted, int target, int start, int sum, List<Integer> path, List<int[]> result) {
12 if (sum == target) {
13 result.add(toArray(path));
14 return;
15 }
16 for (int i = start; i < sorted.length; i++) {
17 if (i > start && sorted[i] == sorted[i - 1]) continue;
18 if (sum + sorted[i] > target) break;
19 path.add(sorted[i]);
20 backtrack(sorted, target, i + 1, sum + sorted[i], path, result);
21 path.remove(path.size() - 1);
22 }
23 }
24
25 private int[] toArray(List<Integer> path) {
26 int[] row = new int[path.size()];
27 for (int j = 0; j < row.length; j++) row[j] = path.get(j);
28 return row;
29 }
30}