Combination Sum

本文介绍了一个编程问题,要求从给定的不重复整数数组中找出所有和为目标值的独特组合。使用深度优先搜索算法进行遍历,同时讨论了时间复杂度(指数级)和空间复杂度(O(n))。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

Problem

Given an array of distinct integers candidates and a target integer target, return a list of all unique combinations of candidates where the chosen numbers sum to target. You may return the combinations in any order.

The same number may be chosen from candidates an unlimited number of times. Two combinations are unique if the 

frequency

 of at least one of the chosen numbers is different.

The test cases are generated such that the number of unique combinations that sum up to target is less than 150 combinations for the given input.

Example 1:

Input: candidates = [2,3,6,7], target = 7
Output: [[2,2,3],[7]]
Explanation:
2 and 3 are candidates, and 2 + 2 + 3 = 7. Note that 2 can be used multiple times.
7 is a candidate, and 7 = 7.
These are the only two combinations.

Example 2:

Input: candidates = [2,3,5], target = 8
Output: [[2,2,2,2],[2,3,3],[3,5]]

Example 3:

Input: candidates = [2], target = 1
Output: []

Intuition

The task is to find all possible combinations of elements from an array candidates such that their sum is equal to a given target. The solution involves using a depth-first search (DFS) approach to explore all possible combinations.

Approach

Initialize Result List:

Create an empty list res to store the resulting combinations.
DFS Function:

Implement a DFS function (dfs) that takes three arguments: the current index i, the current combination cur, and the current sum total.
The base case is when total is equal to the target. In this case, add a copy of the current combination cur to the result res.
In the recursive case:
Include the element at index i in the current combination cur.
Call the DFS function with the same index i, the updated combination cur, and the updated sum total + candidates[i].
Remove the last element from the current combination to backtrack.
Call the DFS function with the next index i + 1, the current combination cur, and the unchanged sum total.
Main Function:

Call the DFS function with an initial index of 0, an empty combination, and an initial sum of 0.
Return Result:

Return the final result res.

Complexity

  • Time complexity:

The time complexity is determined by the number of recursive calls and is exponential in the worst case. It is influenced by the branching factor and the depth of the recursion.

  • Space complexity:

The space complexity is O(n) due to the recursion stack. Additionally, the space required for the cur list contributes to the space complexity. The result res contains the combinations, each with an average length of n/2, resulting in a space complexity of O(n * 2^n).

Code

class Solution:
    def combinationSum(self, candidates: List[int], target: int) -> List[List[int]]:
        res = []

        def dfs(i, cur, total):
            if total == target:
                res.append(cur.copy())
                return
            elif i >= len(candidates) or total > target:
                return

            cur.append(candidates[i])
            dfs(i, cur, total + candidates[i])
            cur.pop()
            dfs(i + 1, cur, total)

        dfs(0, [], 0)
        return res
