![](/img/trans.png)
[英]Understanding Example 12 All Permutations of a string from Big O notation - Cracking the Coding Interview
[英]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個可能的字符串:
為了更好地了解它的來源,請注意每次在字符串末尾添加一個新字母時,您都可以選擇該字母的內容,因此可能的字符串數量是
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項的指數較低。
總結一下:
對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
如果k
為4
,這種模式將繼續,那么它將是:
(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)*k
或O(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.