1. 程式人生 > >【劍指offer】面試題21:調整陣列順序使奇數位於偶數前面

【劍指offer】面試題21:調整陣列順序使奇數位於偶數前面

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

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

  • 只完成基本功的解法,僅適用於初級程式設計師

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

因此我們可以維護兩個指標,第一個指標初始化時指向陣列的第一個數字,它只向後移動;第二個指標初始化時指向陣列的最後一個數字,它指向前移動。在兩個指標相遇之前,第一個指標總是位於第二個指標的前面。如果第一個指標的數字是偶數,並且第二個指標指向的數字是奇數,我們就交換兩個數字。

下面看一個案例:

 

 基於這個分析,我們可以寫出如下程式碼:

public class OddBeforeEven {

	public int[] recorderOddEven(int[] data, int length){
		if(data == null && length <= 0){
			return null;
		}
		
		int begin = 0;
		int end = length - 1;
		
		// begin指標要位於end指標前面
		while(begin < end){
			// 向後移動begin指標,直到它指向偶數
			if(begin < end && (data[begin] & 0x1) != 0){
				begin++;
			}
			
			// 向前移動end指標,直到它指向奇數
			if(begin < end && (data[end] & 0x1) == 0){
				end--;
			}
			
			// 交換奇數和偶數的位置
			if(begin < end){
				int temp = data[begin];
				data[begin] = data[end];
				data[end] = temp;
			}
		}
		return data;
	}
	
	// 測試
	public static void main(String[] args) {
		OddBeforeEven obe = new OddBeforeEven();
		int[] arr= {1,2,3,4,5,6,12,7,8,9,10};
		int[] data = obe.recorderOddEven(arr, arr.length);
		System.out.println(Arrays.toString(data));
	}
}
  • 考慮可擴充套件的解法,能秒殺 Offer

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

面試官:如果把題目改成陣列中的數按照大小分為兩部分,所有的負數在所有的非負數的前面,該怎麼做?

如果再把題目改改,變成把陣列中的數分成兩部分,能被3整除的數都在不能被3整除的數的前面,怎麼辦?

這就是面試官在考察我們對可擴充套件性的理解,即希望我們能夠給出一個模式,在這個模式下能夠很方面第把已有的解決方案擴充套件到同類型的問題上去。

於是我們寫出下面可擴充套件性的程式碼:

public class OddBeforeEvenExtension {

	public int[] recorderOddEven(int[] data, int length){
		if(data == null && length <= 0){
			return null;
		}
		
		int begin = 0;
		int end = length - 1;
		
		// begin指標要位於end指標前面
		while(begin < end){
			// 向後移動begin指標,直到它指向偶數
			if(begin < end && !isEven(data[begin])){
				begin++;
			}
			
			// 向前移動end指標,直到它指向奇數
			if(begin < end && isEven(data[end])){
				end--;
			}
			
			// 交換奇數和偶數的位置
			if(begin < end){
				int temp = data[begin];
				data[begin] = data[end];
				data[end] = temp;
			}
		}
		return data;
	}
    // 擴充套件函式
	private boolean isEven(int data) {
		return (data & 0x1) == 0;
	}

	// 測試
	public static void main(String[] args) {
		OddBeforeEvenExtension obe = new OddBeforeEvenExtension();
		int[] arr= {1,2,3,4,5,6,12,7,8,9,10};
		int[] data = obe.recorderOddEven(arr, arr.length);
		System.out.println(Arrays.toString(data));
	}
}

這樣如果增加額外的判斷條件,只需要改變isEven函式中的邏輯即可,對於recorderOddEven函式中的主體邏輯框架無需改變,這樣就實現瞭解耦。


上面的案例中只需要保證奇數在偶數之前就可以了,但是可以看出來,上面的程式碼會改變原有的奇數和奇數、偶數和偶數之間的順序。那麼如果現在不能改變這個順序又要如何實現呢?

牛客網:https://www.nowcoder.com/questionTerminal/beb5aa231adc45b2a5dcc5b62c93f593

