最长上升子序列问题
描述: 给定一个长度为 n 的数组 arr,求它的最长严格上升子序列的长度。 所谓子序列,指一个数组删掉一些数(也可以不删)之后,形成的新数组。例如 [1,5,3,7,3] 数组,其子序列有:[1,3,3]、[7] 等。但 [1,6]、[1,3,5] 则不是它的子序列。 输入描述: 第一行输入一个正整数 n ,表示数组的长度 第二行输入 n 个整数表示数组的每个元素。 输出描述: 输出最长严格上升子序列的长度
LIS问题有三种解法:
解法一: 可以先将序列从小到大排序,然后求出排序后的序列和原来的序列的LCS长度就是最长上升子序列(LIS)长度 需要注意的是: 这种解法只能用于序列中没有重复元素的情况,因为要求子序列是严格上升的 解法二: step 1:用dp[i]表示到元素i结尾时,最长的子序列的长度,初始化为1,因为只有数组有元素,至少有一个算是递增。 step 2:第一层遍历数组每个位置,得到n个长度的子数组。 step 3:第二层遍历相应子数组求对应到元素i结尾时的最长递增序列长度,期间维护最大值。 step 4:对于每一个到i结尾的子数组,如果遍历过程中遇到元素j小于结尾元素,说明以该元素结尾的子序列加上子数组末尾元素也是严格递增的,因此转移方程为dp[i]=dp[j]+1. 前面两种解法的时间复杂度都一样,都是O(n^2),第三种解法的效率更高 解法三: 步骤(具体实现见代码注释): 1、准备一个辅助数组temp[]统计最长递增子序列的长度,逐个处理原来数组arr中的数字,例如处理的是arr[k] 2、如果arr[k]>temp[temp.length()-1],就将arr[k]加入到temp数组的末尾 3、如果arr[k]<temp[temp.length()-1],就替换temp[]数组中第一个大于arr[k]的数字。 该方法的时间复杂度为O(nlog2n)
代码实现:
import java.util.*;
public class Main {
static int[] sortBefore; //记录原来的数组
public static void main(String[] args) {
Scanner scanner = new Scanner(System.in);
int n = scanner.nextInt();
int[] arr = new int[n];
for (int i = 0; i < n; i++) {
arr[i] = scanner.nextInt();
}
//保存原来的数组
sortBefore = Arrays.copyOf(arr,arr.length);
System.out.println("解法一的结果:" + firstSolve(arr));
System.out.println("解法二的结果:" + secondSolve(sortBefore));
System.out.println("解法三的结果:" + thirdSolve(sortBefore));
}
//解法一,这种解法只能用于序列中没有重复元素的情况,因为要求子序列是严格上升的
public static int firstSolve(int[] arr){
//给原来的数组排序
Arrays.sort(arr);
//求出两个数组的最长公共子序列的长度
return LCS(sortBefore,arr);
}
//求最长公共子序列
private static int LCS(int[] sortBefore, int[] arr) {
int[][] dp = new int[arr.length+1][arr.length+1];
for (int i = 1; i <= arr.length; i++) {
for (int j = 1; j <= sortBefore.length; j++) {
if (arr[i-1] == sortBefore[j-1])
dp[i][j] = dp[i-1][j-1] + 1;
else
dp[i][j] = Math.max(dp[i-1][j],dp[i][j-1]);
}
}
return dp[arr.length][arr.length];
}
//解法二
public static int secondSolve(int[] arr){
//dp[i]表示长度为i的序列中构成的最长上升子序列
int[] dp = new int[arr.length];
//设置数组长度大小的动态规划辅助数组
Arrays.fill(dp, 1);
int res = 0;
for(int i = 1; i < arr.length; i++){
for(int j = 0; j < i; j++){
//可能j不是所需要的最大的,因此需要dp[i] < dp[j] + 1
if(arr[i] > arr[j] && dp[i] < dp[j] + 1){
//i点比j点大,理论上dp要加1
dp[i] = dp[j] + 1;
//找到最大长度
res = Math.max(res, dp[i]);
}
}
}
return res;
}
//解法三
public static int thirdSolve(int[] arr){
//定义一个辅助数组
int[] temp = new int[arr.length];
//记录最终结果
int len = 0;
//将辅助数组中的值设为无穷小,方便判断
Arrays.fill(temp,Integer.MIN_VALUE);
//先将原来数组中的第一个元素放进temp中
temp[0] = arr[0];
for (int i = 1; i < arr.length; i++) {
//找出temp中最后一个元素,并记录
int k ,j;
for (j = 0; j < temp.length; j++) {
if (temp[j] == Integer.MIN_VALUE)
break;
}
//k此时记录的是temp中最后一个元素的下标(不包括无穷小的元素)
k = j - 1;
if (arr[i] > temp[k]){
temp[k+1] = arr[i];
}else {
//找出temp数组中第一个大于arr[i]的值并替换他
//只需要循环k+1次即可
for (int m = 0; m < k + 1; m++) {
if (temp[m] > arr[i]){
temp[m] = arr[i];
break;
}
}
}
}
//统计最长上升子序列长度
for (int i = 0; i < temp.length; i++) {
if (temp[i] == Integer.MIN_VALUE)
break;
len++;
}
//返回结果
return len;
}
}
不了解LCS问题的码友可以看看我上一篇文章