《算法設計手冊》面試題解答 第四章:排序和搜索

4-40.

  如果給你1,000,000個整數來排序,你會選擇什麼算法?消耗的時間和空間呢?

解析:

  我個人傾向於用隨機化的快速排序。

  首先是它在平均意義上來看比同樣O(nlogn)的歸併排序和堆排序快(見4-41)。 

  另外,和堆排序相比,快速排序的元素掃描是線性的,而且交換常被限制在一個有限範圍內。假如這所有的整數不能存入內存,那麼發生缺頁中斷的次數也小於堆排序。當然,當數據量更大時,問題就會牽扯到內部排序(英文維基/百度百科)和外部排序(英文維基/百度百科)的討論。

  同時,在《編程珠璣》上看到,如果這些數字有特徵,如不重複出現,且範圍不是很大,那麼可以設計出專門的算法來完成,比如使用位向量排序

  面試時的開放型題目,不妨儘可能廣泛而深入的探討。

 

4-41.

  分析最常見的排序算法的優點和缺點。

解析:

  這個問題老生常談了,相關文章特別多,不打算在這裏解答。

  p.s.,原書正文提到,雖然都是O(nlogn)的時間複雜度,而且最壞情況下快速排序退化爲O(n2),但快速排序比歸併排序和對排序在多數情況下都快2~3倍,原因是它的最內層迭代語句最簡單和快速(原書4.6.3節)。

 

4-42.

  實現一個算法,返回數組中只出現一次的元素。

 解析:

  如果先排序,再遍歷,時間複雜度O(nlogn)。

  如果遍歷時進行hash,然後輸出整個hash表,那麼時間複雜度O(n)。(《劍指Offer》面試題35:第一個只出現一次的字符,這種方法可以找出所有隻出現一次的字符)

  如果加上額外的條件:只有一個元素出現了一次,其他都出現了偶數次,那麼把所有元素做異或,最終結果就是隻出現一次的元素,時間複雜度O(n)。(《編程之美》1.5快速找出故障機器)

 

4-43.

  限制2Mb內存,如何排序一個500Mb的文件?

解析:

  正如4-40提到的,使用外部排序吧。常見的是多路歸併排序,以下摘自百度百科:

外部排序最常用的算法是多路歸併排序,即將原文件分解成多個能夠一次性裝人內存的部分,分別把每一部分調入內存完成排序。然後,對已經排序的子文件進行歸併排序

 

4-44.

  設計一個棧,支持O(1)內完成push、pop和獲得最小值min的操作。

解析:

  一般思路是維護兩個棧,一個和一般的棧一樣,另一個用維護每個元素壓入第一個棧時的最小值。《劍指Offer》面試題21:包含min函數的棧有詳細分析,下面是它的代碼實現:

template <typename T> class StackWithMin
{
public:
    StackWithMin(void) {}
    virtual ~StackWithMin(void) {}

    T& top(void);
    const T& top(void) const;

    void push(const T& value);
    void pop(void);

    const T& min(void) const;

    bool empty() const;
    size_t size() const;

private:
    std::stack<T>   m_data;     // 數據棧,存放棧的所有元素
    std::stack<T>   m_min;      // 輔助棧,存放棧的最小元素
};

template <typename T> void StackWithMin<T>::push(const T& value)
{
    // 把新元素添加到輔助棧
    m_data.push(value);

    // 當新元素比之前的最小元素小時,把新元素插入輔助棧裏;
    // 否則把之前的最小元素重複插入輔助棧裏
    if(m_min.size() == 0 || value < m_min.top())
        m_min.push(value);
    else
        m_min.push(m_min.top());
}

template <typename T> void StackWithMin<T>::pop()
{
    assert(m_data.size() > 0 && m_min.size() > 0);

    m_data.pop();
    m_min.pop();
}


template <typename T> const T& StackWithMin<T>::min() const
{
    assert(m_data.size() > 0 && m_min.size() > 0);

    return m_min.top();
}

template <typename T> T& StackWithMin<T>::top()
{
    return m_data.top();
}
MinInStack

 

