简体   繁体   English

为什么这是这个 For 循环 O(N) 的时间复杂度?

[英]Why is this the Time Complexity of this For Loop O(N)?

Is there a way to understand why the time complexity of the following is O(N), either(or both) from intuition or by proof?有没有办法从直觉或通过证明来理解为什么以下的时间复杂度是 O(N)?

What it does is basically that given the input of an integer array , we are creating the firstSmallLeft array of the same length where firstSmallLeft[i] = the first index where array[index] < array[i] when doing the back scan .它的作用基本上是给定 integer array的输入,我们正在创建相同长度的firstSmallLeft数组,其中firstSmallLeft[i] = 第一个索引 where array[index] < array[i] when doing back scan (ie, for index i, it scans from i-1, ... , j until it finds the first smaller element such that array[j] < array[i]) (即,对于索引 i,它从 i-1,...,j 开始扫描,直到找到第一个较小的元素,使得 array[j] < array[i])

For Example, if input = [3,2,5,6,4,1] .例如,如果输入 = [3,2,5,6,4,1]
firstSmallLeft would be [-1,-1,1,2,1,-1] firstSmallLeft 将是[-1,-1,1,2,1,-1]

//input int[] array
int[] firstSmallLeft = new int[array.length];
firstSmallLeft[0] = -1;
for(int i = 1; i < array.length; i ++){
    int cur = i -1;
    while (cur >=0 && array[cur] >= array[i]){
        cur = firstSmallLeft[cur];
    }

    firstSmallLeft[i] = cur;
}

Here follows a complete rewrite of my previous (wrong.) answer.以下是对我之前的(错误的)答案的完全重写。

An important insight is that the last entry in firstSmallLeft that received a value (at index i-1 ) represents the top of a stack , which is implemented as a linked list.一个重要的见解是firstSmallLeft中收到值的最后一个条目(在索引i-1处)表示堆栈的顶部,它被实现为链表。 An entry in firstSmallLeft represents an input value (as the index where it occurs is the index in the input array ) and a link in the linked list (as its value is an index in firstSmallLeft ). firstSmallLeft中的条目表示输入值(因为它出现的索引是输入array中的索引)和链表中的链接(因为它的值是firstSmallLeft中的索引)。 The bottom element in the stack has a null-link (-1).堆栈中的底部元素具有空链接 (-1)。

In general not all elements in firstSmallLeft are in the represented stack, since some indices are skipped over.通常,并非firstSmallLeft中的所有元素都在表示的堆栈中,因为跳过了一些索引。 An example:一个例子:

Let's say that the top of the stack is at index 100 (when i is 101), and it has as value 40, and firstSmallLeft[40] is -1, then that means we have a stack with just two elements ( array[100] and array[40] ), and none of the other indices in firstSmallLeft are included in the current stack.假设栈顶的索引为 100(当i为 101 时),它的值为 40, firstSmallLeft[40]为 -1,那么这意味着我们有一个只有两个元素的栈( array[100]array[40] ),并且firstSmallLeft中的其他索引都不包含在当前堆栈中。 Each one of those once was on the stack, but have since been popped of the stack.每一个曾经在堆栈上,但后来被弹出堆栈。

Every time cur = firstSmallLeft[cur];每次cur = firstSmallLeft[cur]; is executed (in the inner loop), we actually pop an element of the stack (the linked list that starts at cur is one element shorter).执行(在内部循环中),我们实际上弹出堆栈的一个元素(从cur开始的链表短一个元素)。

And when we do firstSmallLeft[i] = cur we push array[i] on the stack, making a reference to the part of the stack we wanted to keep.当我们执行firstSmallLeft[i] = cur时,我们将array[i]压入堆栈,引用我们想要保留的堆栈部分。

Now this "virtual" stack is identified, we see the following:现在这个“虚拟”堆栈被识别出来了,我们看到以下内容:

A value is pushed exactly once on the stack, and can only be popped once from the stack, after which it is never visited again.一个值在堆栈上只被压入一次,并且只能从堆栈中弹出一次,之后再也不会访问它。

The overhead of evaluating the while expression once more to find that we need to exit the loop, occurs as many times as the outer loop iterates (ie n-1 times)再次评估while表达式以发现我们需要退出循环的开销与外部循环迭代的次数一样多(即n-1次)

Therefore the algorithm is O(n).因此算法是 O(n)。

You can imagine this algorithm as creating a collection of chains of elements.您可以将此算法想象为创建元素链的集合。 For each element in the array, following the chain backwards will take you to smaller and smaller values until you walk off the array.对于数组中的每个元素,向后跟随链将使您的值越来越小,直到您离开数组。

To build the chain for the next element of the array, we start by looking at the chain of the element before us.要为数组的下一个元素构建链,我们首先查看前面元素的链。 We'll then follow that chain for some number of steps, then form the chain for the new element by adding a link back to the place we stopped.然后我们将沿着该链执行一些步骤,然后通过将链接添加回我们停止的位置来形成新元素的链。

The key intuition here is that the length of the chains drops quickly and grows slowly .这里的关键直觉是链的长度迅速下降并缓慢增长 More concretely, let C be the length of the chain that starts from the current element of the array.更具体地说,令 C 为从数组的当前元素开始的链的长度。 We begin with C = 0. At each step in the algorithm, we drop C by some amount, then increment C by at most one.我们从 C = 0 开始。在算法的每一步,我们将 C 降低一定数量,然后将 C 最多增加 1。 This means that across all n iterations of the algorithm, C increases at most n times.这意味着在算法的所有 n 次迭代中,C 最多增加 n 次。 Therefore, since C is never negative, C can decrease at most n times.因此,由于 C 永远不会是负数,因此 C 最多可以减少 n 倍。 And therefore the total work spent in the inner while loop decreasing C, across all iterations of the algorithm, is O(n).因此,在算法的所有迭代中,内部 while 循环减少 C 所花费的总工作量为 O(n)。

(If you're familiar with amortized analyses and potential functions, you can also think of this as setting Φ = C.) (如果您熟悉摊销分析和潜在函数,也可以将其视为设置 Φ = C。)

This same sort of argument arises in a number of other algorithms, such as building a Cartesian tree with a stack and constructing the failure table in the Knuth-Morris-Pratt string matching algorithm.类似的论点出现在许多其他算法中,例如使用堆栈构建笛卡尔树以及在 Knuth-Morris-Pratt 字符串匹配算法中构建故障表。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM