给定一个数组(假设非负整数),我们需要找到最小长度的子集,使得元素之和不小于K.K是另一个作为输入提供的整数。
是否有可能得到时间复杂度为O(n)[n的大哦]的解决方案?
我目前的想法是以下几点: 我们可以在O(n * log n)中对数组进行排序,然后从最大数字开始迭代排序数组并保持运行总和,直到运行总和变为> = K。
然而,这将是O(n *(log n + 1))的最坏情况运行时间。
所以如果有人能在O(n)时间分享这样做的想法,我真的很感激..
注意:在这种情况下,子阵列的元素不必是原始数组的连续序列
答案 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)算法来找到最好的算法。
一个简单的解决方案是跟踪p
和q
并在数组中遍历这些内容。您可以使用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)可能的不同子阵列
动态编程理念是:
在这种情况下,单个解决方案候选者(一些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
,我们只需要测试一个候选人。这导致p
和q
仅增加,因此搜索是线性的。
其他部分(使用以前的解决方案)来自于识别sum(p,q+1) = sum(p,q) + X(q+1)
和类似sum(p+1,q) = sum(p,q) - X(p)
。因此,我们不必在每个步骤中对p
和q
之间的所有元素求和。每当我们推进其中一个搜索指针时,我们只需要添加或减去一个值。
希望有所帮助。
答案 2 :(得分:3)
从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)的“窗口”,然后一次将窗口向右滑动一个元素,并在每次移动窗口时保持窗口大小最小。