题目:
给定数组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;
}