不同的子序列与数组中的给定数字相加

时间:2013-06-15 16:10:07

标签: algorithm

在我目前的面试准备期间,我遇到了一个问题,我很难找到最佳解决方案 我们得到一个数组A和一个整数Sum,我们需要查找A的所有不同子序列,其总和等于Sum
例如。 A={1,2,3,5,6} Sum=6然后回答应该是 {1,2,3}
{1,5}
{6}

目前,我可以想到两种方法,

  1. 使用递归(我认为应该是面试问题的最后一件事)
  2. 使用整数分区来分区Sum并检查A
  3. 中是否存在分区元素

    请指导我的想法。

3 个答案:

答案 0 :(得分:5)

我同意杰森的观点。想到这个解决方案:
(如果将地图表示为数组,则复杂度为O(sum*|A|)

  • 调用输入集A和目标总和sum
  • 有一个元素B的地图,每个元素都是x:y,其中x(地图键)是总和而y(地图值)是方式的数量得到它。
  • 开始,将0:1添加到地图中 - 有一种方法可以达到0(显然不使用任何元素)
  • 对于A中的每个元素a,请考虑B中的每个元素x:y
    • 如果x+a> sum,不要做任何事情。
    • 如果B中已存在键x+a的元素,请说该元素为x+a:z,请将其修改为x+a:y+z
    • 如果包含密钥的元素不存在,只需将x+a:y添加到集合中。
  • 使用键sum查找元素,因此sum:x - x是我们想要的值。

如果B被排序(或数组),您可以在“不做任何事情”步骤中跳过B中的其余元素。

追溯:

以上只是给出了计数,这将修改它以给出实际的子序列。

在B中的每个元素处,而不是总和,存储所有源元素和用于到达那里的元素(因此在B中的每个元素都有一对对的列表)。

对于0:1,没有源元素。

对于x+a:y,源元素为x,要到达的元素为a

在上述过程中,如果包含密钥的元素已存在,请将对x/a排入元素x+a(enqueue为O(1)操作。)

如果包含该键的元素不存在,只需在元素x/a处创建一对x+a的列表。

要重建,只需从sum开始,然后递归追溯。

我们必须注意重复的序列(我们吗?)和重复元素的序列。

示例 - 不追溯:

A = {1,2,3,5,6}
sum = 6

B = 0:1

考虑1
添加0+1
B = 0:1, 1:1

考虑2
添加0+2:11+2:1
B = 0:1, 1:1, 2:1, 3:1

考虑3
添加0+3:1(已存在 - >添加1),1+3:12+1:13+1:1
B = 0:1, 1:1, 2:1, 3:2, 4:1, 5:1, 6:1

考虑5
B = 0:1, 1:1, 2:1, 3:2, 4:1, 5:2, 6:2
生成的总和被抛弃= 7:1, 8:2, 9:1, 10:1, 11:1

考虑6
B = 0:1, 1:1, 2:1, 3:2, 4:1, 5:2, 6:3
生成的总和被抛弃= 7:1, 8:1, 9:2, 10:1, 11:2, 12:2

然后,从6:3,我们知道我们有3种方法可以达到6。

示例 - 追溯:

A = {1,2,3,5,6}
sum = 6

B = 0:{}

考虑1
B = 0:{}, 1:{0/1}

考虑2
B = 0:{}, 1:{0/1}, 2:{0/2}, 3:{1/2}

考虑3
B = 0:{}, 1:{0/1}, 2:{0/2}, 3:{1/2,0/3}, 4:{1/3}, 5:{2/3}, 6:{3/3}

考虑5
B = 0:{}, 1:{0/1}, 2:{0/2}, 3:{1/2,0/3}, 4:{1/3}, 5:{2/3,0/5}, 6:{3/3,1/5} 生成的总和被抛弃= 7, 8, 9, 10, 11

考虑6
B = 0:{}, 1:{0/1}, 2:{0/2}, 3:{1/2,0/3}, 4:{1/3}, 5:{2/3,0/5}, 6:{3/3,1/5,0/6} 生成的总和被抛弃= 7, 8, 9, 10, 11, 12

然后,从6追溯:(不在{}中表示实际元素,{}表示地图条目)

{6}
  {3}+3
    {1}+2+3
      {0}+1+2+3
      1+2+3
      Output {1,2,3}
    {0}+3+3
      3+3
      Invalid - 3 is duplicate
  {1}+5
    {0}+1+5
      1+5
      Output {1,5}
  {0}+6
    6
      Output {6}

答案 1 :(得分:0)

这是subset-sum问题的变体。子集和问题要求如果有一个子集总和给定一个值。您要求所有子集的总和为给定值。

子集和问题很难(更确切地说,它是NP-Complete)这意味着你的变体也很难(它不是NP-Complete,因为它不是决策问题,但它是NP-Hard) 。

子集和问题的经典方法是递归或动态编程。显而易见,如何修改子集求和问题的递归解决方案来回答您的变体。我建议你也看看dynamic programming solution到subset-sum,看看你是否可以为你的变体修改它(tbc:我不知道这是否真的可行)。无论是否可能,这肯定是一项非常有价值的学习练习,因为它无论如何都会增强您对动态编程的理解。

但是,如果你的问题的预期答案不是递归解决方案,那会让我感到惊讶。它很容易提出,并且是一个可接受的问题解决方法。要求动态编程解决方案有点问题。

然而,你确实忽略了对这个问题的一种非常天真的方法:生成所有子集,并为每个子集检查它是否与给定值相加。显然这是指数级的,但确实解决了这个问题。

答案 2 :(得分:0)

我假设给定的数组包含不同的数字。 让我们定义函数f(i,s) - 这意味着我们在范围[1,i]中使用了一些数字,并且使用的数字的总和是s。

让我们将所有值存储在2维矩阵中,即在单元格(i,j)中,我们将具有f(i,j)的值。现在,如果已经计算了位于上方或小区(i,s)之后的单元格的值,我们可以计算f(i,s)的值,​​即f(i,s)= f(i-1,s);(如果(s> = a [[i])f(i,s)+ = f(i - 1,s - a [i]),则不取i索引数。我们可以使用自下而上的方法填充所有矩阵,设置[f(0,0)= 1; f(0,i)= 0; 1< = i< = s],[f(i,0)= 1; 1< = i< = n;]。如果我们计算了所有矩阵,那么我们在单元格f(n,S)中得到答案;因此,我们有总时间复杂度O(n * s)和存储器复杂度O(n * s);

如果我们注意到在每次迭代中我们只需要来自前一行的信息,我们就可以提高内存复杂度,这意味着我们可以存储大小为2xS但不是nxS的矩阵。我们将内存复杂度降低到线性为S.这个问题是NP完全的,因此我们没有多项式算法,这种方法是最好的。