題目:
給定數組arr,返回arr的最長遞增子序列。
舉例:
arr=[2,1,5,3,6,4,8,9,7],返回的最長遞增子序列爲[1,3,4,9,8]。
思路:
採取兩層循環來遍歷數組arr,第一層循環遍歷完一個數之後,在第二層循環中以第一層循環中的這個數爲第一個數向左進行循環尋找比第一層循環中小的數,並取出這個數的長度並加一。這裏需要用到一個判斷公式:
dp[i] = max(dp[i],dp[j]+1);
數組dp存儲的是數組arr中每一個數作爲結尾的最長遞增子序列的長度。之後依靠着dp數組尋找最長遞增子序列。
解題代碼:
/**
* 最長遞增子序列的求解方式
* @author Administrator
*
*/
public class problem1 {
/**
* 用於返回最長遞增子序列
* @param arr 求解的目標數組
* @return
*/
public ArrayList<Integer> theLongest(int [] arr){
ArrayList<Integer>list = new ArrayList<Integer>();
int [] dp = this.dpValue(arr);
int max = 0,index = 0;
for(int i=0;i<dp.length;i++){
if(dp[i]>max){
max = dp[i];
index = i;
}
}
list.add(arr[index]);
max--;
//查找最長遞增子序列
for(int i=index-1;i>=0;i--){
if(dp[i] == max){
list.add(arr[i]);
max--;
}
}
//翻轉list數組得到結果
Collections.reverse(list);
return list;
}
/**
* 用於計算數組中以每個數字結尾的最長長度
* @param arr 目標數組
* @return
*/
public int [] dpValue(int [] arr){
//定義數組用於儲存以每個數作爲結尾的最長長度
int [] dp = new int [arr.length];
for(int i=0;i<dp.length;i++){
dp[i] = 1;
for(int j=i;j>=0;j--){
if(arr[i]>arr[j]){
dp[i] = Math.max(dp[i], dp[j]+1);
}
}
}
return dp;
}
}
這裏有一個問題,此種解法最終得到的時間複雜度是O(N^2),有沒有什麼辦法可以將其的時間複雜度降下來,二分查找是一個好的解決辦法可以將它的時間複雜度降到O(NlogN).但是這裏需要重新開一個數組,也就是犧牲空間來換取時間上的優勢。以下是此種思路的代碼:
/**
* 用於計算數組中以每個數字結尾的最長長度
* @param arr 目標數組
* @return
*/
public int [] dpValue(int [] arr){
//定義數組用於儲存以每個數作爲結尾的最長長度
int [] dp = new int [arr.length];
int [] ends = new int [arr.length];
int right = 0;
int r = 0;
dp[0] = 1;
ends[0] = arr[0];
for(int i=1;i<dp.length;i++){
r = right;
int index = this.location(ends, 0, r, arr[i]);
right = Math.max(right, index);
ends[index] = arr[i];
dp[i] = index+1;
}
return dp;
}
/**
* 二分查找法
* @param arr
* @param left
* @param right
* @param num
* @return
*/
public int location(int [] arr,int left,int right,int num){
int l = left;
int r = right;
int mid = 0;
while(l<=r){
mid = (l+r)/2;
if(arr[mid] == num){
return mid;
}
if(arr[mid]>num){
r = mid-1;
}else{
l = mid+1;
}
}
return l;
}