最长公共子序列/子串

有两个序列 A 和 B, 问最长的公共序列的长度是?
(如果问子串就是换成字符串做,一样的做法就行)

DP

时间复杂度: O ( N × M ) O(N \times M) O(N×M)

空间复杂度: O ( N × M ) O(N \times M) O(N×M)

一维数组可以将空间复杂度降至 O ( m i n ( N , M ) ) O(min(N,M)) O(min(N,M))

定义 same(i,j):A[ i ] = B[ j ] 时取1,否则取0

所以两个序列可以用same矩阵表示:

A = [1,2,3,2,1] B = [3,2,1,4,7]

在这里插入图片描述
则,最长公共序列长度 = 最长斜线长度

所以递推公式:dp [ i ] [ j ]:A[ i ] 和 B [ j ] 的最长公共序列长度


d p [ i ] [ j ] = { d p [ i − 1 ] [ j − 1 ] + 1   A [ i ] = B [ j ] 0   A [ i ] ≠ B [ j ] dp [ i ] [ j ]=\left\{ \begin{aligned} dp [ i-1 ] [ j-1 ] +1 & & \ A[ i ] = B [ j ] \\ 0 & & \ A[ i ] ≠B [ j ]\\ \end{aligned} \right. dp[i][j]={dp[i1][j1]+10 A[i]=B[j] A[i]=B[j]


代码从1开始是为了避免数组越界和初始化0下标,所以代码中的 dp[ i ][ j ] 实际扩展了一个单位

public int findLength(int[] A, int[] B) {
	int ans = 0;
	int[][] dp = new int[A.length + 1][B.length + 1];
    for (int i = 1; i <= A.length; ++i) {
    	for (int j = 1; j <= B.length; ++j) {
        	if (A[i - 1] == B[j - 1])
            	dp[i][j] = dp[i - 1][j - 1] + 1;
            else
                dp[i][j] = 0;
            ans = Math.max(ans, dp[i][j]);
        }
	}
	return ans;
}

一维数组优化:
dp是从前往后的过程,所以第二重循环倒序才能不影响结果

public int findLength(int[] A, int[] B) {
	int ans = 0;
	int[] dp = new int[B.length + 1];
	for (int i = 1; i <= A.length; i++) {
		for (int j = B.length; j >= 1; j--) {
			if (A[i - 1] == B[j - 1])
				dp[j] = dp[j - 1] + 1;
			else
				dp[j] = 0;
			ans = Math.max(ans, dp[j]);
		}
	}
	return ans;
}
滑动窗口

时间复杂度: O ( ( N + M ) × min ⁡ ( N , M ) ) O((N + M) \times \min(N, M)) O((N+M)×min(N,M))
空间复杂度: O ( 1 ) O(1) O(1)

参考 LeetCode
想象两把尺子,错开之后比较相同的部分

在这里插入图片描述

public int findLength(int[] A, int[] B) {
	return A.length < B.length ? findMaxLength(A, B) : findMaxLength(B, A);
}

public int findMaxLength(int[] A, int[] B) {
	int ans = 0;
	int n = A.length, m = B.length;
	//B一步步滑动至A和B右对齐
	for (int len = 1; len <= n; ++len)
		ans = Math.max(ans, maxLength(A, 0, B, m - len, len));
	//B比A长,继续滑动至A和B左对齐
	for (int j = m - n; j >= 0; j--)
		ans = Math.max(ans, maxLength(A, 0, B, j, n));
	//从对齐位置开始继续滑动至结束
	for (int i = 1; i < n; ++i)
		ans = Math.max(ans, maxLength(A, i, B, 0, n - i));
	return ans;
}

public int maxLength(int[] A, int i, int[] B, int j, int len) {
	/*
	 * A[i~i+len-1]和 B[j~j+len-1]的最长公共长度
	 */
	int cnt = 0, ans = 0;
	for (int k = 0; k < len; ++k) {
		if (A[i + k] == B[j + k]) cnt++;
		else if (cnt > 0) {
			ans = Math.max(ans, cnt);
			cnt = 0;
		}
	}
	return Math.max(ans, cnt);
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值