[英]Understanding Example 12 All Permutations of a string from Big O notation - Cracking the Coding Interview
我一直不明白作者是如何得到以下過程的O(n^2 * n!)
復雜度的,該過程生成字符串的所有排列。
void permutation(String str){
permutation(str,"");
}
void permutation(String str, String prefix){
if(str.length()==0){
System.out.println(prefix);
} else{
for(int i=0;i<str.length();i++){
String rem=str.substring(0,i)+str.substring(i+1);
permutation(rem,prefix+str.charAt(i));
}
}
}
由於 else 路徑成本,該方法的復雜度為O(n^2 *n!)
:
首先注意每次調用String rem=str.substring(0,i)+str.substring(i+1);
是O(n)
,在else
路徑中,我們將它計算n
次,並調用具有復雜度T(n-1)
permutation
。
計算這個的復雜度等價於求解: T(n) = n[n+T(n-1)]
; n
次(for 循環)一個(n+T(n-1))
解決這個重復問題並不是那么容易,如果我沒記錯的話,它應該歸結為解決:
但讓我們嘗試近似。 每個排列(基本情況)代表遞歸樹中的一個節點。 這棵樹有n!
葉子。 每個葉子都有一條到長度為n
的根的路徑。 所以可以安全地假設不超過n*n!
樹中的節點。
這是對permutation
調用次數的上限。 由於每個調用都花費n
,因此復雜度的總體上限為O(n^2*n!)
希望這可以幫助。
我遲到了,但我仍然會發布我的答案。
我是這樣向自己解釋的。 采用簡化的方法,讓我們忽略函數的所有步驟: permutation(String str, String prefix)
除了遞歸步驟。
記住上面的內容,函數的時間復雜度可以用遞推關系表示: T(N) = N*T(N-1)
,其中N
是輸入字符串的長度。 展開后, T(N) = N*(N-1)*(N-2)*...3*2*1*T(0)
。 --- (*)
現在, T(0) = O(N)
因為在基本情況下我們打印前綴字符串,打印長度為 N 的字符串是一個 O(N) 操作。
以封閉形式表示上面的 (*): N*N!
--- (1)
現在考慮permutation
函數的以下行: String rem = str.substring(0, i) + str.substring(i + 1);
這又是一次O(N)
操作,並且對N!
遞歸調用。 因此考慮以上並與上面的表達式(1)乘積,總運行時間復雜度T(N)
結果為
N*N*N! = N^2*N!
時間復雜度來自 for 循環執行的次數。 在書中這用n * n!
表示n * n!
這是一種簡化。 此運行時來自圖中的估計節點數。 如果您開始繪制圖形,您會看到最低級別的節點數為n!
在每個級別中,它是較低級別的節點數除以2, 3, .., n
util 頂層有一個節點。
在書中,他們沒有計算節點的確切數量,而是將圖中的級別數乘以級別中的最大分支數,因此n * n!
. 這個數字將始終高於或等於節點的確切數量,因此它可以很好地作為上限。
然后如書中所述,for 循環體中的字符串連接將采用O(n)
因此總體時間復雜度為O(n * n * n!) = O(n^2 * n!)
出於兩個原因,我們沒有添加 if body 所花費的時間復雜度。 一個是主體的時間復雜度是O(1)
- 因為它只是一個打印語句。 如果主體將依賴於 n 或其他相關變量,那么我們將不得不添加該不變量和 if 主體執行次數的乘法。 其次, if 主體被執行n!
次,但我們已經將其計入 for 循環體執行的次數(有關 for 循環體的確切調用次數,請參見下面的等式)。
如果在 else 語句中但在 for 循環之外有更多代碼行,我們將不得不通過將不變量和 else 循環的執行次數相乘來增加時間復雜度。
我相信我們可以使用下面的等式獲得圖中節點的確切數量:
然后可以通過以下方式確定 for 循環體調用的數量:
我們必須加n! 因為 for 循環另外執行了 n! 長度為零的 rem 的次數。 我們必須減去 1,因為沒有為第一個節點執行 for 循環體(其中rem.length() == str.length()
)。
解決這個問題的一個簡單技巧是 O(節點數 * 每個節點的計算量)。
每個節點的計算是 O(n)。
節點數可以通過對每個級別的節點求和來計算。
即(n!/1!)+(n!/2!)+(n!/3!)...+(n!/n!)
等於 n! * (1/1! + 1/2! + ... + 1/n!)
該系列的總和 (1/1! + 1/2! + ... + 1/n!) 被視為n (作為上限)
這導致節點數為 n * n!
所以,O(節點數*每個節點的計算)是O(n * n! * n),也就是O(n 2 * n!)
但是,如果我們進行真正的數學運算, (1/1! + 1/2! + ... + 1/n!) 是 1.7182 (其中 n > 6 )
因此,節點數為 1.7182 * n!。 復雜度必須是 O(1.7182 * n! * n) 即O(n * n!)
當我在代碼之間使用計數時,我總是得到 n 階乘作為步數。
public class example12 {
int count=0;
public static void main(String args[])
{
example12 a= new example12();
a.permutation("12345678", "test");
}
void permutation(String str){
permutation(str,"");
}
void permutation(String str, String prefix){
if(str.length()==0){
System.out.println(prefix);
System.out.println(count+"at print");
} else{
for(int i=0;i<str.length();i++){
String rem=str.substring(0,i)+str.substring(i+1);
permutation(rem,prefix+str.charAt(i));
System.out.println(count);
count= count+1;
}
}
}
}
聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.