目录
问题描述:
给定一个序列,如:arr=[10, 9, 2, 5, 3, 7, 101, 18],求该序列的最长递增子序列。
最长递增子序列:longest increasing subsequence(LIS),可以用n^2复杂度的动态规划求解,也可以用nlongn复杂度的贪心加二分求解。
动态规划:(n^2)
设dp[i]表示为arr[i]结尾的最大长度,则开始时dp数组中元素值都为1(自身长度),以上述数组为例:
java代码:
public class Main {
public static void main(String[] args) {
int []arr = new int[] {10, 9, 2, 5, 3, 7, 101, 18};
int []dp = new int[arr.length];
for(int i = 0; i < arr.length; i++) {//遍历arr中元素
dp[i] = 1;//dp数组初始化为1,即本身长度为1
for(int j = 0; j < i; j++) {//遍历dp[i]之前元素
if(arr[j] < arr[i]) {//小于当前元素
dp[i] = Math.max(dp[i], dp[j] + 1);//比较
}
}
}
int max = Integer.MIN_VALUE;//找dp中所存放的最大值
for(int i = 0; i < arr.length; i++) {
max = Math.max(max, dp[i]);
}
System.out.println(max);
}
}
贪心+二分:(nlongn)
对于一个上升的子序列,结尾值的元素越小,越有利于后续在其后添加新的元素,使得长度最长。
以arr=[10, 9, 2, 5, 3, 7, 101, 18]为例,设dp数组中元素值为最长的序列,以下是dp的动态变化过程:
java代码:
import java.util.Arrays;
public class Main {
public static void main(String[] args) {
int []arr = new int[] {10, 9, 2, 5, 3, 7, 101, 18};
int []dp = new int[arr.length];
int len = 0;//二分查找的最右端(卡范围),dp中“有意义”的元素个数
for(int i = 0; i < arr.length; i++) {
int index = Arrays.binarySearch(dp, 0, len, arr[i]);
if(index < 0) {
index = -(index + 1);//将index改为期待位置
}
dp[index] = arr[i];//更新或追加
if(index == len) {
len++;
}
}
System.out.println(len);
}
}
binnarySearch()的使用:
在二分查找时调用了工具类Arrays的binnarySearch(),在使用该方法查找时,找到待查找的元素,则返回找到的索引;如果找不到,则返回一个负数,假设排好序时,不存在的元素应该位于索引r处,则 -(r + 1) 便是返回的负数值。
例如:
public class BinarySeach {
public static void main(String[] args) {
int[] src = { 1, 2, 4, 5, 7, 8, 12 };
System.out.print(Arrays.binarySearch(src, 10)); //-7
}
}
10在src数组中不存在,则返回一个负数,将10放入有序数组src时应该位于索引6处,所以返回的负数值为 -(6 + 1) ,即返回-7。