数组的最小子集,其总和不小于key

时间:2012-10-23 03:35:11

标签: arrays algorithm

给定一个数组(假设非负整数),我们需要找到最小长度的子集,使得元素之和不小于K.K是另一个作为输入提供的整数。

是否有可能得到时间复杂度为O(n)[n的大哦]的解决方案?

我目前的想法是以下几点: 我们可以在O(n * log n)中对数组进行排序,然后从最大数字开始迭代排序数组并保持运行总和,直到运行总和变为> = K。

然而,这将是O(n *(log n + 1))的最坏情况运行时间。

所以如果有人能在O(n)时间分享这样做的想法,我真的很感激..

注意:在这种情况下,子阵列的元素不必是原始数组的连续序列

6 个答案:

答案 0 :(得分:5)

有一种线性时间算法可用于查找K个最大数字 - http://en.wikipedia.org/wiki/Selection_algorithm。当然,你想要的只是足够的最大数字,总计至少K.

在标准选择算法中,您采用随机旋转,然后查看每侧有多少个数字。然后你接受或拒绝一半,继续工作另一半。您只是依次查看每一半中的每个数字 - 每个支点阶段的成本是线性的,但每个阶段考虑的数据量减少得足够快,总成本仍然只是线性的。

如果您获取枢轴上方所有数字的总和,则枢轴阶段的成本仍然只是线性的。使用此功能,您可以计算出如果接受所有这些数字以及之前选择的任何数字,将为您提供至少加起来为K的数字集合。如果是这样,您可以抛弃其他数字并使用上面的数字下一次通过的枢轴。如果没有,您可以接受枢轴上方的所有数字,并使用枢轴下方的数字进行下一次传递。与选择算法一样,枢轴本身和任何关系都会为您提供一些特殊情况,并且可以及早找到准确的答案。

(所以我认为你可以在(随机)线性时间内使用选择算法的修改版本来实现这一点,在这个版本中你可以查看数据上方数字的总和,而不是在数据集上方有多少数字。< / p>

答案 1 :(得分:4)

这似乎是动态编程的一个问题。构建数组时,构建另一个数组,其中包含每个特定索引的累积总和。因此,该数组中的每个i都有1..i的总和。

现在很容易看到索引p..q的值之和为SUM(q) - SUM(p-1)SUM(0)0的特殊情况)。显然我在这里使用基于1的索引...这个操作是O(1),所以现在你只需要一个O(n)算法来找到最好的算法。

一个简单的解决方案是跟踪pq并在数组中遍历这些内容。您可以使用q进行扩展。然后,您与p签订合同并重复展开q,就像毛毛虫在您的阵列中爬行一样。

展开q

p <- 1
q <- 1

while SUM(q) - SUM(p-1) < K
    q <- q + 1
end while

现在q位于子阵列总和刚刚超过(或等于)K的位置。子阵列的长度为q - p + 1

q循环之后,您可以测试子阵列长度是否小于当前最佳长度。然后你将p提前一步(这样你就不会意外地跳过最佳解决方案)然后再去。

