算法一:动态规划 $O(n^2)$
思路
$f(i)$表示“只考虑前$i$个数且以$a_i$结尾的上升序列”的长度,属性:$Max$
状态计算不能与之前的数组合成序列:$f(i) = 1$
能与之前的某个数组合成序列:$f(i) = max(f(k_1) + 1, f(k_2) + 1,\cdots,f(k_j) + 1)$,其中$1 \leq k < i$且$a_k < a_i$
综上:$f(i) = max(1, f(k_1) + 1, f(k_2) + 1,\cdots,f(k_j) + 1)$
步骤
遍历所有数字以当前数字(即为$a_i$)结尾的最长序列初始为其本身,则长度为$L_i = 1$
对于每个数字,再遍历数字$a_1 \sim a_{i-1}$。当有$a_k < a_i$时,表明$a_i$可以接到$a_k$后,故$L_i=L_k+1$
$a_1 \sim a_{i-1}$可能存在多个比$a_i$小的数,所以可以接上的位置有多个,即能组成的新序列有多个,此时只需记录下这些序列中最长的一条
遍历所有已记录的长度,最大值即为最长上升子序列长度
代码 $O(n^2)$
import java.lang.*;
import java.util.*;
public class Main {
static Scanner scanner = new Scanner(System.in);
static int n;
static int[] f = new int[1010], a = new int[1010];
public static void main(String[] args) {
n = scanner.nextInt();
for (int i = 1; i <= n; i++) {
a[i] = scanner.nextInt();
}
for (int i = 1; i <= n; i++) {
f[i] = 1; // 初始值为1 表明当前序列仅有该数本身
for (int j = 1; j < i; j++) { // 遍历这之前的所有数
if (a[i] > a[j]) { // 若a[i]可以接在a[j]后面
f[i] = Math.max(f[i], f[j] + 1); // 更新序列长度 取最大值
}
}
}
int ans = Integer.MIN_VALUE;
for (int i = 1; i <= n; i++) {
ans = Math.max(ans, f[i]); // 遍历已记录的所有长度 取最大值
}
System.out.println(ans);
}
}
算法二:动态规划+贪心二分 $O(n*logn)$
$f(i)$表示“长度为$i$的序列的最后一个数字”,属性:$Min$
对于上升序列$【\cdots a_k \cdots a_{i-1}】$,考虑$a_i$若$a_{i-1} < a_i$,则直接添加到末尾,该序列长度+1
若$a_{i-1} > a_i$,则序列中必有$a_{k-1} \leq a_i \leq a_k$(当$a_k$为第一项或最后一项时只满足一半,但不影响以下分析),对于之后以$a_j \in (a_{i+1} \cdots a_n)$为结尾的序列在考虑$【\cdots a_{k-1},a_i, a_j】$必定优先于$【\cdots a_{k-1},a_k, a_j】$,因为$a_i < a_k$使得$a_j$有更大的选择空间,所以可直接替换原序列$a_k$为$a_i$
在考虑是否可以组成$【\cdots a_{k-1},a_k, \cdots a_{i-1}, a_j】$时(即序列最大长度更新),只需判断$a_{i-1} < a_j$,与上一步的替换无关
import java.lang.*;
import java.util.*;
public class Main {
static Scanner scanner = new Scanner(System.in);
static int n;
static int[] f = new int[1010];
public static void main(String[] args) {
n = scanner.nextInt();
int cnt = 0; // 统计当前最长序列的长度
int a = scanner.nextInt(); // 读一个数
f[++cnt] = a; // 仅有一个数 所以序列长度为1的末尾最小数字为a
for (int i = 2; i <= n; i++) {
a = scanner.nextInt(); // 读下一个数
if (f[cnt] < a) { // 如果比当前序列的末尾数字大 则序列可增长
f[++cnt] = a; // 记录a到新序列长度的位置
} else { // 如果这个数字不能添加到末尾 则二分查找第一个大于它的数字 并替换掉
int l = 1, r = cnt;
while (l < r) {
int mid = l + r >> 1;
if (f[mid] >= a) {
r = mid;
} else {
l = mid + 1;
}
}
if (f[r] > a) { // 找到了才替换 (在本题必定能找到)
f[r] = a;
}
}
}
System.out.println(cnt);
}
}