无法从集合中获得的最小金额

时间:2014-01-11 09:55:02

标签: algorithm

给定一组正整数,其元素不需要是不同的,我需要找到不能从给定集合的任何子集中获得的最小非负和。

示例:if S = {1, 1, 3, 7},我们可以0(S' = {})1(S' = {1})2(S' = {1, 1})3(S' = {3})4(S' = {1, 3})5(S' = {1, 1, 3}),但我们无法获取6

现在我们获得一个array A,由N个正整数组成。它们是M个查询,每个查询包含两个整数LiRi描述我的查询:我们需要找到不能从数组elements ={A[Li], A[Li+1], ..., A[Ri-1], A[Ri]}获得的这个Sum。

我知道通过蛮力方法在O(2 ^ n)中完成它。但鉴于1 ≤ N, M ≤ 100,000,这是不可能的。 他们采取任何有效的方法都是如此。

2 个答案:

答案 0 :(得分:12)

概念

假设我们有一个bool数组,表示到目前为止尚未找到的数字(通过求和)。

numbers array

对于我们在n有序(增加值)子集中遇到的每个数字S,我们执行以下操作:

  1. 对于True中位置i的每个现有numbers值,我们将numbers[i + n]设置为True
  2. 我们将numbers[n]设置为True
  3. 使用这种筛子,我们会将所有找到的数字标记为True,并在算法结束时迭代数组会找到最小的无法获得的总和。


    细化

    显然,我们不能有这样的解决方案,因为数组必须是无限的才能适用于所有数字集。

    通过做一些观察可以改进这个概念。输入1, 1, 3时,数组变为(按顺序):

    enter image description here (数字代表true个值) enter image description here numbers array 2

    可以做一个重要的观察:

    • (3)对于每个下一个号码,如果已经找到以前的号码,它将被添加到所有这些号码中。这意味着如果在数字之前没有间隙,则在处理该数字后将没有间隙

    对于7的下一个输入,我们可以声明:

    • (4)由于输入集是有序的,因此不会有小于7
    • 的数字
    • (5)如果没有小于7的数字,则无法获得6

    enter image description here

    我们可以得出结论:

    • (6)第一个差距代表最低无法获得的数字

    算法

    由于(3)和(6),我们实际上不需要numbers数组,我们只需要一个值,max表示到目前为止找到的最大数量

    这样,如果下一个号码n大于max + 1,则会产生差距,max + 1是最低无法获得的号码。

    否则,max变为max + n。如果我们浏览了整个S,则结果为max + 1

    实际代码(C#,很容易转换为C):

    static int Calculate(int[] S)
    {
        int max = 0;
        for (int i = 0; i < S.Length; i++)
        {
            if (S[i] <= max + 1)
                max = max + S[i];
            else
                return max + 1;
        }
        return max + 1;
    }
    

    应该运行得非常快,因为它显然是线性时间(O(n))。由于应该对函数的输入进行排序,因此使用quicksort将变为O(nlogn)。我设法在不到5分钟的时间内在8个内核上获得结果M = N = 100000

    如果数字上限为10 ^ 9,则可以使用基数排序来近似排序的O(n)时间,但由于需要大量的排序,这仍然会超过2秒。

    但是 ,我们可以使用统计概率1为randomed 来消除排序前的子集。在开始时,检查S中是否存在1,如果不存在,则每个查询的结果为1,因为无法获取。

    从统计数据来看,如果我们从10 ^ 9个数字中随机抽取10 ^ 5次,我们有99.9%的机会没有得到单个1。

    在每次排序之前,检查该子集是否包含1,如果不是,则其结果为1。

    通过此修改,代码在我的机器上运行2毫秒。这是http://pastebin.com/rF6VddTx

    上的代码

答案 1 :(得分:1)

这是subset-sum problem 的变体,NP-Complete,但您可以在此处采用伪多项式Dynamic Programming解决方案,基于此递归公式:

f(S,i) = f(S-arr[i],i-1) OR f(S,i-1)
f(-n,i) = false
f(_,-n) = false
f(0,i) = true

递归公式基本上是一个详尽的搜索,如果您可以使用元素i或没有元素i来获取它,则可以实现每个总和。

动态编程是通过构建SUM+1 x n+1表来实现的(其中SUM是所有元素的总和,n是元素的数量),并自下而上构建它

类似的东西:

table <- SUM+1 x n+1 table
//init:
for each i from 0 to SUM+1:
    table[0][i] = true
for each j from 1 to n:
    table[j][0] = false
//fill the table:
for each i from 1 to SUM+1:
    for each j from 1 to n+1:
         if i < arr[j]:
              table[i][j] = table[i][j-1]
         else:
              table[i][j] = table[i-arr[j]][j-1] OR table[i][j-1]

获得该表后,您需要最小的i,以便所有jtable[i][j] = false

解决方案的复杂性为O(n*SUM),其中SUM是所有元素的总和,但请注意,在找到所需数字后,实际上可以修剪算法,而无需继续下一行,这是解决方案所不需要的。