簡體   English   中英

為什么這是這個 For 循環 O(N) 的時間復雜度?

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

有沒有辦法從直覺或通過證明來理解為什么以下的時間復雜度是 O(N)?

它的作用基本上是給定 integer array的輸入,我們正在創建相同長度的firstSmallLeft數組,其中firstSmallLeft[i] = 第一個索引 where array[index] < array[i] when doing back scan (即,對於索引 i,它從 i-1,...,j 開始掃描,直到找到第一個較小的元素,使得 array[j] < array[i])

例如,如果輸入 = [3,2,5,6,4,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;
}

以下是對我之前的(錯誤的)答案的完全重寫。

一個重要的見解是firstSmallLeft中收到值的最后一個條目(在索引i-1處)表示堆棧的頂部,它被實現為鏈表。 firstSmallLeft中的條目表示輸入值(因為它出現的索引是輸入array中的索引)和鏈表中的鏈接(因為它的值是firstSmallLeft中的索引)。 堆棧中的底部元素具有空鏈接 (-1)。

通常,並非firstSmallLeft中的所有元素都在表示的堆棧中,因為跳過了一些索引。 一個例子:

假設棧頂的索引為 100(當i為 101 時),它的值為 40, firstSmallLeft[40]為 -1,那么這意味着我們有一個只有兩個元素的棧( array[100]array[40] ),並且firstSmallLeft中的其他索引都不包含在當前堆棧中。 每一個曾經在堆棧上,但后來被彈出堆棧。

每次cur = firstSmallLeft[cur]; 執行(在內部循環中),我們實際上彈出堆棧的一個元素(從cur開始的鏈表短一個元素)。

當我們執行firstSmallLeft[i] = cur時,我們將array[i]壓入堆棧,引用我們想要保留的堆棧部分。

現在這個“虛擬”堆棧被識別出來了,我們看到以下內容:

一個值在堆棧上只被壓入一次,並且只能從堆棧中彈出一次,之后再也不會訪問它。

再次評估while表達式以發現我們需要退出循環的開銷與外部循環迭代的次數一樣多(即n-1次)

因此算法是 O(n)。

您可以將此算法想象為創建元素鏈的集合。 對於數組中的每個元素,向后跟隨鏈將使您的值越來越小,直到您離開數組。

要為數組的下一個元素構建鏈,我們首先查看前面元素的鏈。 然后我們將沿着該鏈執行一些步驟,然后通過將鏈接添加回我們停止的位置來形成新元素的鏈。

這里的關鍵直覺是鏈的長度迅速下降並緩慢增長 更具體地說,令 C 為從數組的當前元素開始的鏈的長度。 我們從 C = 0 開始。在算法的每一步,我們將 C 降低一定數量,然后將 C 最多增加 1。 這意味着在算法的所有 n 次迭代中,C 最多增加 n 次。 因此,由於 C 永遠不會是負數,因此 C 最多可以減少 n 倍。 因此,在算法的所有迭代中,內部 while 循環減少 C 所花費的總工作量為 O(n)。

(如果您熟悉攤銷分析和潛在函數,也可以將其視為設置 Φ = C。)

類似的論點出現在許多其他算法中,例如使用堆棧構建笛卡爾樹以及在 Knuth-Morris-Pratt 字符串匹配算法中構建故障表。

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM