在数组中的每个项目之前有多少个连续元素

时间:2016-09-03 21:30:28

标签: arrays algorithm dynamic-programming

给定一个N < 10 000元素数组,对于数组中的每个位置i,找到(以最有效的方式)从左边开始的多少个连续元素(即从位置{{1}开始} i-1}小于或等于0

这是一个例子:

array[i]

我认为这是一个动态编程问题,因为它在问题中说“最有效的方式”,在解决方案中它说有一个Array: 4 3 6 1 1 2 8 5 9 Res: 0 0 2 0 1 2 6 0 8 ( pos 0 (element 4) -> 0 consecutive elements before it, pos 1 (element 3) -> 0 consecutive elements before it smaller than 3 (4>3) pos 2 (element 6) -> 2 consecutive elements before it smaller than 6 (4,3) and so on.. ) 解决方案。

O(n)解决方案很简单,两个循环,计算元素。

以下是我对O(n^2)的看法。人们会假设:

0(n)

显然,如果我发现一个元素大于下一个元素,结果显然为0(没有小于左边的数字)。 但是之前的结果告诉我什么,所以我可以使用动态编程?对于那种情况,我找不到任何重复。此外,该公式必须在for (int i = 1; i < array.Length; i++) { if (array[i-1] > array[i]) { c [i] = 0; } else { c [i] = c [i - 1] + MAGIC_FORMULA; } } 中获得,整个解决方案为O(1),对吧?考虑使用hashset,但无法弄明白。想过使用kadane算法的一些修改版本,但没有运气。

我很想理解O(n)解决方案。我整天都在考虑O(n)解决方案而且我真的被卡住了。

我不是原生的,所以任何帮助使这个问题更好/更容易理解的人都会非常感激。

2 个答案:

答案 0 :(得分:5)

有线性解决方案,但它不使用动态编程,而是使用简单的循环和堆栈。首先,您可以进行以下观察:计算“小于或等于c[i]的连续元素的数量”几乎与查找“j <= i更大的索引c[j] > c[i]”相同。

这个想法如下:对于每个i(从左i = 0扫到右i = n - 1),我们维护所有索引j的集合,使c[j] > c[k] j < k < i 1}}适用于所有c[i]。该集可以存储在堆栈中,最低值位于顶部。当您阅读j时,您会弹出元素,直到获得c[j] > c[i]索引i。这是通缉索引。然后你可以在堆栈上推送s

示例:ans[i]是堆栈。此处max{j <= i | c[j] > c[i]}将为ans[i]。如果前一个集合为空,则i 0 1 2 3 4 5 6 7 8 c[i] 4 3 6 1 1 2 8 5 9 ------------------------ i = 0: - s = []: ans[0] = -1 - push i: s = [0] i = 1: - s = [0]: c[1] < c[0] -> ans[1] = 1 - push i: s = [0, 1] i = 2: - s = [0, 1]: c[2] >= c[1] -> pop s = [0]: c[2] >= c[0] -> pop s = []: ans[2] = -1 - push i: s = [2] i = 3: - s = [2]: c[3] < c[2] -> ans[3] = 2 - push i: s = [2, 3] i = 4: - s = [2, 3]: c[4] >= c[3] -> pop s = [2]: c[4] < c[2] -> ans[4] = 2 - push i: s = [2, 4] i = 5 - s = [2, 4]: c[5] >= c[3] -> pop s = [2]: c[5] < c[2] -> ans[5] = 2 - push i: s = [2, 5] i = 6 - s = [2, 5]: c[6] >= c[5] -> pop s = [2]: c[6] >= c[2] -> pop s = [] -> ans[6] = -1 - push i: s = [6] i = 7 - s = [6]: c[7] < c[6] -> ans[7] = 6 - push i: s = [6, 7] i = 8 - s = [6, 7]: c[8] >= c[7] -> pop s = [6]: c[8] >= c[6] -> pop s = [] -> ans[8] = -1 - push i: s = [8] 将为-1。

new_module

答案 1 :(得分:0)

(编辑/主持人请在删除此问题之前阅读我对该问题的选定答案的最后评论。)

堆叠操作

在我们的第一个聚合分析示例中,我们分析了使用新操作增强的堆栈。第10.1节介绍了两个基本的堆栈操作,每个操作都需要O(1)时间:

PUSH(S,x)将对象x推到堆栈S上。

POP(S)弹出堆栈S的顶部并返回弹出的对象。

由于每个操作都在O(1)时间运行,让我们考虑每个操作的成本为1.因此,n个PUSH和POP操作序列的总成本为n,n的实际运行时间为n因此,操作是(n)。

现在我们添加堆栈操作MULTIPOP(S,k),它删除堆栈S的k个顶层对象,或者如果它包含少于k个对象则弹出整个堆栈。在下面的伪代码中,如果堆栈上当前没有对象,则操作STACK-EMPTY返回TRUE,否则返回FALSE。

MULTIPOP(S,k)在一堆s对象上的运行时间是多少?实际运行时间与实际执行的POP操作的数量是线性关系,因此就PUSH和POP的每个抽象成本1分析MULTIPOP就足够了。 while循环的迭代次数是从堆栈弹出的对象的最小数量(s,k)。对于循环的每次迭代,在第2行中对POP进行一次调用。因此,MULTIPOP的总成本为min(s,k),实际运行时间是此成本的线性函数。

让我们分析一个在最初空堆栈中的n PUSH,POP和MULTIPOP操作的序列。序列中MULTIPOP操作的最坏情况成本是O(n),因为堆栈大小最多为n。因此,任何堆栈操作的最坏情况时间是O(n),因此n个操作的序列花费O(n2),因为我们可能具有O(n)MULTIPOP操作,每个操作花费O(n)。虽然这种分析是正确的,但通过单独考虑每项操作的最坏情况成本得到的O(n2)结果并不严格。

使用聚合分析,我们可以获得更好的上限,该上限考虑n个操作的整个序列。实际上,尽管单个MULTIPOP操作可能很昂贵,但是在最初空的堆栈上的任何n PUSH,POP和MULTIPOP操作序列的成本最多为O(n)。为什么?每次推送时,每个对象最多可以弹出一次。因此,可以在非空堆栈上调用POP的次数,包括MULTIPOP内的调用,最多是PUSH操作的次数,最多为n。对于n的任何值,n PUSH,POP和MULTIPOP操作的任何序列总共花费O(n)时间。操作的平均成本是O(n)/ n = O(1)。在汇总分析中,我们将每项业务的摊余成本分配为平均成本。因此,在此示例中,所有三个堆栈操作都具有摊销成本O(1)。