簡體   English   中英

為什么這個例子的時間復雜性來自“破解編碼面試”O(k c ^ k)?

[英]Why is the time complexity of this example from “Cracking the Coding Interview” O(k c^k)?

這個問題來自Cracking the Coding Interview第6版,問題V1.11。

以下代碼打印長度為k的所有字符串,其中字符按排序順序排列。 它通過生成長度為k的所有字符串然后檢查每個字符串是否已排序來完成此操作。 什么是運行時?

package QVI_11_Print_Sorted_Strings;


public class Question {

    public static int numChars = 26;

    public static void printSortedStrings(int remaining) {
        printSortedStrings(remaining, "");
    }

    public static void printSortedStrings(int remaining, String prefix) {
        if (remaining == 0) {
            if (isInOrder(prefix)) {
                System.out.println(prefix);
            }
        } else {
            for (int i = 0; i < numChars; i++) {
                char c = ithLetter(i);
                printSortedStrings(remaining - 1, prefix + c);
            }
        }
    }

    public static boolean isInOrder(String s) {
        for (int i = 1; i < s.length(); i++) {
            int prev = ithLetter(s.charAt(i - 1));
            int curr = ithLetter(s.charAt(i));
            if (prev > curr) {
                return false;
            }
        }
        return true;
    }

    public static char ithLetter(int i) {
        return (char) (((int) 'a') + i);
    }

    public static void main(String[] args) {
        printSortedStrings(5);
    }

}

答案是O(kc ^ k),其中k是字符串的長度,c是字母表中的字符數。 生成每個字符串需要O(c ^ k)時間。 然后,我們需要檢查每個是否排序,這需要O(k)時間。

現在,我明白O(k)來自哪里,但我不知道O(c ^ k)是如何產生的。

上述算法通過使用一組c個字符選擇遞歸地生成長度為k的所有可能的字符串來工作。 你可以從c個字母選擇中得到的長度為k的可能字符串數等於c k 例如,如果我有兩個字母可以從(a和b)中選擇並且我有長度為3的字符串,那么我可以制作2 3 = 8個可能的字符串:

  • AAA
  • AAB
  • ABA
  • ABB
  • BAB
  • BBA
  • BBB

為了更好地了解它的來源,請注意每次在字符串末尾添加一個新字母時,您都可以選擇該字母的內容,因此可能的字符串數量是

c·c·...·c(k次)=

c k

這意味着上述代碼通過生成每個字符串來工作,必須至少做Ω(c k )工作,因為這是要檢查的最小字符串數。

那么它對每個字符串做了多少工作呢? 事情變得棘手。 通過不斷地從可能的字符列表中添加新字符,這些字符串一次構建一個字符。 在Java中,附加到字符串會產生字符串的完整副本,因此附加第一個字符的成本(大致)為1,第二個字符是(大致)2,然后是3,然后是4等。這意味着成本建立一個長度為k的完整字符串

1 + 2 + 3 + ... + k

=Θ(k 2

因此,實際上這里的運行時似乎是O(c k k 2 )而不是O(kc k ),因為構建所有這些字符串的成本相當快。

但是,這不是一個緊張的約束。 例如,用於形成字符串aaa一些工作也用於形成字符串aab ,因為兩個字符串都是通過以aa開頭並連接其中的另一個字符來形成的。

為了獲得更好的分析,我們可以總結在樹中每個級別執行連接的總工作量。 樹的第0級具有大小為0的單個字符串,因此不會進行連接。 樹的第一級具有大小為1的c字符串,需要c連接才能進行連接。 樹的第二層有2個大小為2的字符串,需要2c 2才能形成。 三者中的第三級有3個大小為3的字符串,需要3c 3才能形成。 更一般地,級別i要求ic i工作形成。 這意味着我們要確定

0c 0 + 1c 1 + 2c 2 + ... + kc k

該求和求出Θ(kc k ),其中k項的指數較低。

總結一下:

  • c k術語來自需要檢查的字符串數。
  • k項來自每串完成的工作。
  • 仔細分析表明,生成所有這些字符串的時間不會影響O(kc k )的整體運行時間。

printSortedStrings的遞歸調用形成遞歸樹。 因為節點的總數大約是最低級別的節點數量的常數因子,並且上級別比最低級別的工作量更多,所以只有最低級別的成本是顯着的。

例如, c為2, k為3:

查看樹的第一級,這會產生:

2 (或2^1 )個字符串, "a""b"

第二級產生:

4 (或2^2 )個字符串, "aa""ab""ba""bb"

第三級產生:

8 (或2^3 )的字符串, "aaa" "aab" "aba" "abb""baa" "bab" "bba" "bbb"

按順序生成下一個字符串的成本是線性的。 舊字符串中的字符加上新字符將復制到新字符串中。

這取決於字符串的長度,因此對於第一級,成本為1,第二級成本為2,第三級成本為3.乘以每個級別的項目數:

(2^1)*1 + (2^2)*2 + (2^3)*3 = 34

如果k4 ,這種模式將繼續,那么它將是:

(2^1)*1 + (2^2)*2 + (2^3)*3 + (2^4)*4 = 98

這個關於這樣的總和的事情是,最后一個術語比前面的所有術語組合得更多。 所以只有最后一個詞是重要的:

(2^1)*1 + (2^2)*2 + (2^3)*3 < (2^4)*4

因為:

(2^1)*1 + (2^2)*2 + (2^3)*3
< (2^1)*3 + (2^2)*3 + (2^3)*3
= (2^1 + 2^2 + 2^3) * 3
= (2^4 - 2) * 3
< (2^4 - 2) * 4
< (2^4) * 4

因此,總和小於2*(2^4)*4 ,或2*(c^k)*kO(kc^k)

在遞歸結束時,有更多的線性時間工作。 存在具有k工作的c^k節點,給出另一個O(kc^k)成本。 所以總成本仍然是O(kc^k)

另一個問題是for循環也需要每個字符串的線性時間,但由於上述類似的原因,這總計為O(kc^k)

我不認為他們在書中占了k ^ 2,因為代碼不是用實際書中的Java編寫的。 因此可以安全地假設字符串連接是O(1)。 此外,書中的答案解釋並不是最好的。 生成每個字符串只需要O(k)時間,但是有O(c ^ k)個可能的字符串。 並且需要O(k)來驗證結果,因此它應該是O(k ^ 2 c ^ k)而不是O(kc ^ k)(具有O(1)字符串連接時間)。 但是,您應該與您的面試官討論,只要詢問打印,連接等是否需要O(1)次或更長時間。

暫無
暫無

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

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