編程之法:面試和算法心得(奇偶調序)
內容全部來自編程之法:面試和算法心得一書,實現是自己寫的使用的是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; } } }
編程之法:面試和算法心得(奇偶調序)