1. 程式人生 > >在一個亂序的陣列中找到最長的遞增子序列

在一個亂序的陣列中找到最長的遞增子序列

這個題是牛客上左程雲講其他俄國沙皇問題是提及到的一個演算法原型。程式碼中給出了兩個思路。時間複雜度分別為 N^2 和 NlogN,原始碼中沒有給出註釋,自己照著思路又重新捋了遍程式,稍微加了點註釋,方便理解。

原題目的講解在牛客網公開課http://www.nowcoder.com/live/11/1/1

以下是程式碼

public class Problem_05_LIS {

	public static int[] lis1(int[] arr) {
		if (arr == null || arr.length == 0) {
			return null;
		}
		int[] dp = getdp1(arr);
		return generateLIS(arr, dp);
	}
//dp中放的是,對應陣列中每個元素為輸出序列的最後一個值時的最大長度
//方法一:O(n^2)複雜度,具體思路是:兩層迴圈,外層為i時,內從從0到i,依次判斷,當加入 arr[i]的值後,更新dp中最長子序列的長度,最長
//子序列更新,依賴於i前面的dp中的陣列值,若是arr[i]比i之前的某個位置上的的數要大,則通過max(dp[i], dp[j] + 1);來
//更新第i位上的值。
	public static int[] getdp1(int[] arr) {
		int[] dp = new int[arr.length];
		for (int i = 0; i < arr.length; i++) {
			dp[i] = 1;
			for (int j = 0; j < i; j++) {
				if (arr[i] > arr[j]) {
					dp[i] = Math.max(dp[i], dp[j] + 1);
				}
			}
		}
		return dp;
	}

	//根據 dp 陣列的值生成要列印的陣列,作為最後結果的輸出,
	public static int[] generateLIS(int[] arr, int[] dp) {
		int len = 0;
		int index = 0;
		for (int i = 0; i < dp.length; i++) {
			if (dp[i] > len) {
				len = dp[i];
				index = i;
			}
		}
		int[] lis = new int[len];
		lis[--len] = arr[index];
		for (int i = index; i >= 0; i--) {
			if (arr[i] < arr[index] && dp[i] == dp[index] - 1) {
				lis[--len] = arr[i];
				index = i;
			}
		}
		return lis;
	}

	public static int[] lis2(int[] arr) {
		if (arr == null || arr.length == 0) {
			return null;
		}
		int[] dp = getdp2(arr);
		return generateLIS(arr, dp);
	}

//方法二,時間複雜度為O(N*logN),這個時間複雜度具體體現在一個for迴圈裡套了一個二分查詢
//dp中放的是,對應陣列中每個元素為輸出序列的最後一個值時的最大長度
//ends陣列作為一個輔助陣列,存放的是有效區的數值,ends[i]的含義是:
//當遍歷到當前的數時,ends[i]表示的是,遍歷到當前時刻,長度為i+1的子序列中的末尾元素值
//每次更新的是長度為i+1子序列的末尾元素的值,那麼,可以通過二分法查詢每個當前遍歷到的元素在ends中應該所處的位置
//然後更新對應的位置上的元素值,然後根據i+1,就可以知道,當前元素作為子序列的末尾元素時,前面有幾個數了(i)
//程式中的right值記錄的是,有效區陣列ends的長度,l為左邊界,r為右邊界,m為中間值
	public static int[] getdp2(int[] arr) {
		int[] dp = new int[arr.length];
		int[] ends = new int[arr.length];
		ends[0] = arr[0];
		dp[0] = 1;
		int right = 0;
		int l = 0;
		int r = 0;
		int m = 0;
		for (int i = 1; i < arr.length; i++) {
			l = 0;
			r = right;
			while (l <= r) {
				m = (l + r) / 2;
				if (arr[i] > ends[m]) {
					l = m + 1;
				} else {
					r = m - 1;
				}
			}
			right = Math.max(right, l);  //更新有效區陣列的邊界值,若是所有的元素都小於當前的元素,則陣列值往外擴充一個
			ends[l] = arr[i];
			dp[i] = l + 1;
		}
		return dp;
	}

	// for test
	public static void printArray(int[] arr) {
		for (int i = 0; i != arr.length; i++) {
			System.out.print(arr[i] + " ");
		}
		System.out.println();
	}

	public static void main(String[] args) {
		int[] arr = { 2, 1, 5, 3, 6, 4, 8, 9, 7 };
		printArray(arr);
		printArray(lis1(arr));
		printArray(lis2(arr));

	}
}