你真的不需要创建SUM数组......你可以随时建立子阵列总和......你需要回到使用'真实'{{1}而不是之前的那个。

p

注意:

  

j_random_hacker说:这将有助于解释为什么只检查O(n)个不同的子阵列是否可接受   算法检查,而不是所有O(n ^ 2)可能的不同子阵列

动态编程理念是:

  1. 不遵循会导致非最佳结果的解决方案路径;和
  2. 使用以前解决方案的知识来计算新的解决方案。
  3. 在这种情况下,单个解决方案候选者(一些subsum <- VAL(1) p <- 1 q <- 1 while q <= N -- Expand while q < N and subsum < K q <- q + 1 subsum <- subsum + VAL(q) end while -- Check the length against our current best len <- q - p + 1 if len < bestlen ... end if -- Contract subsum <- subsum - VAL(p) p <- p + 1 end while 使得(p,q))通过元素的求和来计算。因为这些元素是正整数,所以我们知道对于任何求解候选p <= q,求解候选(p,q)都会更大。

    所以我们知道如果(p,q+1)是最小解,那么(p,q)就不是。(p,q+1)。我们一有候选人就立即结束搜索,并测试该候选人是否比我们迄今为止看到的更好。这意味着对于每个p,我们只需要测试一个候选人。这导致pq仅增加,因此搜索是线性的。

    其他部分(使用以前的解决方案)来自于识别sum(p,q+1) = sum(p,q) + X(q+1)和类似sum(p+1,q) = sum(p,q) - X(p)。因此,我们不必在每个步骤中对pq之间的所有元素求和。每当我们推进其中一个搜索指针时,我们只需要添加或减去一个值。

    希望有所帮助。

答案 2 :(得分:3)

OP在他对评论的回答中明确指出,问题在于找到一个子集,不一定是连续的序列(术语“子阵列”无疑是差的)。然后,我相信mcdowella指出的方法是正确的,包括以下步骤:

从N个元素开始,找到MEDIAN元素(即第(N / 2)个元素想象一个你没有和不构造的排序数组)。这是通过“Median of Medians”算法实现的,证明是O(n),请参阅已经给出的wiki ref并在此重复:Selection algorithm, see section on the Median of Median algorithm

具有中值元素:线性扫描整个集合,并在“下方”和“上方”分区,同时对每个“一半”求和,计数和做任何你想要跟踪的事情。该步骤(也)是O(N)。

完成扫描后,如果“上半部分”-sum高于目标(K),则会忘记下半部分并重复上半部分的程序,其大小为(大致)N / 2。 另一方面,如果“上半部分”小于K,则将上半部分加到最终结果中,从K中减去其总和,并用下半部分重复该过程。

总而言之,你处理大小为N,N / 2,N / 4,N / 8等的集合,每个集合在O(M)中相对于它们各自的大小M,因此整体的东西在N中也是线性的,因为N + N / 2 + N / 4 + N / 8 ...保持在2N以下。

答案 3 :(得分:1)

这是一个应该足够快的解决方案。 我猜它几乎是线性的。

def solve(A, k):
    assert sum(A) >= k
    max_ = max(A)
    min_ = min(A)
    n = len(A)
    if sum(A) - min_ < k:
        return A
    bucket_size = (max_ - min_)/n + 1
    buckets = []
    for i in range(n):
        buckets.append([])
    for item in A:
        bucket = (item - min_)/bucket_size
        buckets[bucket].append(item)

    solution = []

    while True:
        bucket = buckets.pop() #the last bucket
        sum_ = sum(bucket)
        if sum_ >= k:
            #don't need everything from this bucket
            return solution + solve(bucket, k)
        else:
            k -= sum_
            solution += bucket

print solve([5,2,7,52,30,12,18], 100)
"[52, 30, 18]"

答案 4 :(得分:0)

我认为“子数组”术语意味着数组的连续部分(like here,另一个问题是示例)。

所以有简单的O(n)算法来找到最小长度的子阵列:

将两个索引(左,右)设置为第一个元素并将它们移动到数组的末尾。检查这些索引之间的总和,前进右指针,如果总和太小(或指针相等),如果总和很大则向前推进

答案 5 :(得分:0)

子阵列必须与数组的定义相邻。

使用2个指针(开始,结束)。将它们初始化为数组的开头。 跟踪(开始,结束)之间的当前总和,并逐个向右移动。每次移动结束指针时,sum = sum + array [end]。

当sum&gt; = target时,开始向右移动并保持跟踪和为sum = sum - array [start]。

在向右移动时,继续检查总和仍然不低于目标。我们还需要通过length = end - start + 1以及minLength = min(minLength,length)来跟踪长度。

现在,当我们将两个指针尽可能地移动时,我们只需要返回minLength。

一般的想法是首先找到满足条件(sum&gt; = target)的“窗口”,然后一次将窗口向右滑动一个元素,并在每次移动窗口时保持窗口大小最小。