无法从数组中的数字之和形成的最小数字

时间:2014-01-12 17:19:51

标签: arrays algorithm data-structures dynamic-programming subset-sum

亚马逊采访中向我询问了这个问题 -

给定一个正整数数组,你必须找到无法从数组中的数字之和形成的最小正整数。

示例:

Array:[4 13 2 3 1]
result= 11 { Since 11 was smallest positive number which can not be formed from the given array elements }


我做的是:

  1. 对数组进行了排序
  2. 计算前缀总和
  3. 遍历sum数组并检查next元素是否小于1 大于和,即A [j]< =(sum + 1)。如果不是那么回答会 be sum + 1
  4. 但这是nlog(n)解决方案。

    采访者对此并不满意,并在不到O(n log n)时间内提出解决方案。

5 个答案:

答案 0 :(得分:38)

有一个漂亮的算法可以在时间O(n + Sort)中解决这个问题,其中Sort是对输入数组进行排序所需的时间。

算法背后的想法是对数组进行排序,然后提出以下问题:使用数组的前k个元素,你无法做出的最小正整数是多少?然后从左到右向前扫描数组,更新你对这个问题的答案,直到找到你不能做的最小数字。

这是它的工作原理。最初,您不能做的最小数字是1.然后,从左到右,执行以下操作:

  • 如果当前的数字大于你目前无法达到的最小数字,那么你知道你不能做的最小数字 - 这是你记录的数字,而且你已经完成了。
  • 否则,当前数字小于或等于您无法进行的最小数字。声称你可以确实这个数字。现在,您知道使用数组的前k个元素(称为candidate)无法创建的最小数字,现在正在查看值A[k]。因此,数字candidate - A[k]必须是您可以使用数组的前k个元素创建的某个数字,否则candidate - A[k]将小于您声称无法使用的最小数字candidate。数组中的前k个数字。此外,您可以在candidate + A[k]A[k]范围内输入任意数字,因为您可以从1到candidate - 1的任意数字开头,然后添加{{ 1}}到它。因此,请将candidate设置为candidate + A[k]并增加k

在伪代码中:

Sort(A)
candidate = 1
for i from 1 to length(A):
   if A[i] > candidate: return candidate
   else: candidate = candidate + A[i]
return candidate

这是[4, 13, 2, 1, 3]的测试。对数组进行排序以获得[1, 2, 3, 4, 13]。然后,将candidate设置为1.然后执行以下操作:

  • A [1] = 1,candidate = 1:
    • A [1]≤candidate,因此请设置candidate = candidate + A[1] = 2
  • A [2] = 2,candidate = 2:
    • A [2]≤candidate,因此请设置candidate = candidate + A[2] = 4
  • A [3] = 3,candidate = 4:
    • A [3]≤candidate,因此请设置candidate = candidate + A[3] = 7
  • A [4] = 4,candidate = 7:
    • A [4]≤candidate,因此请设置candidate = candidate + A[4] = 11
  • A [5] = 13,candidate = 11:
    • A [4]> candidate,请返回candidate(11)。

所以答案是11。

这里的运行时是O(n + Sort),因为在排序之外,运行时是O(n)。您可以使用heapsort清楚地在O(n log n)时间内进行排序,如果您知道数字的某些上限,则可以使用基数排序按时间排序O(n log U)(其中U是最大可能数)。如果U是固定常数(例如,10 9 ),则基数排序在时间O(n)中运行,然后整个算法也在时间O(n)中运行。

希望这有帮助!

答案 1 :(得分:8)

使用bitvectors在线性时间内完成此操作。

以空位向量b开头。然后对于数组中的每个元素k,执行以下操作:

b = b | b<< k | 2 ^(K-1)

要清楚,第i个元素设置为1表示数字i,| k将第k个元素设置为1。

完成数组处理后,b中第一个零的索引就是你的答案(从右边算起,从1开始)。

  1. B = 0
  2. 过程4:b = b | b <&lt; 4& 1000 = 1000
  3. 过程13:b = b | b <&lt; 13 | 1000000000000 = 10001000000001000
  4. 过程2:b = b | b&lt;&lt; 2& 10 = 1010101000000101010
  5. 过程3:b = b | b <&lt; 3& 100 = 1011111101000101111110
  6. 过程1:b = b | b&lt;&lt; 1 | 1 = 11111111111001111111111
  7. 第一个零:位置11。

