给定一个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)
解决方案而且我真的被卡住了。
我不是原生的,所以任何帮助使这个问题更好/更容易理解的人都会非常感激。
答案 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)。