1. LIS 模型
LIS问题:给定一个长度为N
的数列 a
,求数值严格单调递增的子序列的长度最长是多少。
算法思想:
状态表示:f [ i ] f[i] f [ i ] 表示由数列a a a 的前i i i 个数字组成(不一定包含所有数字)且以a [ i ] a[i] a [ i ] 结尾的最长上升子序列的长度。
状态计算:遍历小于i i i 的每一个j j j ,若有a [ j ] < a [ i ] a[j] < a[i] a [ j ] < a [ i ] ,则f [ i ] = m a x ( f [ i ] , f [ j ] + 1 ) f[i] = max(f[i], f[j] + 1) f [ i ] = m a x ( f [ i ] , f [ j ] + 1 ) ,否则f [ i ] = 1 f[i] = 1 f [ i ] = 1 。
代码实现:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 import java.util.*;public class Main { static final int N = 1010 ; static int [] a = new int [N], f = new int [N]; public static void main (String[] args) { Scanner sc = new Scanner (System.in); int n = sc.nextInt(); for (int i = 1 ; i <= n; i ++) a[i] = sc.nextInt(); for (int i = 1 ; i <= n; i ++) { f[i] = 1 ; for (int j = 1 ; j <= n; j ++) { if (a[j] < a[i]) f[i] = Math.max(f[i], f[j] + 1 ); } } int res = 0 ; for (int i = 1 ; i <= n; i ++) res = Math.max(res, f[i]); System.out.println(res); } }
优化: 在状态计算那一步,我们每次去遍历所有小于i i i 的j j j ,这样显然时间复杂度是O ( n 2 ) O(n^2) O ( n 2 ) 。对于数据量大的题目,是会T L E TLE T L E 的。我们可以使用贪心的方式来优化,具体如下: 我们用一个数组q q q 来维护当前遍历到的元素之前的序列,每次遍历到一个元素a [ i ] a[i] a [ i ] ,就在q q q 中寻找小于a [ i ] a[i] a [ i ] 的最大的元素q [ r ] q[r] q [ r ] ,并将q [ r + 1 ] q[r+1] q [ r + 1 ] 赋值为a [ i ] a[i] a [ i ] ,同时更新一下最长上升子序列的长度。 有三个注意点:
将q [ r + 1 ] q[r+1] q [ r + 1 ] 赋值为a [ i ] a[i] a [ i ] :有可能a [ i ] a[i] a [ i ] 就是维护的序列中最大的元素,因此会是添加到序列后面。也有可能是将q [ r + 1 ] q[r+1] q [ r + 1 ] 修改为a [ i ] a[i] a [ i ] 。
如果是将q [ r + 1 ] q[r+1] q [ r + 1 ] 修改为a [ i ] a[i] a [ i ] ,这个操作是不影响后面的遍历的,因为如果后面某个元素a [ j ] a[j] a [ j ] 能放到原来的q [ r + 1 ] q[r+1] q [ r + 1 ] 后面,也必然能放到新的q [ r + 1 ] ( a [ i ] ) q[r+1](a[i]) q [ r + 1 ] ( a [ i ] ) 后面(因为当前的q [ r + 1 ] q[r+1] q [ r + 1 ] 必然是大于a [ i ] a[i] a [ i ] 的)。
在该序列中寻找小于a [ i ] a[i] a [ i ] 的最大的元素q [ r ] q[r] q [ r ] :这个操作我们可以通过二分来实现。
优化代码实现:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 import java.io.*;public class Main { static final int N = 100010 ; static int [] a = new int [N], q = new int [N]; public static void main (String[] args) throws IOException{ BufferedReader br = new BufferedReader (new InputStreamReader (System.in)); int n = Integer.parseInt(br.readLine()); String[] s = br.readLine().split(" " ); for (int i = 0 ; i < n; i ++) a[i] = Integer.parseInt(s[i]); int len = 0 ; q[0 ] = -(int )2e9 ; for (int i = 0 ; i < n; i ++) { int l = 0 , r = len; while (l < r) { int mid = l + r + 1 >> 1 ; if (q[mid] < a[i]) l = mid; else r = mid - 1 ; } len = Math.max(len, r + 1 ); q[r + 1 ] = a[i]; } System.out.print(len); } }
LIS模型相关题目:
2. LCS模型
LCS问题:给定两个长度分别为n 和 m 的字符串 a 和 b,求既是 a 的子序列又是 b 的子序列的字符串长度最长是多少。
算法思想:
状态表示:f [ i ] [ j ] f[i][j] f [ i ] [ j ] 表示由数列a a a 的前i i i 个数字与数列b b b 的前j j j 个数字组成的公共子序列的长度最大值。
状态计算:
当包含a [ i ] a[i] a [ i ] 且不包含b [ j ] b[j] b [ j ] 时,f [ i ] [ j ] = m a x ( f [ i ] [ j ] , f [ i ] [ j − 1 ] ) f[i][j] = max(f[i][j], f[i][j - 1]) f [ i ] [ j ] = m a x ( f [ i ] [ j ] , f [ i ] [ j − 1 ] ) ;
当包含b [ j ] b[j] b [ j ] 且不包含a [ i ] a[i] a [ i ] 时,f [ i ] [ j ] = m a x ( f [ i ] [ j ] , f [ i − 1 ] [ j ] ) f[i][j] = max(f[i][j], f[i - 1][j]) f [ i ] [ j ] = m a x ( f [ i ] [ j ] , f [ i − 1 ] [ j ] ) ;
当既不包含a [ i ] a[i] a [ i ] 也不包含b [ j ] b[j] b [ j ] 时,f [ i ] [ j ] = m a x ( f [ i ] [ j ] , f [ i − 1 ] [ j − 1 ] ) f[i][j] = max(f[i][j], f[i - 1][j - 1]) f [ i ] [ j ] = m a x ( f [ i ] [ j ] , f [ i − 1 ] [ j − 1 ] ) ,但是这种情况可以被以上任意一种包含;
当包含a [ i ] a[i] a [ i ] 且包含b [ j ] b[j] b [ j ] 时(要求a [ i ] = b [ j ] a[i] = b[j] a [ i ] = b [ j ] ),f [ i ] [ j ] = m a x ( f [ i ] [ j ] , f [ i − 1 ] [ j − 1 ] + 1 ) f[i][j] = max(f[i][j], f[i - 1][j - 1] + 1) f [ i ] [ j ] = m a x ( f [ i ] [ j ] , f [ i − 1 ] [ j − 1 ] + 1 ) 。
代码实现:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 import java.util.*;public class Main { static final int N = 1010 ; static int [][] f = new int [N][N]; public static void main (String[] args) { Scanner sc = new Scanner (System.in); int n = sc.nextInt(), m = sc.nextInt(); char [] a = (" " + sc.next()).toCharArray(), b = (" " + sc.next()).toCharArray(); for (int i = 1 ; i <= n; i ++) for (int j = 1 ; j <= m; j ++) { f[i][j] = Math.max(f[i][j - 1 ], f[i - 1 ][j]); if (a[i] == b[j]) f[i][j] = Math.max(f[i][j], f[i - 1 ][j - 1 ] + 1 ); } System.out.println(f[n][m]); } }
3. LCIS模型
LCIS问题:给定两个长度分别为n 和 m 的字符串 a 和 b,求既是 a 的子序列又是 b 的子序列且数值严格单调递增的字符串长度最长是多少。
算法思想:
状态表示:f [ i ] [ j ] f[i][j] f [ i ] [ j ] 表示由数列a a a 的前i i i 个数字与数列b b b 的前j j j 个数字组成且以b [ j ] b[j] b [ j ] 结尾的最长上升公共子序列的长度的最大值。
状态计算:
当不包含a [ i ] a[i] a [ i ] 时,f [ i ] [ j ] = f [ i − 1 ] [ j ] f[i][j] = f[i - 1][j] f [ i ] [ j ] = f [ i − 1 ] [ j ] ;
当包含a [ i ] a[i] a [ i ] 时(要求a [ i ] = b [ j ] a[i] = b[j] a [ i ] = b [ j ] ),此时f [ i ] [ j ] f[i][j] f [ i ] [ j ] 起码为1,f [ i ] [ j ] = m a x ( f [ i ] [ j ] , 1 ) f[i][j] = max(f[i][j], 1) f [ i ] [ j ] = m a x ( f [ i ] [ j ] , 1 ) ,然后遍历b [ j ] b[j] b [ j ] 之前的元素,若b [ k ] < b [ j ] b[k]<b[j] b [ k ] < b [ j ] ,则f [ i ] [ j ] = m a x ( f [ i ] [ j ] , f [ i ] [ k ] + 1 ) f[i][j] = max(f[i][j], f[i][k] + 1) f [ i ] [ j ] = m a x ( f [ i ] [ j ] , f [ i ] [ k ] + 1 ) 。
代码实现:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 import java.util.*;public class Main { static final int N = 3010 ; static int [] a = new int [N], b = new int [N]; static int [][] f = new int [N][N]; public static void main (String[] args) { Scanner sc = new Scanner (System.in); int n = sc.nextInt(); for (int i = 1 ; i <= n; i ++) a[i] = sc.nextInt(); for (int i = 1 ; i <= n; i ++) b[i] = sc.nextInt(); for (int i = 1 ; i <= n; i ++) for (int j = 1 ; j <= n; j ++) { f[i][j] = f[i - 1 ][j]; if (a[i] == b[j]) { f[i][j] = Math.max(f[i][j], 1 ); for (int k = 1 ; k < j; k ++) if (b[k] < b[j]) f[i][j] = Math.max(f[i][j], f[i][k] + 1 ); } } int res = 0 ; for (int i = 1 ; i <= n; i ++) res = Math.max(res, f[n][i]); System.out.println(res); } }
优化: 上述的方法时间复杂度达到了O ( n 3 ) O(n^3) O ( n 3 ) ,数据量稍大一些就T L E TLE T L E 。我们可以对代码做等价变形优化成O ( n 2 ) O(n^2) O ( n 2 ) 。 可以发现,当a [ i ] = b [ j ] a[i] = b[j] a [ i ] = b [ j ] 时,if (b[k] < b[j]) f[i][j] = Math.max(f[i][j], f[i][k] + 1);
这一句等价于if (b[k] < a[i]) f[i][j] = Math.max(f[i][j], f[i][k] + 1);
,我们就能发现,这一部分求解其实是与b [ j ] b[j] b [ j ] 无关的。我们可以直接用一个变量代替这一部分,每次循环j j j 时维护起来即可。优化代码实现:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 import java.util.*;public class Main { static final int N = 3010 ; static int [] a = new int [N], b = new int [N]; static int [][] f = new int [N][N]; public static void main (String[] args) { Scanner sc = new Scanner (System.in); int n = sc.nextInt(); for (int i = 1 ; i <= n; i ++) a[i] = sc.nextInt(); for (int i = 1 ; i <= n; i ++) b[i] = sc.nextInt(); for (int i = 1 ; i <= n; i ++) { int maxv = 1 ; for (int j = 1 ; j <= n; j ++) { f[i][j] = f[i - 1 ][j]; if (a[i] == b[j]) f[i][j] = Math.max(f[i][j], maxv); if (a[i] > b[j]) maxv = Math.max(maxv, f[i][j] + 1 ); } } int res = 0 ; for (int i = 1 ; i <= n; i ++) res = Math.max(res, f[n][i]); System.out.println(res); } }