对于一个数字序列,请设计一个复杂度为O(nlogn)的算法,返回该序列的最长上升子序列的长度,这里的子序列定义为这样一个序列U1,U2…,其中Ui < Ui+1,且A[Ui] < A[Ui+1]。
给定一个数字序列A及序列的长度n,请返回最长上升子序列的长度。
测试样例:
[2,1,4,3,1,5,6],7
返回:4
package alex.suda.dp;
import java.util.Scanner;
public class test1 {
public static void main(String[] args) {
// TODO Auto-generated method stub
Scanner scanner = new Scanner(System.in);
while (scanner.hasNext()) {
int n = scanner.nextInt();
int[] A = new int[n];
for (int i = 0; i < n; i++) {
A[i] = scanner.nextInt();
}
System.out.print(findLongest1(A, n));
}
}
//方法1 o(n*n)
public static int findLongest(int[] A, int n) {
// 以[2,1,4,3,1,5,6]为例。使用i来表示当前遍历的位置:
// i = 1 时,最长递增序列为{1} ,序列长度为1
// i = 2时,1 < 2 所以得丢弃2 重新建立最长递增序列{1},序列长度为1
// i = 3时,4>2,4>1 最长递增序列为{2,4} {1,4},长度为2
// 以此类推,可得:d[i+1] = max{1,d[k]+1},其中对于所有的k<= i ,A[i+1] > A[k]
// d[i] 表示A数组的前i个元素当中,最长递增序列的长度
int[] d = new int[n];
for (int i = 0; i < n; i++) {
d[i] = 1; // 初始化默认长度
for (int j = 0; j < i; j++) { // 前面最长的序列
if (A[i] > A[j] && d[j] + 1 > d[i]) { // 增加了当前的数之后,大于原来的长度,就更新
d[i] = d[j] + 1;
}
}
}
int maxDis = Integer.MIN_VALUE;
for (int i = 0; i < n; i++) {
if (d[i] > maxDis) {
maxDis = d[i];
}
}
return maxDis;
}
// 方法2:O(nlogn) 采用两个数组d[],maxV[]。其中d[i]表示必须以A[i]结尾的最长递增子序列的长度,
// maxV[i]表示递增子序列长度为i+1的所有序列中结尾最小的值.d[0]=1,maxV[0]=A[0]
// maxV[]是排好序的,所以在更新maxV[]时查找A[i]的位置时采用二分搜索
public static int findLongest1(int[] A, int n) {
int[] d = new int[n];
int[] maxV = new int[n];
d[0] = 1;
maxV[0] = A[0];
int maxDis = 0;
int count = 0;
for (int i = 1; i < n; i++) {
// 二分法查找A[i]在maxV[]序列中的所处的位置。
int right = count;
int left = 0;
int mid;
while (left <= right) {
mid = (right + left) / 2;
if (A[i] >= maxV[mid]) {
left = mid + 1;
} else {
right = mid - 1;
}
}
// 更新maxV[]的值
// ,表示递增子序列长度为left+1的所有序列中结尾最小的值变成了A[i](因为A[i]<maxV[left])
maxV[left] = A[i];
if (left > count) {
count = left;
}
d[i] = left + 1;
if (d[i] > maxDis) {
maxDis = d[i];
}
}
return maxDis;
}
}