最长上升子序列
输入:
10
1 4 5 1 4 1 9 1 9
输出:
4
算法思想:记录每个数为结尾的最长子序列长度,作为dp数组。
eg:(这个是以每个位置开头的记录最长的来穷举但核心仍为上述的算法思想)
初始序列为
穷举遍历所有组合
保留绿色部分(3,1,12,124,1246)以3,1,2,4,6结尾的最长子序列
落实到代码:
import java.util.*;
public class LIS {
public static void main(String []args) {
Scanner a = new Scanner(System.in);
int n=a.nextInt();
int []b = new int [n+1];
int []f = new int [n+1];//开一个数组存以第i位置数字结尾的最长子序列
//输入每个数并且将每个f[i]设置为1即本身
for(int i =1;i<n+1;i++) {
b[i]=a.nextInt();
f[i]=1;
}
for(int i =1;i<n+1;i++) {
for(int j=1;j<=i;j++) {
//如果第j位置的数小于第i位置的可以在其基础上+1和本身比较更新dp
if(b[j]<b[i]) {
f[i]=Math.max(f[j]+1, f[i]);
}
}
}
int ans=0;
//遍历一遍数组f找到最大的子序列长度
for(int i =1;i<n+1;i++) {
//System.out.print(f[i]+",");
ans = Math.max(ans, f[i]);
}
System.out.println(ans);
}
}
算法复杂度为O(n^2)两重for循环
优化:
因为核心就是要最后一个数越小越好且保持长度最长,这样后面方便延长长度(贪心思想)
算法思路保存长度为i,f[i]为最小的数的dp方便后面可以更新,由于序列有序固用二分查找去更新
eg:
10
3 1 2 4 6 5 3 4 5 6
前五个数遍历完
f为1 2 4 6
到第六个数(5)后会更新为
f = 1 2 4 5
再到第其七个数(3)后会更新为
f = 1 2 3 5
再第8(4)就是
f = 1 2 3 4
最后就是直接插入5 6
f = 1 2 3 4 5 6
ps:该核心主要在于只存储长度为i的序列的最小数字在f[i]中为dp状态方便后面可以更新,因为都是从左往右遍历固f是按从小到大顺序排放,后面数大于f最末尾可以直接插入,否则可以二分找到对应的pos位置看看能不能更新dp,len数组f长度即为最长的序列:
import java.util.Scanner;
public class LISplus {
public static void main(String []args) {
Scanner a = new Scanner(System.in);
int n=a.nextInt();
int []b = new int [n+1];
int []f = new int [n+1];//开一个数组存目前结尾最小的长度为i的序列
f[0]=0;//因为输入的数都为正整数所以设置初始数组0号位置为0最合适不过
int len=0;
for(int i =1;i<n+1;i++) {
b[i] = a.nextInt();
//如果当前输入的数大于最优序列的末尾的数说明可以直接插入
if(b[i]>f[len]) {
len++;
f[len]=b[i];
}
//否则需要二分从中间开始找能不能有更优的插入位置
else {
int l=1,r=len;
int mid,pos=0;//因为pos必不为0所以先设置一个默认值防止报错
//二分当左边小于右边时就分
while(l<=r) {
mid =(l+r)/2;
if(b[i]>f[mid]) {
l=mid+1;
}else {
r=mid-1;//则半或者使l>r结束
pos = mid;//该位置为可能可以替换的位置
}
}
f[pos]=Math.min(f[pos], b[i]);
}
}
System.out.println(len);
}
}
再优化就是其实不需要存储输入的数组比较完就丢,只需要dp状态数组
import java.util.Scanner;
public class LISplus {
public static void main(String []args) {
Scanner a = new Scanner(System.in);
int n=a.nextInt();
//int []b = new int [n+1];
int x;
int []f = new int [n+1];//开一个数组存目前结尾最小的长度为i的序列
f[0]=0;//因为输入的数都为正整数所以设置初始数组0号位置为0最合适不过
int len=0;
for(int i =1;i<n+1;i++) {
x= a.nextInt();
//如果当前输入的数大于最优序列的末尾的数说明可以直接插入
if(x>f[len]) {
len++;
f[len]=x;
}
//否则需要二分从中间开始找能不能有更优的插入位置
else {
int l=1,r=len;
int mid,pos=0;//因为pos必不为0所以先设置一个默认值防止报错
//二分当左边小于右边时就分
while(l<=r) {
mid =(l+r)/2;
if(x>f[mid]) {
l=mid+1;
}else {
r=mid-1;//则半或者使l>r结束
pos = mid;//该位置为可能可以替换的位置
}
}
f[pos]=Math.min(f[pos], x);
}
}
System.out.println(len);
}
}
错误点:
每次编译运行run或者debug找不到源程序
一定要检查一下编辑版有没有问题
这里面漏其中一部分虽然不会报错但因为没有编辑版原因就会有上面情况(一直蠢了不知道这个才发现的)