演算法知識梳理(4) 陣列第一部分

澤毛發表於2017-12-21

一、概要

本文介紹了有關陣列的演算法第一部分的Java程式碼實現,所有程式碼均可通過 線上編譯器 直接執行,演算法目錄:

  • 二維陣列的整數查詢
  • 旋轉陣列中的最小數字(旋轉陣列中的最大數字)
  • 調整陣列使奇數位於偶數之前
  • 找出陣列中出現次數超過一半的數字

二、程式碼實現

2.1 二維陣列的整數查詢

問題描述

在一個二維陣列中,每一行都按照 從左到右遞增的順序,每一列都按照 從上到下遞增的順序 排序,編寫一個函式,輸入這樣的一個二維陣列和一個整數,判斷該整數是否在二位陣列中。

解決思路

首先要確定整數存在於陣列的一個 前提條件:如果最小的元素(左上角)大於d,或者最大的元素(右下角)小於d,那麼可以確定矩陣中不存在d

這裡需要關注一個特殊的點,二維陣列右上角的元素d,如果以d所在的列將陣列分為左右兩個部分,那麼 右邊部分的所有元素都是大於d 的;而如果以d所在的行將陣列分為上下兩個部分,那麼 上半部分的所有元素都是小於d 的。利用這一特性,我們 從陣列的右上角 開始搜尋:

  • 判斷陣列右上角元素是否就是查詢的值
  • 如果不是,先按x軸的反方向遍歷,從 右至左找到第一個不大於d 的元素。
  • 之後再按y軸的正方向遍歷,從 上至下找到第一個不小於d 的元素
  • 經過以上兩步,會得到一個縮小後的矩陣,檢查前提條件是否仍然滿足,如果不滿足,那麼就說明p不存在於陣列中,反之則從第一步開始重複。

實現程式碼

class Untitled {

	static Boolean searchMisInt(int p[][], int d, int maxx, int maxy){
		int minx = 0; int miny = 0;
		while(minx <= maxx && miny <= maxy){
			//如果最小的元素(左上角)大於d,或者最大的元素(右下角)小於d,那麼可以確定矩陣中不存在p。
			if (p[minx][miny] > d|| p[maxx][maxy] < d)
				return false;
			//如果右上角的元素和p直接相等,那麼直接返回。
			if (d == p[minx][maxy]) {
				return true;
			}
			//不斷縮小矩形的範圍。
			while (p[minx][maxy] > d) //從右上角的第一個元素,從右往左,找到第一個不大於d的元素。
				maxy -= 1;
			while (p[minx][maxy] < d) //從右上角的第一個元素,從上到下,找到第一個不小於d的元素。
				minx += 1;
		}
		return false;
	}
	public static void main(String[] args) {
		int p[][] = { {1, 2, 8, 9}, {2, 4, 9, 12}, {4, 7, 10, 13}, {6, 8, 11, 15}};
		System.out.println("result=" + searchMisInt(p, 10, 3, 3));
	}
}
複製程式碼

執行結果:

>> result=true
複製程式碼

2.2 獲得旋轉陣列中的最小數字

問題描述

把一個陣列最開始的若干個元素搬到陣列的末尾,稱為陣列的旋轉。現在輸入一個遞增序列的旋轉陣列,輸出旋轉陣列的最小值,例如{1, 2, 3, 4, 5}的一個旋轉陣列為{3, 4, 5, 1, 2},最小值為1

解決思路

加入經過旋轉後的陣列不等於它本身,那麼這個旋轉後的陣列有一個特點:它以最小值作為分界點被分為兩個部分,不包含最小值的前半部分是有序的,包含最小值的後半部分也是有序的,因此我們可以藉助 二分查詢 的思想。

首先獲得當前陣列的中點位置pMid

  • 如果pMid的數值 大於等於 首節點pStart的數值,那麼說明 pMid 位於前半部分陣列中,因此可以確定最小值在[pMid, pEnd]之間
  • 如果pMid的數值 小於等於 末節點pEnd的數值,那麼說明 pMid 位於後半陣列當中,而pMid[pMid,pEnd]之間最小的元素,因此可以確定最小值在[pStart, pMid]之間。
  • 通過以上兩步驟,不斷縮小陣列的範圍,直到縮小後的陣列只有兩個元素為止,第二個元素的值就是我們要找的最小值。

實現程式碼

class Untitled {