4-45.

  給定3個字母組成的字符串,比如ABC,和一篇文檔。找出文檔中的包含這3個字母的最短片段。同時,各個字母在文檔中出現位置的下標已經存放在一個排序數組中,比如A:[1,4,5]。

  (補充說明)爲了幫助理解原題題意,下面幾個典型輸入和輸出。

input1: [1,10], [2,20], [3,30]

output1:[1, 3],length=3

input2:[1,9,27], [6,10,19], [8,12,14]

output2:[8, 10],length=3

input3:[1,4,11,27], [3,6,10,19], [5,8,12,14]

output3:[3, 5],length=3

input4:[1,4,5], [3,9,10], [2,6,15]

output4:[1, 3],length=3

解析:

  假定文檔是CxxxAxxxBxxAxxCxBAxxxC,其中x代表非ABC的其他字母或符號。掃描過程是這樣的:

C
CA
CAB - all words, length 9 (CxxxAxxxB...)
CABA - all words, length 12 (CxxxAxxxBxxA...)
CABAC - violates The Property, remove first C
ABAC - violates The Property, remove first A
BAC - all words, length 7 (...BxxAxxC...)
BACB - violates The Property, remove first B
ACB - all words, length 6 (...AxxCxB...)
ACBA - violates The Property, remove first A
CBA - all words, length 4 (...CxBA...)
CBAC - violates The Property, remove first C
BAC - all words, length 6 (...BAxxxC)

  這個過程可以總結爲:

  對三個數組進行歸併,維護這個歸併字符串並統計歸併的字符串中A、B、C的個數;

  歸併時,當新加入的字符導致滿足A、B、C都出現時,統計這時片段長度並與最小值比較,如果小於最小值則更新並記錄開始和結尾的索引;

  當新加入的字符與歸併字符串第一個字符相同時,刪去第一個字符串。如果此時新的第一個字符在字符串出現次數非0,同樣刪去。這個過程遞歸進行直到首字母只出現了1次。

  繼續歸併直到整片文檔掃描完畢。

  方法來自於stackoverflow

 

  類似問題:《編程之美》3.5最短摘要的生成 

 

4-46.

  12個硬幣,其中11個是重量相同的真幣,另一個是假幣,重量與它們不同,但可能輕了也可能重了。請用天平只稱三次就確定哪個是假幣。

解析:

  如果已知不標準的硬幣是輕還是重,那麼很簡單,直接分3組,稱第一二組確定出硬幣在哪組,然後再組內對半稱,最後再對半稱。但此時不知是輕是重,這個方法不可行。

這裏輕重未知,在每次稱量時,應該儘量利用上次稱量出的輕重關係。爲了便於敘述,講12個硬幣標記爲1、2、...12。先稱量1+2+3+4和5+6+7+8:

如果相等,那麼假幣在9、10、11、12中。此時已知1~8是真幣,可以作爲標準來判斷,那麼使用1+10和2+12比較,如果相同則假幣在9和11中,9和1稱來判斷假幣是9還是11;否則用1和10來稱一次判斷假幣是10還是12。

如果不等,假設1+2+3+4>5+6+7+8(反之類似),此時9、10、11、12是真幣。那麼將1、2、3去掉換成5、6、7,再在右邊加上標準的9、10、11,形成5+6+7+4和9+10+11+8比較。

如果相等,假幣只可能在1、2、3中,並且由第一次稱量的結果,假幣比真幣重。從1、2、3中選擇2個,若平衡,則剩餘一個爲假幣,不平衡時重的那個是假幣。

如果5+6+7+4<9+10+11+8,只可能因爲輕的假幣來到了左邊。那麼就在5、6、7中判斷那個輕的假幣,和上面類似。

如果5+6+7+4>9+10+11+8,5、6、7必然都不是假幣,那麼只用判斷4和8哪個是假幣。使用1枚真幣和4稱量即可判斷。

  擴展一下,可以發現這個方法也能判斷13枚的情況:先分成12枚和1枚,如果假幣在12枚中,分析同上;如果假幣是那分出來的1枚,上面第一種相等的情況每次都是相等,判斷完三次就可得出結論:假幣不在12枚中,只能是那額外的1枚。

  另外,官方wiki answer裏是一個萬能的分組解法,操作起來按部就班,直接根據結果查表即可,但分組理由沒有詳細解釋,就沒有深入研究。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章