1. 程式人生 > >編程之法:面試和算法心得(奇偶調序)

編程之法:面試和算法心得(奇偶調序)

一中 gpo part exc java 面試 正常 序列 pre

內容全部來自編程之法:面試和算法心得一書,實現是自己寫的使用的是java

題目描述

輸入一個整數數組,調整數組中數字的順序,使得所有奇數位於數組的前半部分,所有偶數位於數組的後半部分。要求時間復雜度為O(n)。

分析與解法

最容易想到的辦法是從頭掃描這個數組,每碰到一個偶數,拿出這個數字,並把位於這個數字後面的所有數字往前挪動一位。挪完之後在數組的末尾有一個空位,然後把該偶數放入這個空位。由於每碰到一個偶數,需要移動O(n)個數字,所以這種方法總的時間復雜度是O(n^2),不符合題目要求。

事實上,若把奇數看做是小的數,偶數看做是大的數,那麽按照題目所要求的奇數放在前面偶數放在後面,就相當於小數放在前面大數放在後面,聯想到快速排序中的partition過程,不就是通過一個主元把整個數組分成大小兩個部分麽,小於主元的小數放在前面,大於主元的大數放在後面。

而partition過程有以下兩種實現:

  • 一頭一尾兩個指針往中間掃描,如果頭指針遇到的數比主元大且尾指針遇到的數比主元小,則交換頭尾指針所分別指向的數字;
  • 一前一後兩個指針同時從左往右掃,如果前指針遇到的數比主元小,則後指針右移一位,然後交換各自所指向的數字。

類似這個partition過程,奇偶排序問題也可以分別借鑒partition的兩種實現解決。

為何?比如partition的實現一中,如果最終是為了讓整個序列元素從小到大排序,那麽頭指針理應指向的就是小數,而尾指針理應指向的就是大數,故當頭指針指的是大數且尾指針指的是小數的時候就不正常,此時就當交換。

解法一

借鑒partition的實現一,我們可以考慮維護兩個指針,一個指針指向數組的第一個數字,我們稱之為頭指針,向右移動;一個指針指向最後一個數字,稱之為尾指針,向左移動。

這樣,兩個指針分別從數組的頭部和尾部向數組的中間移動,如果第一個指針指向的數字是偶數而第二個指針指向的數字是奇數,我們就交換這兩個數字。

因為按照題目要求,最終是為了讓奇數排在數組的前面,偶數排在數組的後面,所以頭指針理應指向的就是奇數,尾指針理應指向的就是偶數,故當頭指針指向的是偶數且尾指針指向的是奇數時,我們就當立即交換它們所指向的數字。

思路有了,接下來,寫代碼實現:

//判斷是否為奇數
    public static boolean isOdd(int number)
    {
        return number%2==1;
    }
    /*
     * 解法一
     * 借鑒partition的實現一,我們可以考慮維護兩個指針,一個指針指向數組的第一個數字,我們稱之為頭指針,向右移動;一個指針指向最後一個數字,稱之為尾指針,向左移動。
     * 這樣,兩個指針分別從數組的頭部和尾部向數組的中間移動,如果第一個指針指向的數字是偶數而第二個指針指向的數字是奇數,我們就交換這兩個數字。
     * 因為按照題目要求,最終是為了讓奇數排在數組的前面,偶數排在數組的後面,所以頭指針理應指向的就是奇數,尾指針理應指向的就是偶數,故當頭指針指向的是偶數且尾指針指向的是奇數時,我們就當立即交換它們所指向的數字。
     
*/ public static void solution1(int[] arr) { int i=0; int j=arr.length-1; while(i<j) { if(isOdd(arr[i])) { i++; } else if(!isOdd(arr[j])) { j--; } else { int temp = arr[i]; arr[i] = arr[j]; arr[j] = temp; } } }

解法二

我們先來看看快速排序partition過程的第二種實現是具體怎樣的一個原理。

partition分治過程,每一趟排序的過程中,選取的主元都會把整個數組排列成一大一小的序列,繼而遞歸排序完整個數組。如下偽代碼所示:

PARTITION(A, p, r)
1  x ← A[r]
2  i ← p - 1
3  for j ← p to r - 1
4       do if A[j] ≤ x
5             then i ← i + 1
6                  exchange A[i] <-> A[j]
7  exchange A[i + 1] <-> A[r]
8  return i + 1

舉個例子如下:現要對數組data = {2, 8,7, 1, 3, 5, 6, 4}進行快速排序,為了表述方便,令i指向數組頭部前一個位置,j指向數組頭部元素,j在前,i在後,雙雙從左向右移動。

① j 指向元素2時,i 也指向元素2,2與2互換不變

i  p/j

      2   8   7   1   3   5   6   4(主元)

② 於是j 繼續後移,直到指向了1,1 <= 4,於是i++,i 指向8,故j 所指元素1 與 i 所指元素8 位置互換:

     i       j

  2   1   7   8   3   5   6   4

③ j 繼續後移,指到了元素3,3 <= 4,於是同樣i++,i 指向7,故j 所指元素3 與 i 所指元素7 位置互換:

        i       j

  2   1   3   8   7   5   6   4

④ j 一路後移,沒有再碰到比主元4小的元素:

          i               j

  2   1   3   8   7   5   6   4

⑤ 最後,A[i + 1] <-> A[r],即8與4交換,所以,數組最終變成了如下形式:

    2   1   3   4   7   5   6   8

這樣,快速排序第一趟完成。就這樣,4把整個數組分成了倆部分,2 1 3,7 5 6 8,再遞歸對這兩部分分別進行排序。

借鑒partition的上述實現,我們也可以維護兩個指針i和j,一個指針指向數組的第一個數的前一個位置,我們稱之為後指針i,向右移動;一個指針指向數組第一個數,稱之為前指針j,也向右移動,且前指針j先向右移動。如果前指針j指向的數字是奇數,則令i指針向右移動一位,然後交換i和j指針所各自指向的數字。

因為按照題目要求,最終是為了讓奇數排在數組的前面,偶數排在數組的後面,所以i指針理應指向的就是奇數,j指針理應指向的就是偶數,所以,當j指針指向的是奇數時,不正常,我們就當讓i++,然後交換i和j指針所各自指向的數字。

參考代碼如下:

/*
     * 借鑒partition的上述實現,我們也可以維護兩個指針i和j,
     * 一個指針指向數組的第一個數的前一個位置,我們稱之為後指針i,向右移動;
     * 一個指針指向數組第一個數,稱之為前指針j,也向右移動,且前指針j先向右移動。
     * 如果前指針j指向的數字是奇數,則令i指針向右移動一位,然後交換i和j指針所各自指向的數字。
     * 因為按照題目要求,最終是為了讓奇數排在數組的前面,偶數排在數組的後面,所以i指針理應指向的就是奇數,j指針理應指向的就是偶數,所以,當j指針指向的是奇數時,不正常,我們就當讓i++,然後交換i和j指針所各自指向的數字。
     */
    public static void solution2(int[] arr)
    {
        int i=-1;
        int j=0;
        for(;j<arr.length;j++)
        {
            if(isOdd(arr[j]))
            {
                i++;
                int temp = arr[i];
                arr[i] = arr[j];
                arr[j] = temp;
            }
        }
    }

編程之法:面試和算法心得(奇偶調序)