[英]Is my analysis of space complexity correct?
這是Cracking the Coding Interview 第 5版的問題9.5
問題:編寫一種方法來計算字符串的所有排列
這是我的解決方案,用Java編碼(測試它,它工作:))
public static void generatePerm(String s) {
Queue<Character> poss = new LinkedList<Character>();
int len = s.length();
for(int count=0;count<len;count++)
poss.add(s.charAt(count));
generateRecurse(poss, len, "");
}
private static void generateRecurse(Queue<Character> possibles, int n, String word) {
if(n==0)
System.out.println(word);
else {
for(int count=0;count<n;count++) {
char first = possibles.remove();
generateRecurse(possibles, n-1, word+first);
possibles.add(first);
}
}
}
我同意作者的觀點,我的解決方案在O(n!)時間復雜度下運行,因為要解決這個問題,你必須考慮階乘,就像像“top”這樣的單詞,第一個字母有三種可能性,2為第二等......
然而,她沒有提到空間復雜性。 我知道面試官喜歡問你解決方案的時間和空間復雜性。 這個解決方案的空間復雜性是什么?
我最初的猜測是O(n 2 ),因為在每個級別n都有n個遞歸調用。 所以你要加n + n - 1 + n - 2 + n - 3 ..... + 1得到n(n + 1) / 2 ,它在O(n 2 )中。 我推斷有n個遞歸調用,因為你必須在每個級別回溯n次,並且空間復雜性是你的算法所做的遞歸調用的次數。 例如,當考慮“TOP”的所有排列時,在級別上,3個遞歸調用,gR([O,P],2,“T”),gR([P,T],2,“O”),制作gR([T,O],2,“P”)。 我對空間復雜性的分析是否正確?
我認為你得到了正確的答案,但出於錯誤的原因。 遞歸調用的數量與它沒有任何關系。 當你進行遞歸調用時,它會向堆棧添加一定的空間; 但是當該調用退出時,堆棧空間被釋放。 所以假設你有這樣的東西:
void method(int n) {
if (n == 1) {
for (int i = 0; i < 10000; i++) {
method(0);
}
}
}
method(1);
盡管method
自身調用了10000次,但在任何時候堆棧上的method
調用仍然不會超過2次。 因此空間復雜度將為O(1)[常數]。
您的算法具有空間復雜度O(n 2 )的原因是由於word
字符串。 當n
下降到0時,將通過調用generateRecurse
來獲取len
堆棧條目。 最多會有len
堆棧條目,因此堆棧的空間使用量只有O(n); 但是每個堆棧條目都有自己的word
,它們同時存在於堆棧中; 這些word
參數的長度為1,2,..., len
,當然這相加(len * (len+1)) / 2
,這意味着空間使用量為O(n 2 )。
關於堆棧框架的更多信息:似乎對堆棧框架基礎知識的解釋會有所幫助......
“堆棧幀”只是內存區域的“堆棧”的一部分。 通常,堆棧是預定義的存儲區域; 但是,堆棧幀的位置和大小不是預定義的。 當程序第一次執行時,堆棧中不會有任何東西(實際上,那里可能會有一些初始數據,但是假設沒有什么,只是為了簡單起見)。 所以內存的堆棧區域如下所示:
bottom of stack top of stack
------------------------------------------------------------------
| nothing |
------------------------------------------------------------------
^
+--- stack pointer
(這假設堆棧從較低地址向較高地址增長。許多機器都有向下增長的堆棧。為了簡化,我將繼續假設這是一個堆棧向上增長的機器。)
當調用方法(函數,過程,子例程等)時,分配堆棧的某個區域。 該區域足以容納方法的局部變量(或對它們的引用),參數(或對它們的引用),一些數據以便程序在返回時知道return
,以及可能的其他信息 - 其他信息高度依賴於機器,編程語言和編譯器。 在Java中,第一種方法將是main
方法
bottom of stack top of stack
------------------------------------------------------------------
| main's frame | nothing |
------------------------------------------------------------------
^
+--- stack pointer
請注意,堆棧指針已向上移動。 現在main
調用method1
。 由於method1
將返回到main
,局部變量和參數main
必須保存在main
變恢復執行。 在堆棧上分配一些大小的新幀:
bottom of stack top of stack
------------------------------------------------------------------
| main's frame | method1's frame | nothing |
------------------------------------------------------------------
^
+--- stack pointer
然后method1
調用method2
:
bottom of stack top of stack
------------------------------------------------------------------
| main's frame | method1's frame | method2's frame | nothing |
------------------------------------------------------------------
^
+--- stack pointer
現在method2
返回。 在method2
返回后,將無法再訪問其參數和局部變量。 因此,可以拋出整個框架。 這是通過將堆棧指針移回到之前的位置來完成的。 (“上一個堆棧指針”是某些幀中保存的東西之一。)堆棧返回到如下所示:
bottom of stack top of stack
------------------------------------------------------------------
| main's frame | method1's frame | nothing |
------------------------------------------------------------------
^
+--- stack pointer
這意味着,此時,機器將看到從堆棧指針開始的堆棧部分為“未使用”。 這不是真正正確的說話的method2
的幀被重用。 你不能真正使用已經不存在的東西,並且method2
的框架不再存在。 從概念上講,所有存在的堆棧都是一個很大的空白區域。 如果method1
調用另一個方法,無論是method2
, method1
遞歸, System.out.println
還是其他方法,都會在堆棧指針指向的位置創建一個新幀。 該幀可以比以前的method2
幀更小,相等或更大。 它將占用method2
幀所在的部分或全部內存。 如果它是對method2
的另一次調用,那么使用相同或不同的參數調用它並不重要。 這沒關系,因為程序不記得上次使用的參數。 它只知道從堆棧指針開始的內存區域是空的並且可以使用。 該計划不知道最近在那里生活的框架。 那個框架消失了,消失了,消失了。
如果您可以遵循這一點,您可以看到在計算空間復雜性時以及僅查看堆棧使用的空間量時,唯一重要的是,在任何一個時間點堆棧上可以存在多少幀? 過去可能存在但不再存在的幀與計算無關,無論調用哪些方法參數。
(PS如果有人計划指出我在這個或那個細節上的技術錯誤 - 我已經知道這是一個粗略的過度簡化。)
聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.