题目链接https://www.acwing.com/problem/content/897/
image.png

一、求最长上升子序列长度

1、朴素版本 O(n2)

f[i]可以看作以num[i]为末尾的最长子序列的长度集合,现在要求集合中的最大值。f[i]的分类是由此序列的倒数第二个数num[j]来划分的,f[i]可以用f[j]+1来表示,此处j是这个上升子序列的倒数第二个值,用循环来求

  1. import java.util.Scanner;
  2. public class Main {
  3. public static void main(String[] args) {
  4. Scanner in = new Scanner(System.in);
  5. int n = in.nextInt();
  6. int[] num = new int[1005];
  7. int[] f = new int[1005];
  8. for(int i = 1; i <= n; i++) num[i] = in.nextInt();
  9. for(int i = 1; i <= n; i++) {
  10. f[i] = 1;
  11. for(int j = 1; j <= n; j++) {
  12. if(num[i] > num[j]) {
  13. f[i] = Math.max(f[i],f[j] + 1);
  14. }
  15. }
  16. }
  17. int res = 0;
  18. for(int i = 1; i<= n;i++) {
  19. res = Math.max(f[i],res);
  20. }
  21. System.out.print(res);
  22. }
  23. }

2、优化版本O(nlogn)

这个版本比起DP更像贪心

时间复杂度的计算

二分的时间复杂度是logn,循环n次,故为nlogn :::info 假使总共有n个元素,那么二分后每次查找的区间大小就是n,n/2,n/4,…,n/2^k(接下来操作元素的剩余个数),其中k就是循环的次数。
最坏的情况是K次二分之后,每个区间的大小为1,找到想要的元素
令n/2^k=1,
可得k=log2n,(是以2为底,n的对数),所以时间复杂度可以表示O()=O(logn). :::

代码

  1. import java.util.Scanner;
  2. public class Main {
  3. public static void main(String[] args) {
  4. Scanner in = new Scanner(System.in);
  5. int n = in.nextInt();
  6. int[] q = new int[n+5];
  7. int[] a = new int[n+5];
  8. for (int i = 0; i < n; i++) {
  9. a[i] = in.nextInt();
  10. }
  11. int len = 0;
  12. q[0] = (int) -2e9;
  13. for (int i = 0; i <= n; i++) {
  14. int l = 0,r = len;
  15. while (l < r) {
  16. int mid = l + r +1 >> 1;
  17. if(q[mid] < a[i]) l = mid;
  18. else r = mid - 1;
  19. }
  20. q[l + 1] = a[i];
  21. len = Math.max(len, r + 1);
  22. }
  23. System.out.println(len);
  24. }
  25. }

二、打印最长上升子序列(倒序输出)

打印最长子序列的时候可以用g[i]数组来表示每个以num[i]结尾的最长上升子序列的上一个数在num数组中的下标。
以输入样例为例

下标 0 1 2 3 4 5 6 7
num 3 1 2 1 8 5 6
f 1 1 2 1 3 3 4
g 0 0 2 0 3 3 6
  1. import java.util.Scanner;
  2. public class Main {
  3. public static void main(String[] args) {
  4. Scanner in = new Scanner(System.in);
  5. int n = in.nextInt();
  6. int[] num = new int[1005];
  7. int[] f = new int[1005];
  8. int[] g = new int[1005];
  9. for(int i = 1; i <= n; i++) num[i] = in.nextInt();
  10. for(int i = 1; i <= n; i++) {
  11. f[i] = 1;
  12. g[i] = 0;
  13. for(int j = 1; j < i; j++) {
  14. if(num[i] > num[j]) {
  15. if(f[i] < f[j] + 1){
  16. f[i] = f[j] + 1;
  17. // g[i]存的是以i结尾的序列的倒数第二个数在num中的下标
  18. g[i] = j;
  19. }
  20. }
  21. }
  22. }
  23. // 这个循环取出最大值
  24. int k = 1;
  25. for(int i = 1; i<= n;i++) {
  26. if (f[k] < f[i]) k = i;
  27. }
  28. System.out.println(f[k]);
  29. // 这个循环取出路径,输出为倒序
  30. for (int i = 0, len = f[k]; i < len; i++) {
  31. System.out.print(num[k]+" ");
  32. k = g[k];
  33. }
  34. }
  35. }