名企面試官精講典型程式設計題之資料結構陣列篇

broadviewbj發表於2011-12-05

名企面試官精講典型程式設計題之資料結構陣列篇

資料結構一直是技術面試的重點,大多數面試題都是圍繞著陣列、字串、連結串列、樹、棧及佇列這幾種常見的資料結構展開的,因此每一個應聘者都要熟練掌握這幾種資料結構。

陣列和字串是兩種最基本的資料結構,它們用連續記憶體分別儲存數字和字元。連結串列和樹是面試中出現頻率最高的資料結構。由於操作連結串列和樹需要操作大量的指標,應聘者在解決相關問題的時候一定要留意程式碼的魯棒性,否則容易出現程式崩潰的問題。棧是一個與遞迴緊密相關的資料結構,同樣佇列也與廣度優先遍歷演算法緊密相關。深刻理解這兩種資料結構能幫助我們解決很多演算法問題。

陣列

陣列可以說是最簡單的一種資料結構,它佔據一塊連續的記憶體並按照順序儲存資料。建立陣列時,我們需要首先指定陣列的容量大小,然後根據大小分配記憶體。即使我們只在陣列中儲存一個數字,也需要為所有的資料預先分配記憶體。因此陣列的空間效率不是很好,經常會有空閒的區域沒有得到充分利用。

由於陣列中的記憶體是連續的,於是可以根據下標在O(1)時間讀/寫任何元素,因此它的時間效率是很高的。我們可以根據陣列時間效率高的優點,用陣列來實現簡單的雜湊表:把陣列的下標設為雜湊表的鍵值(Key),而把陣列中的每一個數字設為雜湊表的值(Value),這樣每一個下標及陣列中該下標對應的數字就組成了一個鍵值-值的配對。有了這樣的雜湊表,我們就可以在O(1)實現查詢,從而可以快速高效地解決很多問題。面試題35“第一個只出現一次的字母”就是一個很好的例子。

為了解決陣列空間效率不高的問題,人們又設計實現了多種動態陣列,比如C++STL中的vector。為了避免浪費,我們先為陣列開闢較小的空間,然後往陣列中新增資料。當資料的數目超過陣列的容量時,我們再重新分配一塊更大的空間(STLvector每次擴充容量時,新的容量都是前一次的兩倍),把之前的資料複製到新的陣列中,再把之前的記憶體釋放,這樣就能減少記憶體的浪費。但我們也注意到每一次擴充陣列容量時都有大量的額外操作,這對時間效能有負面影響,因此使用動態陣列時要儘量減少改變陣列容量大小的次數。

C/C++中,陣列和指標是相互關聯又有區別的兩個概念。當我們宣告一個陣列時,其陣列的名字也是一個指標,該指標指向陣列的第一個元素。我們可以用一個指標來訪問陣列。但值得注意的是,C/C++沒有記錄陣列的大小,因此用指標訪問陣列中的元素時,程式設計師要確保沒有超出陣列的邊界。下面透過一個例子來了解陣列和指標的區別。執行下面的程式碼,請問輸出是什麼?

int GetSize(int data[])

{

    return sizeof(data);

}

 

int _tmain(int argc, _TCHAR* argv[])

{

    int data1[] = {1, 2, 3, 4, 5};

    int size1 = sizeof(data1);

 

    int* data2 = data1;

    int size2 = sizeof(data2);

 

    int size3 = GetSize(data1);

 

    printf("%d, %d, %d", size1, size2, size3);

}

答案是輸出“20, 4, 4”。data1是一個陣列,sizeof(data1)是求陣列的大小。這個陣列包含5個整數,每個整數佔4位元組,因此總共是20位元組。data2宣告為指標,儘管它指向了陣列data1的第一個數字,但它的本質仍然是一個指標。在32位系統上,對任意指標求sizeof,得到的結果都是4。在C/C++中,當陣列作為函式的引數進行傳遞時,陣列就自動退化為同型別的指標。因此儘管函式GetSize的引數data被宣告為陣列,但它會退化為指標,size3的結果仍然是4

面試題3:二維陣列中的查詢

題目:在一個二維陣列中,每一行都按照從左到右遞增的順序排序,每一列都按照從上到下遞增的順序排序。請完成一個函式,輸入這樣的一個二維陣列和一個整數,判斷陣列中是否含有該整數。

例如下面的二維陣列就是每行、每列都遞增排序。如果在這個陣列中查詢數字7,則返回true;如果查詢數字5,由於陣列不含有該數字,則返回false

 

在分析這個問題的時候,很多應聘者都會把二維陣列畫成矩形,然後從陣列中選取一個數字,分3種情況來分析查詢的過程。當陣列中選取的數字剛好和要查詢的數字相等時,就結束查詢過程。如果選取的數字小於要查詢的數字,那麼根據陣列排序的規則,要查詢的數字應該在當前選取的位置的右邊或者下邊(如圖2.1a)所示)。同樣,如果選取的數字大於要查詢的數字,那麼要查詢的數字應該在當前選取的位置的上邊或者左邊(如圖2.1b)所示)。

  

2.1  二維陣列中的查詢

注:在陣列中間選擇一個數(深色方格),根據它的大小判斷要查詢的數字可能出現的區域(陰影部分)。

