劍指offer-3-面試14:調整數組順序使奇數位於偶數前面

題目

輸入一個整數數組,實現一個函數來調整該數組中數字的順序,使得所有奇數位於數組的前半部分,所有偶數位於數組的後半部分。

分析

如果不考慮時間複雜度,最簡單的思路應該是從頭掃描這個數組,每碰到一個偶數時,拿出這個數字,並把位於這個數字後面的所有數字往前挪動一位。挪完之後在數組的末尾有一個空位,這時把該偶數放入這個空位。由於每碰到一個偶數就需要移動O(n)個數字,因此總的時間複雜度是O(n^2)。但是,這種方法不能讓面試官滿意。不過如果我們在聽到這個題目之後馬上能說出這個解法,面試官至少會覺得我們的思維非常敏捷。

只完成基本功能的解法,僅適用於初級程序員

這個題目要求把奇數放在數組的前半部分,偶數放在數組的後半部分,因此所有的奇數應該位於偶數的前面。也就是說我們在掃描這個數組的時候,如果發現有偶數出現在奇數的前面,我們可以交換它們的順序,交換之後就符合要求了。

void ReorderOddEven_1(int *pData, unsigned int length)
{
    if(pData == NULL || length == 0)
        return;

    int *pBegin = pData;
    int *pEnd = pData + length - 1;

    while(pBegin < pEnd)
    {
        // 向後移動pBegin,直到它指向偶數
        while(pBegin < pEnd && (*pBegin & 0x1) != 0)
            pBegin ++;

        // 向前移動pEnd,直到它指向奇數
        while(pBegin < pEnd && (*pEnd & 0x1) == 0)
            pEnd --;

        if(pBegin < pEnd)
        {
            int temp = *pBegin;
            *pBegin = *pEnd;
            *pEnd = temp;
        }
    }
}

考慮可擴展性的解法,能秒殺offer

如果是面試應屆畢業生或者工作時間不長的程序員,面試官會滿意前面的代碼。但如果應聘者申請的是資深的開發職位,那面試官可能會接着問幾個問題。

面試官: 如果把題目改成把數組中的數按照大小分爲兩部分,所有負數都在非負數的前面,該怎麼做?
應聘者:可以重新定義一個函數,在新的函數裏,只要修改第二個和第三個while循環中的判斷條件就行了。
面試官:如果把題目再改改,變成把數組中的數分爲兩部分,能被3整除的數都在不能被3整除的數的前面。怎麼辦
應聘者:可以定義一個新的函數。在這個函數中。。。
面試官:(打斷應聘者的話)難道就沒有更好的辦法?

這個時候應聘者應該要反應過來,面試官期待我們提供的不僅僅是解決一個問題的方法,而是解決一系列同類型問題的通用辦法。這就是面試官在考察我們對擴展性的理解,即希望我們能夠給出一個模式,在這個模式下能夠很方便地把已有的解決方案擴展到同類型的問題上去。

回到面試官提出的兩個問題上來。我們發現要解決這兩個新的問題,其實只需要修改函數ReorderOddEven中的兩處判斷的標準,而大的邏輯框架完全不需要改動。因此我們可以把這個邏輯框架抽象出來,而把判斷的標準變成一個函數指針,也就是用一個單獨的函數來判斷數字是不是符合標準。這樣我們就把整個函數解耦成兩部分:一是判斷數字應該在數組前半部分還是後半部分的標準,二是拆分數組的操作。

void Reorder(int *pData, unsigned int length, bool (*func)(int))
{
    if(pData == NULL || length == 0)
        return;

    int *pBegin = pData;
    int *pEnd = pData + length - 1;

    while(pBegin < pEnd) 
    {
        // 向後移動pBegin
        while(pBegin < pEnd && !func(*pBegin))
            pBegin ++;

        // 向前移動pEnd
        while(pBegin < pEnd && func(*pEnd))
            pEnd --;

        if(pBegin < pEnd)
        {
            int temp = *pBegin;
            *pBegin = *pEnd;
            *pEnd = temp;
        }
    }
}


bool isEven(int n)
{
    return (n & 1) == 0;
}

在上面的代碼中,函數Reorder根據func的標準把數組pData分成兩部分:而函數isEven則是一個具體的標準,即判斷一個數是不是偶數。有了這兩個函數,我們可以很方便地把數組中所有奇數移到偶數的前面

void ReorderOddEven_2(int *pData, unsigned int length)
{
    Reorder(pData, length, isEven);
}

如果把問題改成把數組中的負數移到非負數的前面,或者把能被3整除到不能被3整除的數前面,都只需要定義新的函數來確定分組的標準,而函數Reorder不需要做任何改動。也就是說解耦的好處就是提高了代碼的重用性,爲功能擴展提供了便利。

測試用例&代碼

(1)功能測試(輸入數組中的奇數、偶數交替出現,輸入的數組中所有偶數都出現在奇數的前面,輸入的數組中所有奇數都出現在偶數的前面)

(2)特殊輸入測試(輸入NULL指針、輸入的數組只包含一個數字)

本題考點

(1)快速思維能力。要在短時間內按照要求把數組分隔成兩部分,不是一件容易的事情,需要較快的思維能力

(2)對於一件工作幾年的應聘者,面試官還將考察其對擴展性的理解,要求應聘者寫出的代碼具有可重用性。

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