題目2:輸入一個整數陣列,實現一個函式來調整該陣列中數字的順序,使得所有的奇數位於陣列的前半部分,所有的偶數位於陣列的後半部分,並保證奇數和奇數,偶數和偶數之間的相對位置不變。

1、要想保證原有的次序,則只能順序移動或者相鄰交換;

2、i 從左到右開始遍歷,找到第一個偶數;

3、j 從 i+1 開始向後找,直到找到第一個奇數;

4、將 [ i , ... . j - 1 ] 的元素整體後移一位,最後將找到的奇數放入 i 位置,然後 i++;

5、終止條件,j 向後遍歷查詢失敗,即後面沒有奇數了。

/**
 * 輸入一個整數陣列,實現一個函式來調整該陣列中數字的順序,使得所有的奇數位於陣列的前半部分,
 * 所有的偶數位於陣列的後半部分,並保證奇數和奇數,偶數和偶數之間的相對位置不變。
 */
public class OddBeforeEven_Improved {

	public int[] reOrderArray(int[] arr) {
        if(arr == null && arr.length <= 0){
        	return null;
        }
        
        int i = 0, j;   
        while(i < arr.length){
        	while(i < arr.length && !isEven(arr[i])){
        		i++;
        	}
        	// i指向偶數時,j從i+1開始向後遍歷找奇數
        	j = i + 1;  
        	while(j < arr.length && isEven(arr[j])){
        		j++;
        	}
        	if(j < arr.length){
        		// 將[i,...,j-1]的元素整體後移1位,最後將找到的奇數放入i位置
        		int temp = arr[j];
        		for(int k = j - 1; k >= i; k--){
        			arr[k + 1] = arr[k];
        		}
        		arr[i++] = temp;
        	}else{
        		// 查詢失敗
        		break;
        	}
        }
        return arr;
    }

	// 判斷n是否是偶數
	private boolean isEven(int n) {
		if(n % 2 == 0){
			return true;
		}
		return false;
	}
	
	// 測試
	public static void main(String[] args) {
		OddBeforeEven_Improved obe = new OddBeforeEven_Improved();
		int[] arr= {1,2,3,4,5,6,12,7,8,9,10};
		int[] data = obe.reOrderArray(arr);
		System.out.println(Arrays.toString(data));
	}
}

上面這種解法每次發現一個奇數在偶數後面,就需要搬移他們之間所有的元素,時間複雜度為O(n^{2^{^{}}}),下面我們將使用一種空間換時間的思想,將時間複雜度降低為O(n)。具體做法如下:

首先統計奇數的個數,然後新建一個與原陣列等長的陣列,設定兩個指標,奇數指標從0開始,偶數指標從奇數個數的末尾開始遍歷填數。

public class OddBeforeEven_Improving {

	public int[] reOrderArray(int[] arr){
		if(arr.length == 0 || arr.length == 1){
			return arr;
		}
		
		int oddCount = 0, oddBegin = 0;
		int[] newArr = new int[arr.length];
		// 統計原陣列中奇數的個數
		for (int i = 0; i < arr.length; i++) {
			if((arr[i] & 1) == 1){
				oddCount++;
			}
		}
		
		for (int i = 0; i < arr.length; i++) {
			if((arr[i] & 1) == 1){
				// 在新陣列中,先放奇數,從頭開始放
				newArr[oddBegin++] = arr[i];
			}else{
				// 偶數從奇數總個數後開始放
				newArr[oddCount++] = arr[i];
			}
		}
		
		for (int i = 0; i < arr.length; i++) {
			arr[i] = newArr[i];
		}
		return arr;
	}
	

	// 測試
	public static void main(String[] args) {
		OddBeforeEven_Improving obe = new OddBeforeEven_Improving();
		int[] arr= {1,2,3,4,5,6,12,7,8,9,10};
		int[] data = obe.reOrderArray(arr);
		System.out.println(Arrays.toString(data));
	}
}