	static int rotateMinIndex(int p[], int length){
		if (length == 1) {
			return 0;
		}
		
		int pStart = 0;
		int pMid = 0;
		int pEnd = length-1;
		
		if (p[pStart] < p[pEnd]) {
			return pStart;
		}
		while (pStart < pEnd - 1) {
			pMid = (pStart + pEnd) >> 1;
			if (p[pStart] == p[pMid] && p[pMid] == p[pEnd])
				return rotateMinIndexOrder(p, pStart, pEnd);
			if (p[pMid] >= p[pStart]) //左邊陣列是有序的,最小點在右邊
				pStart = pMid;
			else if(p[pMid] <= p[pEnd]) //右邊陣列是有序的,最小點在左邊
				pEnd = pMid;
		}
		
		if (p[pStart] > p[pEnd])
			return p[pEnd];
		
		return -1;
	}

	static int rotateMinIndexOrder(int p[], int pStart, int pEnd){
		for(int i = pStart; i < pEnd; i++){
			if (p[i+1] < p[i])
				return i+1;
		}
		return pEnd;
	}

	public static void main(String[] args) {
		int p[] = {3, 4, 5, 1, 2};
		System.out.println("result=" + rotateMinIndex(p, 5));
	}
}
複製程式碼

執行結果

>> result=1
複製程式碼

2.3 調整陣列使奇數位於偶數之前

問題描述

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

解答思路

通過兩個變數pStartpEnd分別記錄從首節點和末節點開始掃描的下標,pStart從首節點向末節點開始掃描,如果找到一個偶數,那麼停止掃描,讓pEnd從末節點向首節點開始掃描,直到找到一個奇數,然後和pStart所指向的偶數交換,這樣就能保證[0, pStart]之間的都是奇數,而[pEnd, len-1]之間的都是偶數。

完成交換後,再開始移動pStart尋找下一個偶數,重複上面的操作,直到pStartpEnd相遇,就可以滿足問題的要求了。

實現程式碼

class Untitled {
	
	static void printArray(int p[]) {
		for (int i = 0; i < p.length; i++) {
			System.out.print(p[i] + ",");
		}
	}

	static void reverseOddEven(int p[], int length){
		int i = 0;
		int j = length-1;
		int t;
		while (true) {
			/*i指向陣列length,或指向一個偶數*/
			while (i < length && p[i] % 2 != 0)
				i++;
			/*j指向-1,j指向一個奇數,j的值小於i*/
			while (j >= 0 && (p[j] % 2 == 0) && j >= i)
				j--;
			if (i > j)
				break;
			t = p[i]; p[i] = p[j]; p[j] = t;
		}
	}

	public static void main(String[] args) {
		int p[] = {3, 4, 5, 1, 2};
		reverseOddEven(p, p.length);
		printArray(p);
	}
}
複製程式碼

執行結果:

>> 3,1,5,4,2,
複製程式碼

2.4 找出陣列中出現次數超過一半的數字

問題描述

找出陣列中出現次數超過一半的數字

解決思路

將整個陣列中的元素看成兩類:出現次數超過一半的數字其餘數字。利用一個輔助的變數timed,初始時候將d設為p[0]time設為1,開始從頭開始遍歷陣列。

實現程式碼

class Untitled {

	static Boolean verifyMoreThanHalf(int p[], int data, int length){
		int time = 0;
		for (int i = 0; i < length; i++) {
			if(p[i] == data)
				time++;
		}
		if ((time << 1) >= length)
			return true;
		return false;
	}

	static void moreThanHalf(int p[], int length) {
		//初始化
		int d = p[0];
		int time = 1;
		//從第二個元素開始遍歷。
		for (int i = 1; i < length; i++) {
			if(time == 0){
				d = p[i];
				time = 1;
			}else if(d == p[i])
				time += 1;
			else
				time -= 1;
		}
		if (verifyMoreThanHalf(p, d, length)) {
			System.out.println("出現次數超過一半的元素=" + d);
		} else {
			System.out.println("沒有次數超過一半的元素");
		}
		
	} 
	public static void main(String[] args) {
		int p[] = {1, 2, 3, 2, 2, -1, 2, 2, 8, 2, 2};
		moreThanHalf(p, p.length);
	}
}
複製程式碼

執行結果

>> 出現次數超過一半的元素=2
複製程式碼

更多文章,歡迎訪問我的 Android 知識梳理系列:

相關文章