面試題-全排列輸出其所有的排列方式的兩種解法

我丟下C語言有些時間了,上一次刷題是爲了2017年秋季的PAT考試。趁着週末大好時光不能浪費,做點有意義的事情,回顧一下這道經典面試題。回顧的過程不是很順利,上午去官網下CodeBlock,官網只有最新版本17.12,折騰了一個多小時mingw編譯器就是沒能配好。問小夥伴要了個13.12的版本,10分鐘就配好了。

說到CodeBlock版本,我想起了大一新生那會我帶了臺很老舊的筆記本去學校,有多老呢,電腦的CPU是U7600,主頻1.2GHZ。
那時工作室來宣傳招新,我立馬來了興趣。進工作室要先刷題霸榜,學長推薦刷題用CodeBlock編譯器,就在學校的NYOJ刷,那時最新版本是13.12,我那電腦啓動編譯器需要近半分鐘,編譯一次要好幾秒,可能是因爲打字姿勢不對,CodeBlock在我手上抽風得厲害,因此我堅持用了半年Microsoft Visual C++(真是太難了),直到換電腦。

現在回頭看,手上的電腦換了又換,CodeBlock也更新了好幾版13.12、16.01、17.12,倒是這題目還是老題。這道題我以前肯定做過,說不定解法還發到了評論區並且底下還有幾句學弟們的牛逼尖叫呢。可是我已經不記得以前的解法,只能從頭再來敲一遍,這一遍要記錄下來,既要放蕩不羈也要穩如老狗。


這道題應該各OJ平臺都有,我貼上來的圖片是在leetcode上截取的。

看到全排列問題,我首先想到這肯定有解,並且是有限解(這不是廢話嘛,無限解讓判題機咋判題)。

看到整整齊齊的輸出結果,我聞到了用for循環打印的味道,邊界在哪裏呢,我感覺應該是找到未使用過的元素,有點像路徑搜索類題目。

最後只剩下一個未使用元素,找到它就完成一次輸出,因此某種情況下幾乎可以確定會有結果,這又有了點數學歸納法的味道。
簡單的1-2-3位全排列,我們可以腦補出來是什麼結果,複雜的4位或以上的,通過化簡成3-2-1位後也是一樣的解法。


這道題就有了兩個思路,一個是搜索,一個是數學歸納法(遞歸求解)。
這裏的代碼代碼只是爲了驗證思路,沒有按格式輸入輸出。


先說搜索的解法,
通過給每個元素做標記,我們可以知道這個元素是否使用過,
對於使用過的元素,我們跳過,沒有使用過的元素,我們記下來,
並且記錄這是迄今爲止找到的第幾個元素,進入下一階搜索,如果已經籌夠N個,
表示已完成一次全排列的搜尋。
完成一次全排列的搜尋後,需要按階一步步後退,
後退時將用過的元素標記成未使用過,這樣在下一階求解可以用到這個元素。

 

#include <stdio.h>
struct Node{
    bool isUsed;
    int num;
};
struct Node arr[3];
void init();
int main(void) {
    init();
    for(int i = 0; i < 3; i++){
        if(!arr[i].isUsed){
            arr[i].isUsed = true;
            for(int j = 0; j < 3; j++){
                if(!arr[j].isUsed){
                    arr[j].isUsed = true;
                    for(int k = 0; k < 3; k++){
                        if(!arr[k].isUsed){
                            printf("%d",arr[i].num);
                            printf(" %d",arr[j].num);
                            printf(" %d\n",arr[k].num);
                        }
                    }
                    arr[j].isUsed = false;
                }

            }
        arr[i].isUsed = false;
        }
    }
    return 0;
}
void init(){
    arr[0].isUsed = false;
    arr[1].isUsed = false;
    arr[2].isUsed = false;
    arr[0].num = 1;
    arr[1].num = 2;
    arr[2].num = 3;
}

運行截圖:

再說說數學歸納法(遞歸)的解法,
通過將n階問題簡化成n-1階,遞歸求解每次下降一階,直至降到1階段,得到答案。

在運算過程中,數組中的元素排列就是所求的答案,
按住第一位(arr[0]),對後面的元素求全排列。這裏有2個解法。
交換第一位和第二位,對後面的元素求全排列。這裏又有2個解法。
交換第一位和第三位,對後面的元素求全排列。這裏還有2個解法,共計6種。


#include <stdio.h>
void swap(int *a, int *b);
int arr[] = {1, 2, 3};
void permutation(int index)//ееап
{
    int i;
    if(index > 2)
    {
    printf("%d,%d,%d\n", arr[0],arr[1],arr[2]);
    }
    else
    {
        for(i = index; i < 3; i++)
        {
            swap(&arr[index], &arr[i]);
            permutation(index + 1);
            swap(&arr[index], &arr[i]);
        }
    }
}
int main()
{
    permutation(0);
    return 0;
}
void swap(int *a, int *b)
{
    int temp = *a;
    *a = *b;
    *b = temp;
}


運行截圖:


總結:搜索的解法解釋起來難,但是編程起來簡單,遞歸的解法解釋起來簡單,但是編程起來難。難在遞歸的關鍵是何時跳出遞歸,也就是遞推公式要有出口,在本題中遞歸解法回溯時還有點逆向思維的味道。這兩種解法效果上也不太相同,第一種是字典順序輸出,第二種不是。對比兩種解法可以獲得更深刻的理解,兩種解法都不難,希望本文可以給各位小夥伴一點啓發。

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