最长上升子序列二分java_AcWing 895. 最长上升子序列(Java 两种DP算法)

算法一:动态规划 $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);

}

}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值