<think>好的,我现在要解决这个问题:给定一个候选数字数组candidates和一个目标数target,找出所有唯一组合使得它们的和等于target,每个数字在每个组合中只能使用一次,并且结果要按升序排列。用户还给出了Java的代码框架,需要补全。 首先,我需要理解问题要求。题目中的关键点有几个:每个数字在每个组合中只能使用一次,结果不能有重复的组合,且结果需要按升序排列返回。例如,如果输入是[10,1,2,7,6,1,5],target=8,那么可能的组合有[1,1,6], [1,2,5], [1,7], [2,6]。这些组合需要按升序排列,并且结果列表本身也要有序。 接下来,我需要确定用什么算法来解决这个问题。这类组合求和问题通常可以用回溯法(DFS)来处理。但由于数组中可能有重复元素,并且每个元素只能用一次,所以需要考虑去重的步骤。例如,如果数组中有两个1,那么在同一个递归层级中,不能多次选择相同的数字,否则会导致重复的组合。 然后,我需要考虑如何处理重复元素。首先,应该对数组进行排序,这样相同的元素会排列在一起。在回溯的过程中,当遇到重复的元素时,需要跳过这些重复的元素,以避免生成重复的组合。例如,排序后的数组是[1,1,2,5,6,7,10],当处理第一个1之后,同一层级的后续1应该被跳过,因为它们已经被处理过了,这样可以避免生成重复的组合。 接下来是回溯法的具体步骤。回溯函数需要跟踪当前的路径、起始索引、剩余的目标值。每次选择一个数字后,递归调用函数处理剩下的元素,同时起始索引加1,因为每个数字只能用一次。如果剩余的目标值变为0,说明当前路径是一个有效的组合,将其加入结果列表。如果剩余目标值小于0,则剪枝,提前终止递归。 然后,我需要考虑代码的结构。用户提供的代码框架中有一个方法combinationSum2,返回类型是int[][],但通常组合结果会用List<List<Integer>>来存储,最后再转换为二维数组。因此,在实现过程中,可能需要先将结果存储在List中,最后再转换格式。转换时需要注意每个内部列表需要排序,并且整个结果列表也需要排序,以满足题目要求的升序排列。不过,由于在回溯前已经对原数组进行了排序,所以在生成组合时,路径自然就是有序的,不需要额外排序每个组合。但整个结果列表可能会有不同的顺序,需要确保最终结果的每个组合是唯一的,并且整体按升序排列。不过,如果原数组已经排序,并且回溯时是按顺序处理的,那么生成的组合自然就是有序的,且结果列表中的每个组合也是按升序排列的,不需要额外排序。 现在,我需要处理代码中的具体实现。首先,对候选数组进行排序。然后,使用回溯法,遍历每个元素,跳过重复的元素,避免重复组合。在每次递归调用时,起始索引是当前索引加1,因为每个元素只能用一次。此外,当当前元素大于剩余目标值时,可以提前终止循环,因为后面的元素更大,无法满足条件。 在代码实现时,需要注意Java中List的操作,例如添加路径时要新建一个ArrayList,避免引用问题。最后,将结果从List<List<Integer>>转换为int[][],每个内部列表转换为数组,并且整个结果数组需要按升序排列。不过,由于回溯过程中已经按照排序后的数组进行处理,生成的组合本身就是有序的,所以结果列表中的组合顺序应该是正确的,不需要额外排序。 可能的错误点包括:未正确去重,导致生成重复的组合;在递归调用时起始索引未正确增加,导致重复使用元素;转换结果时处理不当,导致返回的数组不符合要求。 例如,处理数组[1,1,2]和target=3时,正确的组合应该是[1,2]和[1,2]吗?不,因为两个1是不同的元素,但它们的值相同,所以应该视为同一组合,即只出现一次[1,2]。所以需要跳过同一层级中的重复元素,确保同一位置不会选择相同的数字多次。 总结步骤: 1. 对候选数组排序。 2. 使用回溯法,遍历每个元素。 3. 在每一层递归中,跳过重复的元素。 4. 递归调用时,起始索引+1,避免重复使用同一元素。 5. 收集所有符合条件的组合,转换为要求的输出格式。 现在,我需要编写具体的代码,并测试一些案例,确保正确性。例如,输入candidates = [10,1,2,7,6,1,5], target=8,排序后变为[1,1,2,5,6,7,10]。回溯过程中,选择第一个1,然后剩余目标为7,接着在后面的元素中选择,可能找到1(第二个1)、2、5等,但需要注意不能重复使用同一位置的元素。需要确保生成的组合正确,且没有重复。</think>#### 问题分析 给定可能包含重复元素的数组 `candidates` 和目标值 `target`,需找出所有**唯一组合**使得和为 `target`,要求每个数字在每个组合中**只能使用一次**。关键挑战在于**去重**和**剪枝优化**。 --- #### 算法设计(回溯+剪枝) 1. **排序预处理**:将数组排序,使相同元素相邻,方便跳过重复项。 2. **回溯框架**: - 用 `start` 控制遍历起点,避免重复使用元素 - 当 `target == 0` 时记录有效组合 - 遇到重复元素时跳过(`i > start` 时比较当前与前一个元素) 3. **剪枝优化**:若当前元素值已超过剩余 `target`,提前终止循环。 --- #### Java 代码实现 ```java import java.util.*; public class Solution { public int[][] combinationSum2(int[] candidates, int target) { Arrays.sort(candidates); // 关键步骤:排序以方便去重 List<List<Integer>> result = new ArrayList<>(); backtrack(candidates, target, 0, new ArrayList<>(), result); return convertToArray(result); } private void backtrack(int[] nums, int remain, int start, List<Integer> path, List<List<Integer>> result) { if (remain == 0) { result.add(new ArrayList<>(path)); return; } for (int i = start; i < nums.length; i++) { // 剪枝:当前值超过剩余目标时跳过后续更大值 if (nums[i] > remain) break; // 去重:跳过同一层中相同的数字(i > start 保证不同层) if (i > start && nums[i] == nums[i-1]) continue; path.add(nums[i]); backtrack(nums, remain - nums[i], i + 1, path, result); path.remove(path.size() - 1); } } // 转换结果到二维数组 private int[][] convertToArray(List<List<Integer>> list) { int[][] res = new int[list.size()][]; for (int i = 0; i < list.size(); i++) { List<Integer> subList = list.get(i); res[i] = new int[subList.size()]; for (int j = 0; j < subList.size(); j++) { res[i][j] = subList.get(j); } } return res; } } ``` --- #### 关键点解析 1. **去重逻辑**: - 条件 `i > start && nums[i] == nums[i-1]` 确保只在**同一层级**跳过重复元素,允许不同层级使用相同值。 - 例如 `[1,1,2]` 中,当 `start=0` 时,第一个 `1` 被选中后,第二个 `1` 在同一层级会被跳过,避免生成重复组合 `[1,2]`。 2. **剪枝优化**: - 排序后若 `nums[i] > remain`,可以直接 `break` 而非 `continue`,因为后续元素更大,必然无法满足条件。 3. **复杂度分析**: - 时间复杂度:$O(2^n)$(最坏情况需遍历所有子集) - 空间复杂度:$O(n)$(递归栈深度) --- #### 示例验证 输入:`candidates = [10,1,2,7,6,1,5]`, `target = 8` 排序后:`[1,1,2,5,6,7,10]` 输出: ``` [ [1,1,6], [1,2,5], [1,7], [2,6] ] ``` 算法通过剪枝和去重机制,确保结果无重复且高效。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值