Combination Sum
update Jul 21, 2017 17:58
Given a set of candidate numbers (C) and a target number (T), find all unique combinations in C where the candidate numbers sums to T.
The same repeated number may be chosen from C unlimited number of times.
Notice
All numbers (including target) will be positive integers.
Elements in a combination (a1, a2, … , ak) must be in non-descending order. (ie, a1 ≤ a2 ≤ … ≤ ak).
The solution set must not contain duplicate combinations.
Example
Given candidate set [2,3,6,7] and target 7, a solution set is:
[7]
[2, 2, 3]
Basic Idea:
整个程序肯定是Subsets的框架,但是又有一些细节需要注意。
题目说每个数字可以使用多次,隐含了重复元素其实是没有意义的,所以我们可以先去重。去重的方法类似于quick sort中的partition函数,2 pointers。
如何实现多次使用某个元素呢?只要在dfs传递参数时传递 i 而不是 i + 1 即可。
Python Code:
class Solution:
# @param candidates, a list of integers
# @param target, integer
# @return a list of lists of integers
def combinationSum(self, candidates, target):
if target <= 0: return [[]]
if not candidates: return [[]]
res = []
# 先排序,去重,因为每个元素可以使用多次,所以可以放心去重
nums = self.removeDuplicates(candidates)
self.helper(nums, 0, [], res, target)
return res
# core function
def helper(self, nums, pos, path, res, remainSum):
# int[] nums, int pos, list<int> path, list<list<int>> res, int remainSum
# return: void
if remainSum == 0:
res.append(path[:])
return
for i in range(pos, len(nums)):
if nums[i] > remainSum: break # 因为升序排列
path.append(nums[i])
# 这里用 i 而不是 i + 1 是因为可以重复用一个数
self.helper(nums, i, path, res, remainSum - nums[i])
del path[-1]
# 排序并去重
def removeDuplicates(self, arr):
# int[] arr, return: void
arr.sort()
i = 0
for j in range(len(arr)):
if arr[i] != arr[j]:
arr[i + 1] = arr[j]
i += 1
return arr[: i + 1]
update Jan 9, 2017 15:35
Update: 新的思考(better solution)
首先我们分析一下之前这种方法的时间空间复杂度。之前的dfs算法中,一共会有 target / min(candidates)
层,每一层会有 len(candidates)
个分支,如果 input 的 target sum 非常大,会有很深的递归深度,同时也会有很高的时间复杂度(如果input: {1,5,10,25}
, target=99
, 则最大递归深度为 99 / 1 = 99
层,总时间复杂度 O(4^99)
);
接下来,我们换一种方法考虑(详见 DFS notes)。这次我们如下定义 DFS:
共有
len(candidates)
层;每层考虑使用一个 candidate 不同的次数;
所以第 i 层有
remain sum / candidates[i]
个分支;
如此,如果按照刚刚的 input,时间复杂度就变为了 O(99^4),显著降低。递归深度也从99变4,空间复杂度降低。
实现思路:
这种方法实现的时候和之前有了较大的不同,但是只要理解了 dfs 具体在每层做了什么就不难写了。因为我们对于每个数考虑不同次数选用它,在dfs中一定会有一个 for loop 来进行这件事情,同时因为candidates的长度固定,我们可以传入一个int[len(candidates)] times
来记录选取每个 candidates 的次数,框架为:
for (int i = 0; candidates[pos] * i <= remainSum; ++i) {
times[pos] = i;
dfs(pos + 1, remainSum - i * candidates[pos], ...);
}
基础版本的写法,最终的结果就保存在了 int[] times
中,每个元素对应 candidates
相应元素的次数;
Java Code: (LintCode version)
public class Solution {
/*
* @param candidates: A list of integers
* @param target: An integer
* @return: A list of lists of integers
*/
public List<List<Integer>> combinationSum(int[] candidates, int target) {
List<List<Integer>> res = new ArrayList<>();
Arrays.sort(candidates);
dfs(candidates, target, 0, new int[candidates.length], res);
return res;
}
private void dfs(int[] candidates, int remainSum, int pos, int[] times,
List<List<Integer>> res) {
if (remainSum == 0 || pos == candidates.length) {
if (remainSum == 0) {
// convert times array to the occurrence of each element
List<Integer> lst = new ArrayList<>();
for (int i = 0; i < times.length; ++i) {
for (int k = 0; k < times[i]; ++k) {
lst.add(candidates[i]);
}
}
res.add(lst);
}
return;
}
// for the pos-th candidate, 检查选择它不同次数 i 的情况,继续向下一个数递归
for (int i = 0; i * candidates[pos] <= remainSum; ++i) {
times[pos] = i;
dfs(candidates, remainSum - i * candidates[pos], pos + 1, times, res);
times[pos] = 0;
}
}
}