答案 2 :(得分:7)

考虑区间[2 i .. 2 i + 1 - 1]中的所有整数。并且假设低于2 i 的所有整数可以由给定数组的数字之和形成。还假设我们已经知道C,它是低于2 i 的所有数字的总和。如果C> = 2 i + 1 -1,则该间隔中的每个数字可以表示为给定数字的总和。否则,我们可以检查区间[2 i .. C + 1]是否包含给定数组中的任何数字。如果没有这样的数字,C + 1就是我们搜索的内容。

以下是算法草图:

  1. 对于每个输入数字,确定它所属的时间间隔,并更新相应的总和:S[int_log(x)] += x
  2. 数组S的计算前缀和:foreach i: C[i] = C[i-1] + S[i]
  3. 过滤数组C以仅保留值低于下一次幂2的条目。
  4. 再次扫描输入数组并注意哪个区间[2 i .. C + 1]包含至少一个输入数字:i = int_log(x) - 1; B[i] |= (x <= C[i] + 1)
  5. 查找未在步骤3中过滤掉的第一个间隔以及未在步骤#4上设置的B[]的相应元素。
  6. 如果不明显为什么我们可以申请第3步,这是证据。选择2 i 和C之间的任意数字,然后按降序顺序从中减去2 i 以下的所有数字。最终我们得到的数字少于最后一个减去的数字或零。如果结果为零,只需将所有减去的数字加在一起,我们就可以得到所选数字的表示。如果结果为非零且小于最后一个减去的数字,则该结果也小于2 i ,因此它是“可表示的”,并且没有一个减去的数字用于其表示。当我们添加这些减去的数字时,我们有所选数字的表示。这也表明,不是逐个过滤间隔,我们可以通过直接跳转到C的int_log来一次跳过几个间隔。

    时间复杂度由函数int_log()确定,函数int_log()是整数对数或数字中最高设置位的索引。如果我们的指令集包含整数对数或其任何等价(计数前导零或具有浮点数的技巧),则复杂度为O(n)。否则,我们可以使用一些黑客来在O(日志日志U)中实现Input: [ 4 13 2 3 1] [ 1 2 3 9] [ 1 1 2 9] int_log: 2 3 1 1 0 0 1 1 3 0 0 1 3 int_log: 0 1 2 3 0 1 2 3 0 1 2 3 S: 1 5 4 13 1 5 0 9 2 2 0 9 C: 1 6 10 23 1 6 6 15 2 4 4 13 filtered(C): n n n n n n n n n n n n number in [2^i..C+1]: 2 4 - 2 - - 2 - - C+1: 11 7 5 并获得O(n * log log U)时间复杂度。 (这里U是数组中最大的数字)。

    如果步骤1(除了更新总和)也将更新给定范围内的最小值,则不再需要步骤4。我们可以将C [i]与Min [i + 1]进行比较。这意味着我们只需要单次传递输入数组。或者我们可以将此算法应用于数组而不是数字流。

    几个例子:

    {{1}}

    对于多精度输入数,此方法需要O(n * log M)时间和O(log M)空间。其中M是数组中的最大数字。只需读取所有数字就需要同一时间(在最坏的情况下,我们需要它们的每一个数字)。

    这个结果仍然可以改进为O(n * log R),其中R是该算法找到的值(实际上是它的输出敏感变体)。这种优化所需的唯一修改不是一次处理整数,而是逐位处理它们:第一次传递处理每个数字的低位(如位0..63),第二次传递 - 下一位(如64..127)等我们可以在找到结果后忽略所有高阶位。这也减少了对O(K)数的空间要求,其中K是机器字中的位数。

答案 3 :(得分:0)

如果您对数组进行排序,它将对您有用。计数排序本可以在 O(n) 中完成,但如果您认为在实际上很大的场景中,范围可能相当大。

Quicksort O(n*logn) 将为您完成这项工作:

def smallestPositiveInteger(self, array): 
    candidate = 1
    n = len(array)
    array = sorted(array)
    for i in range(0, n):
        if array[i] <= candidate:
            candidate += array[i]
        else:
            break
    return candidate

答案 4 :(得分:-1)

https://www.geeksforgeeks.org/find-smallest-value-represented-sum-subset-given-array/ 在这个问题中,因为它与询问数组已被排序的问题类似。之后,最佳的运行时复杂度是O(n)。因此@templatetypedef提供的解决方案将起作用。