1. 程式人生 > 其它 >劍指 offer程式碼解析——面試題38數字在排序陣列中出現的次數

劍指 offer程式碼解析——面試題38數字在排序陣列中出現的次數

題目:統計一個有序陣列中K出現的次數。

分析:本題最直觀的思路就是遍歷陣列,統計K出現的次數即可。

這種方式的時間複雜度為O(n)。下面我們充分利用“有序陣列”這一條件,提高演算法的時間效率。

對於一個有序陣列,所有的數字K一定都集中在一起,因此只要我們找到這一組K的頭和尾就能知道K出現的次數。

此時問題就轉化為:在一個有序陣列中尋找某個數字。

我們很自然地就想到了二分搜尋,在目前所有的搜尋演算法中,二分搜尋具有最高的搜尋效率。

對於本題,我們需要進行兩次二分搜尋,一次尋找K的頭,一次尋找K的尾。

二分搜尋K頭部的過程如下:

1.確定當前陣列的中點;

2.若中點<K,則K的起點在後半段;

3.若中點>K,則K的起點在前半段;

4.若中點=K,則判斷中點的前一個點是否等於K;

  4.1.若等於K,則K的起點在前半段;

  4.2.若不等於K,則該中點即為K的起點。

尋找K的終點與尋找起點類似。本演算法的具體程式碼如下:

/**
 * 題目:統計一個有序陣列中K出現的次數。
 * @author 大閒人柴毛毛
 * @date 2016年3月25日
 */
public class CountKNumber {
	
	/**
	 * 分析:本題最直觀的思路就是遍歷陣列,統計K出現的次數即可。
	 * 這種方式的時間複雜度為O(n)。下面我們充分利用“有序陣列”這一條件,提高演算法的時間效率。
	 * 
	 * 對於一個有序陣列,所有的數字K一定都集中在一起,因此只要我們找到這一組K的頭和尾就能知道K出現的次數。
	 * 此時問題就轉化為:在一個有序陣列中尋找某個數字。
	 * 我們很自然地就想到了二分搜尋,在目前所有的搜尋演算法中,二分搜尋具有最高的搜尋效率。
	 * 對於本題,我們需要進行兩次二分搜尋,一次尋找K的頭,一次尋找K的尾。
	 * 二分搜尋K頭部的過程如下:
	 * 1.確定當前陣列的中點;
	 * 2.若中點<K,則K的起點在後半段;
	 * 3.若中點>K,則K的起點在前半段;
	 * 4.若中點=K,則判斷中點的前一個點是否等於K;
	 * 	4.1.若等於K,則K的起點在前半段;
	 * 	4.2.若不等於K,則該中點即為K的起點。
	 * 尋找K的終點與尋找起點類似。
	 * 
	 * 本演算法的具體程式碼如下:
	 */
	
	/**
	 * 獲取陣列中K出現的個數
	 * @param a 陣列
	 * @param k
	 * @return 返回K出現的個數(若為-1表示獲取失敗)
	 */
	public static int getKNumber(int[] a,int k){
		//健壯性判斷:若陣列為空
		if(a==null || a.length<=0){
			System.out.println("陣列為空!");
			return -1;
		}
		
		//子陣列起點的下標
		int start = 0;
		//子陣列終點的下標
		int end = a.length-1;
		
		//K起點的下標
		int k_start = -1;
		//K終點的下標
		int k_end = -1;
		
		//當子陣列的長度大於0的時候一直迴圈,獲取k的起點座標
		while(end-start >= 0){
			//計算中點下標
			int mid = (start+end)/2;
			
			//若a[mid]>k,則k的起點在前半段
			if(a[mid]>k){
				end = mid-1;
			}
			//若a[mid]<k,則k的起點在後半段
			else if(a[mid]<k){
				start = mid+1;
			}
			//若a[mid]=k
			else{
				//若a[mid-1]==k,則說明a[mid]不是k的起點
				if(a[mid-1]==k){
					end = mid-1;
				}
				//若a[mid-1]!=k,則說明a[mid]是k的起點
				else{
					k_start = mid;
					break;
				}
			}
		}
		
		
		//將start、end指向陣列的頭和尾
		start = 0;
		end = a.length-1;
		//當子陣列的長度大於0的時候一直迴圈,獲取k的終點座標
		while(end-start >= 0){
			//計算中點下標
			int mid = (start+end)/2;
			
			//若a[mid]>k,則k的起點在後半段
			if(a[mid]>k){
				end = mid-1;
			}
			//若a[mid]<k,則k的起點在前半段
			else if(a[mid]<k){
				start = mid+1;
			}
			//若a[mid]=k
			else{
				//若a[mid+1]==k,則說明a[mid]不是k的終點
				if(a[mid+1]==k){
					start = mid+1;
				}
				//若a[mid+1]!=k,則說明a[mid]是k的終點
				else{
					k_end = mid;
					break;
				}
			}
		}
		
		//若未找到k的起點或終點
		if(k_start==-1 || k_end==-1)
			return 0;
		
		//統計k的個數
		return k_end-k_start+1;
	}
	
	
	
	/**
	 * 測試
	 */
	public static void main(String[] args){
		//構建陣列
		int[] a = {0,1,2,3,4,6,7,7,7,7,7,7,8,9};
		//統計k的個數
		System.out.println(getKNumber(a,7));
	}
}