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

Posted

技术标签:

【中文标题】在数组中的每个项目之前有多少个连续元素较小【英文标题】:how many consecutive elements are smaller before each item in the array 【发布时间】:2017-01-11 16:45:34 【问题描述】:

给定一个包含N < 10 000 元素的数组,对于数组中的每个位置i,找出(以最有效的方式)从它左边开始有多少个连续元素(即从位置i-10)小于或等于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) 的看法。有人会假设:

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;
   

显然,如果我找到一个大于下一个的元素,结果显然是 0(左边没有比它小的数字)。 但是前面的结果告诉我什么让我可以使用动态编程?我找不到那个案例的任何复发。此外,该公式必须在O(1) 中获得,整个解决方案为O(n),对吗?想过使用哈希集,但无法弄清楚。考虑过使用 kadane 算法的一些修改版本,但没有成功。

我很想了解O(n) 解决方案。我整天都在考虑O(n) 解决方案,但我真的被困住了。

我不是本地人,因此非常感谢任何有助于使这个问题更好/更易于理解的帮助。

【问题讨论】:

【参考方案1】:

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

这个想法如下:对于每个i(从左i = 0到右i = n - 1),我们维护所有索引j的集合,这样c[j] &gt; c[k]对于所有j &lt; k &lt; i。这个集合可以存储在堆栈中,最低的值在顶部。当你读到c[i] 时,你会弹出元素,直到你得到一个索引j 这样c[j] &gt; c[i]。这是想要的索引。然后你可以将i 压入堆栈。

示例:s 是堆栈。这里ans[i] 将是maxj &lt;= i | c[j] &gt; c[i]。如果前一组为空,ans[i] 将为 -1。

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]     

【讨论】:

很抱歉,我很难理解答案。你能举一个简短的例子吗?通常我更容易从具体到抽象。 @RustinAlexandru:如果还不清楚,请告诉我。实际上,仅仅提供一个实现会更容易,但我让你这样做。 ;) 谢谢,这太完美了。实现不会有问题。这是我对算法的误解。 i = 2: - s = [0, 1]: c[2] &gt;= c[1] -&gt; pop s = [0]: c[2] &gt;= c[0] -&gt; pop s = []: ans[2] = -1 - push i: s = [2] 不应该 ans[2] = 2 因为 4 2 个连续位置元素小于 c[2] ? 我认为可能存在误解,由于我的表述,连续元素意味着连续位置上的元素,而不是像 1 1+1 1+1+1 (1 2 3..)。跨度> @RustinAlexandru:ans[i] 并不是您问题的确切答案,而是低于i(我们称之为j)的最大索引,例如c[j] &gt; c[i]。但是,ans[i] 与您期望的结果之间存在简单的数学关系。你能找到吗?【参考方案2】:

(编辑/版主请阅读我对问题所选答案的最后评论,然后再删除此评论。)

堆栈操作

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

PUSH(S, x) 将对象 x 压入栈 S。

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

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

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

MULTIPOP(S, k) 在一堆 s 对象上的运行时间是多少?实际运行时间与实际执行的 POP 操作的数量成线性关系,因此只需根据 PUSH 和 POP 的抽象成本 1 来分析 MULTIPOP。 while 循环的迭代次数是从堆栈中弹出的对象的数量 min(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)。

【讨论】:

【参考方案3】:

因此,很明显,在发布原始问题 5 年后,我在准备我的算法课时发现了这个问题。直到今天,这是我在互联网上找到的唯一解决方案。我花了很多时间来编写解决方案,所以我把它贴在这里。以后可能有人需要它。我的代码是用 Python3 编写的,并已尽我所能进行了重构。

from collections import  deque

def less_then_count(arr):
    stack = deque()
    ans = [0] * len(arr)    

    for i in range(len(arr)):
        while len(stack)>0 and arr[i] >= arr[stack[-1]]:
            stack.pop()
        ans[i] = i
        if len(stack) > 0:
            ans[i] -= stack[-1]+1
        stack.append(i)

    return ans


print(*less_then_count([1,2,4,2,5]))
print(*less_then_count([4, 3, 6, 1, 1, 2, 8, 5, 9]))

【讨论】:

以上是关于在数组中的每个项目之前有多少个连续元素较小的主要内容,如果未能解决你的问题,请参考以下文章

数组最多能有多少个元素

给定一个数组,找出每个元素的最后一个较小的元素

数组的存储方式是啥?

最多有多少个连续子数组。 n 个唯一编号

给定一个唯一正整数数组,为每个元素找到最近的较小元素,但距离至少为 k

Lua:“拖动”数组中的元素序列