簡體   English   中英

我對空間復雜性的分析是否正確?

[英]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調用另一個方法,無論是method2method1遞歸, System.out.println還是其他方法,都會在堆棧指針指向的位置創建一個新幀。 該幀可以比以前的method2幀更小,相等或更大。 它將占用method2幀所在的部分或全部內存。 如果它是對method2的另一次調用,那么使用相同或不同的參數調用它並不重要。 這沒關系,因為程序不記得上次使用的參數。 它只知道從堆棧指針開始的內存區域是空的並且可以使用。 該計划不知道最近在那里生活的框架。 那個框架消失了,消失了,消失了。

如果您可以遵循這一點,您可以看到在計算空間復雜性時以及僅查看堆棧使用的空間量時,唯一重要的是,在任何一個時間點堆棧上可以存在多少幀? 過去可能存在但不再存在的幀與計算無關,無論調用哪些方法參數。

(PS如果有人計划指出我在這個或那個細節上的技術錯誤 - 我已經知道這是一個粗略的過度簡化。)

暫無
暫無

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

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