你如何从给定的N个数字中测试所有可能的加法组合,使它们加起来得到给定的最终数字?
一个简单的例子:
要添加的数字集:N ={1,5,22,15,0,…} 期望结果:12345
你如何从给定的N个数字中测试所有可能的加法组合,使它们加起来得到给定的最终数字?
一个简单的例子:
要添加的数字集:N ={1,5,22,15,0,…} 期望结果:12345
当前回答
Java解决方案的Swift 3转换(by @JeremyThompson)
protocol _IntType { }
extension Int: _IntType {}
extension Array where Element: _IntType {
func subsets(to: Int) -> [[Element]]? {
func sum_up_recursive(_ numbers: [Element], _ target: Int, _ partial: [Element], _ solution: inout [[Element]]) {
var sum: Int = 0
for x in partial {
sum += x as! Int
}
if sum == target {
solution.append(partial)
}
guard sum < target else {
return
}
for i in stride(from: 0, to: numbers.count, by: 1) {
var remaining = [Element]()
for j in stride(from: i + 1, to: numbers.count, by: 1) {
remaining.append(numbers[j])
}
var partial_rec = [Element](partial)
partial_rec.append(numbers[i])
sum_up_recursive(remaining, target, partial_rec, &solution)
}
}
var solutions = [[Element]]()
sum_up_recursive(self, to, [Element](), &solutions)
return solutions.count > 0 ? solutions : nil
}
}
用法:
let numbers = [3, 9, 8, 4, 5, 7, 10]
if let solution = numbers.subsets(to: 15) {
print(solution) // output: [[3, 8, 4], [3, 5, 7], [8, 7], [5, 10]]
} else {
print("not possible")
}
其他回答
我不喜欢上面看到的Javascript解决方案。下面是我使用部分应用、闭包和递归构建的一个:
好的,我主要关心的是,如果组合数组能满足目标要求,希望这样你就能找到剩下的组合了
这里只需要设置目标并传递组合数组。
function main() {
const target = 10
const getPermutationThatSumT = setTarget(target)
const permutation = getPermutationThatSumT([1, 4, 2, 5, 6, 7])
console.log( permutation );
}
我提出的当前实现
function setTarget(target) {
let partial = [];
return function permute(input) {
let i, removed;
for (i = 0; i < input.length; i++) {
removed = input.splice(i, 1)[0];
partial.push(removed);
const sum = partial.reduce((a, b) => a + b)
if (sum === target) return partial.slice()
if (sum < target) permute(input)
input.splice(i, 0, removed);
partial.pop();
}
return null
};
}
import java.util.*;
public class Main{
int recursionDepth = 0;
private int[][] memo;
public static void main(String []args){
int[] nums = new int[] {5,2,4,3,1};
int N = nums.length;
Main main = new Main();
main.memo = new int[N+1][N+1];
main._findCombo(0, N-1,nums, 8, 0, new LinkedList() );
System.out.println(main.recursionDepth);
}
private void _findCombo(
int from,
int to,
int[] nums,
int targetSum,
int currentSum,
LinkedList<Integer> list){
if(memo[from][to] != 0) {
currentSum = currentSum + memo[from][to];
}
if(currentSum > targetSum) {
return;
}
if(currentSum == targetSum) {
System.out.println("Found - " +list);
return;
}
recursionDepth++;
for(int i= from ; i <= to; i++){
list.add(nums[i]);
memo[from][i] = currentSum + nums[i];
_findCombo(i+1, to,nums, targetSum, memo[from][i], list);
list.removeLast();
}
}
}
到目前为止,有很多解决方案,但都是生成然后过滤的形式。这意味着他们可能会在递归路径上花费大量时间,而这些递归路径不会导致解决方案。
这里的解决方案是O(size_of_array * (number_of_sum + number_of_solutions))。换句话说,它使用动态规划来避免列举永远不会匹配的可能解决方案。
为了搞笑,我让这个函数同时使用正数和负数,并让它成为一个迭代器。它适用于Python 2.3+。
def subset_sum_iter(array, target):
sign = 1
array = sorted(array)
if target < 0:
array = reversed(array)
sign = -1
# Checkpoint A
last_index = {0: [-1]}
for i in range(len(array)):
for s in list(last_index.keys()):
new_s = s + array[i]
if 0 < (new_s - target) * sign:
pass # Cannot lead to target
elif new_s in last_index:
last_index[new_s].append(i)
else:
last_index[new_s] = [i]
# Checkpoint B
# Now yield up the answers.
def recur(new_target, max_i):
for i in last_index[new_target]:
if i == -1:
yield [] # Empty sum.
elif max_i <= i:
break # Not our solution.
else:
for answer in recur(new_target - array[i], i):
answer.append(array[i])
yield answer
for answer in recur(target, len(array)):
yield answer
这里有一个例子,它与数组和目标一起使用,在其他解决方案中使用的过滤方法实际上永远不会结束。
def is_prime(n):
for i in range(2, n):
if 0 == n % i:
return False
elif n < i * i:
return True
if n == 2:
return True
else:
return False
def primes(limit):
n = 2
while True:
if is_prime(n):
yield(n)
n = n + 1
if limit < n:
break
for answer in subset_sum_iter(primes(1000), 76000):
print(answer)
这将在2秒内打印所有522个答案。之前的方法如果能在宇宙当前的生命周期内找到答案,那就太幸运了。(整个空间有2^168 = 3.74144419156711e+50个可能的组合。那需要一段时间。)
解释 我被要求解释代码,但解释数据结构通常更能说明问题。我来解释一下数据结构。
让我们考虑subset_sum_iter([2, 2、3、3、5、5、7、7、-11、11),10)。
在检查点A,我们已经意识到我们的目标是正的,所以符号= 1。我们已经对输入进行了排序,使array =[-11, -7, -5, -3, -2, 2,3,5,7,11]。由于我们经常通过索引访问它,下面是从索引到值的映射:
0: -11
1: -7
2: -5
3: -3
4: -2
5: 2
6: 3
7: 5
8: 7
9: 11
通过检查点B,我们使用动态规划生成last_index数据结构。它包含什么?
last_index = {
-28: [4],
-26: [3, 5],
-25: [4, 6],
-24: [5],
-23: [2, 4, 5, 6, 7],
-22: [6],
-21: [3, 4, 5, 6, 7, 8],
-20: [4, 6, 7],
-19: [3, 5, 7, 8],
-18: [1, 4, 5, 6, 7, 8],
-17: [4, 5, 6, 7, 8, 9],
-16: [2, 4, 5, 6, 7, 8],
-15: [3, 5, 6, 7, 8, 9],
-14: [3, 4, 5, 6, 7, 8, 9],
-13: [4, 5, 6, 7, 8, 9],
-12: [2, 4, 5, 6, 7, 8, 9],
-11: [0, 5, 6, 7, 8, 9],
-10: [3, 4, 5, 6, 7, 8, 9],
-9: [4, 5, 6, 7, 8, 9],
-8: [3, 5, 6, 7, 8, 9],
-7: [1, 4, 5, 6, 7, 8, 9],
-6: [5, 6, 7, 8, 9],
-5: [2, 4, 5, 6, 7, 8, 9],
-4: [6, 7, 8, 9],
-3: [3, 5, 6, 7, 8, 9],
-2: [4, 6, 7, 8, 9],
-1: [5, 7, 8, 9],
0: [-1, 5, 6, 7, 8, 9],
1: [6, 7, 8, 9],
2: [5, 6, 7, 8, 9],
3: [6, 7, 8, 9],
4: [7, 8, 9],
5: [6, 7, 8, 9],
6: [7, 8, 9],
7: [7, 8, 9],
8: [7, 8, 9],
9: [8, 9],
10: [7, 8, 9]
}
(旁注,它不是对称的,因为条件if 0 < (new_s - target) *符号阻止我们记录超过target的任何内容,在我们的例子中是10。)
这是什么意思?以条目10为例:[7,8,9]。这意味着我们可以得到10的最终和,最后选择的数字在索引7、8或9处。也就是说,最后选择的数字可以是5,7或11。
让我们仔细看看如果我们选择索引7会发生什么。这意味着我们以5结束。因此,在得到下标7之前,我们必须得到10-5 = 5。5的条目为5:[6,7,8,9]。所以我们可以选择指数6,也就是3。虽然我们在第7、8和9处得到了5,但在第7号下标之前我们没有得到5。所以倒数第二个选项是指数6处的3。
现在我们要在下标6之前得到5-3 = 2。条目2是:2:[5,6,7,8,9]。同样,我们只关心下标5的答案因为其他的都发生得太晚了。所以倒数第三个选项是指数5处的2。
最后我们要在下标5之前得到2-2 = 0。条目0表示:0:[- 1,5,6,7,8,9]。同样,我们只关心-1。但是-1不是下标实际上我用它来表示我们已经完成了选择。
我们求出了2+3+5 = 10的解。这是我们打印出来的第一个解。
现在我们来看递归子函数。因为它是在main函数内部定义的,所以它可以看到last_index。
首先要注意的是,它调用的是yield,而不是return。这使它成为一个发电机。当你调用它时,你会返回一个特殊类型的迭代器。当你循环遍历那个迭代器时,你会得到一个它能产生的所有东西的列表。但你是在生成它们时得到它们的。如果它是一个很长的列表,你不把它放在内存中。(有点重要,因为我们可以得到一个很长的列表。)
recur(new_target, max_i)将产生的结果是你可以用数组中最大索引为max_i的元素求和为new_target的所有方法。这就是它的答案:“我们必须在索引max_i+1之前到达new_target。”当然,它是递归的。
因此,recur(target, len(array))是所有使用任意索引到达目标的解。这就是我们想要的。
这也可以用来打印所有的答案
public void recur(int[] a, int n, int sum, int[] ans, int ind) {
if (n < 0 && sum != 0)
return;
if (n < 0 && sum == 0) {
print(ans, ind);
return;
}
if (sum >= a[n]) {
ans[ind] = a[n];
recur(a, n - 1, sum - a[n], ans, ind + 1);
}
recur(a, n - 1, sum, ans, ind);
}
public void print(int[] a, int n) {
for (int i = 0; i < n; i++)
System.out.print(a[i] + " ");
System.out.println();
}
时间复杂度是指数级的。2^n的阶
下面是一个更好的版本,具有更好的输出格式和c++ 11特性:
void subset_sum_rec(std::vector<int> & nums, const int & target, std::vector<int> & partialNums)
{
int currentSum = std::accumulate(partialNums.begin(), partialNums.end(), 0);
if (currentSum > target)
return;
if (currentSum == target)
{
std::cout << "sum([";
for (auto it = partialNums.begin(); it != std::prev(partialNums.end()); ++it)
cout << *it << ",";
cout << *std::prev(partialNums.end());
std::cout << "])=" << target << std::endl;
}
for (auto it = nums.begin(); it != nums.end(); ++it)
{
std::vector<int> remaining;
for (auto it2 = std::next(it); it2 != nums.end(); ++it2)
remaining.push_back(*it2);
std::vector<int> partial = partialNums;
partial.push_back(*it);
subset_sum_rec(remaining, target, partial);
}
}