在上面的分析中,由於要查詢的數字相對於當前選取的位置有可能在兩個區域中出現,而且這兩個區域還有重疊,這問題看起來就複雜了,於是很多人就卡在這裡束手無策了。

當我們需要解決一個複雜的問題時,一個很有效的辦法就是從一個具體的問題入手,透過分析簡單具體的例子,試圖尋找普遍的規律。針對這個問題,我們不妨也從一個具體的例子入手。下面我們以在題目中給出的陣列中查詢數字7為例來一步步分析查詢的過程。

前面我們之所以遇到難題,是因為我們在二維陣列的中間選取一個數字來和要查詢的數字做比較,這樣導致下一次要查詢的是兩個相互重疊的區域。如果我們從陣列的一個角上選取數字來和要查詢的數字做比較,情況會不會變簡單呢?

首先我們選取陣列右上角的數字9。由於9大於7,並且9還是第4列的第一個(也是最小的)數字,因此7不可能出現在數字9所在的列。於是我們把這一列從需要考慮的區域內剔除,之後只需要分析剩下的3列(如圖2.2a)所示)。在剩下的矩陣中,位於右上角的數字是8。同樣8大於7,因此8所在的列我們也可以剔除。接下來我們只要分析剩下的兩列即可(如圖2.2b)所示)。

在由剩餘的兩列組成的陣列中,數字2位於陣列的右上角。2小於7,那麼要查詢的7可能在2的右邊,也有可能在2的下邊。在前面的步驟中,我們已經發現2右邊的列都已經被剔除了,也就是說7不可能出現在2的右邊,因此7只有可能出現在2的下邊。於是我們把數字2所在的行也剔除,只分析剩下的三行兩列數字(如圖2.2c)所示)。在剩下的數字中,數字4位於右上角,和前面一樣,我們把數字4所在的行也刪除,最後剩下兩行兩列數字(如圖2.2d)所示)。

在剩下的兩行兩列4個數字中,位於右上角的剛好就是我們要查詢的數字7,於是查詢過程就可以結束了。

 

2.2  在二維陣列中查詢7的步驟

注:矩陣中加陰影背景的區域是下一步查詢的範圍。

總結上述查詢的過程,我們發現如下規律:首先選取陣列中右上角的數字。如果該數字等於要查詢的數字,查詢過程結束;如果該數字大於要查詢的數字,剔除這個數字所在的列;如果該數字小於要查詢的數字,剔除這個數字所在的行。也就是說如果要查詢的數字不在陣列的右上角,則每一次都在陣列的查詢範圍中剔除一行或者一列,這樣每一步都可以縮小查詢的範圍,直到找到要查詢的數字,或者查詢範圍為空。

把整個查詢過程分析清楚之後,我們再寫程式碼就不是一件很難的事情了。下面是上述思路對應的參考程式碼:

bool Find(int* matrix, int rows, int columns, int number)

{

    bool found = false;

 

    if(matrix != NULL && rows > 0 && columns > 0)

    {

        int row = 0;

        int column = columns - 1;

        while(row < rows && column >=0)

        {

            if(matrix[row * columns + column] == number)

            {

                found = true;

                break;

            }

            else if(matrix[row * columns + column] > number)

                -- column;

            else

                ++ row;

        }

    }

 

    return found;

}

在前面的分析中,我們每一次都是選取陣列查詢範圍內的右上角數字。同樣,我們也可以選取左下角的數字。感興趣的讀者不妨自己分析一下每次都選取左下角的查詢過程。但我們不能選擇左上角或者右下角。以左上角為例,最初數字1位於初始陣列的左上角,由於1小於7,那麼7應該位於1的右邊或者下邊。此時我們既不能從查詢範圍內剔除1所在的行,也不能剔除1所在的列,這樣我們就無法縮小查詢的範圍。

  原始碼:

本題完整的原始碼詳見03_FindInPartiallySortedMatrix專案。

  測試用例:

     二維陣列中包含查詢的數字(查詢的數字是陣列中的最大值和最小值,查詢的數字介於陣列中的最大值和最小值之間)。

     二維陣列中沒有查詢的數字(查詢的數字大於陣列中的最大值,查詢的數字小於陣列中的最小值,查詢的數字在陣列的最大值和最小值之間但陣列中沒有這個數字)。

     特殊輸入測試(輸入空指標)。

  本題考點:

     考查應聘者對二維陣列的理解及程式設計能力。二維陣列在記憶體中佔據連續的空間。在記憶體中從上到下儲存各行元素,在同一行中按照從左到右的順序儲存。因此我們可以根據行號和列號計算出相對於陣列首地址的偏移量,從而找到對應的元素。

     考查應聘者分析問題的能力。當應聘者發現問題比較複雜時,能不能透過具體的例子找出其中的規律,是能否解決這個問題的關鍵所在。這個題目只要從一個具體的二維陣列的右上角開始分析,就能找到查詢的規律,從而找到解決問題的突破口。

 名企面試官精講典型程式設計題之資料結構陣列篇

 

本文選自《劍指Offer——名企面試官精講典型程式設計題》一書

圖書詳細資訊:http://space.itpub.net/?uid-13164110-action-viewspace-itemid-712760

 

來自 “ ITPUB部落格 ” ,連結:http://blog.itpub.net/13164110/viewspace-712761/,如需轉載,請註明出處,否則將追究法律責任。

相關文章