数据结构与算法---动态规划---最长公共子串

最长公共子串

最长公共子串,Longest Common Substring
子串是连续的子序列

求两个字符串的最长公共子串长度
ABCBA 和 BABCA的最长公共子串是 ABC,长度为3

思路

是否为最值问题?

能否使用DP解决?
  1. 原问题分解为子问题
    可以

  2. 无后效性

使用DP步骤
  1. 确定含义
    假设dp(i, j) 是 str1[i - 1]、str2[j - 1]的最长公共子串
    i ∈ [1, str1.length];
    j ∈ [1, str2.length];

最长公共子串,必然以某一个元素结尾,我们求出所有的结尾元素的最大值,既是问题的答案。

  1. 边界值
    dp(0, j) = dp(i, 0) = 0;

  2. 状态转移方程
    如果str1[i] = str2[j],dp(i, j) = dp(i - 1, j - 1) + 1;
    如果str1[i] != str2[j],dp(i, j) = 0;

注意点就是,有的不相等dp(i, j)等于子列;而有的不相等,dp(i, j)等于0
另外,要明确是以i结尾,还是前i个元素

然后求max{dp(i, j)}就是两个字符串的最大公共子串长度

思路想明白,把转移方程写好,代码也就容易写了。

package dynamicProgramming;

/**最长公共子串*/
public class LCSubstring {
	public static void main(String[] args)
	{
		String str1 = "ABCBA";
		String str2 = "BABCA";
		int length = lcsubstring(str1, str2);
		System.out.println(length);
	}
	
	public static int lcsubstring(String str1, String str2)
	{
		//临界条件
		if (str1 == null) return 0;
		char[] cs1 = str1.toCharArray();
		if (cs1.length == 0) return 0;
		if (str2 == null) return 0;
		char[] cs2 = str2.toCharArray();
		if (cs2.length == 0) return 0;
		
		int[][] dp = new int[cs1.length + 1][cs2.length + 1];
		int max = 0;
		for (int i = 1; i <= cs1.length; i++) {
			for (int j = 1; j <= cs2.length; j++) {
				if (cs1[i - 1] == cs2[j - 1]) {//注意这里
					dp[i][j] = dp[i - 1][j - 1] + 1;
				}else {
					dp[i][j] = 0;
				}
				max = Math.max(dp[i][j], max);
			}
		}
		return max;
	}
}

复杂度分析

时间复杂度:O(n * m)
空间复杂度:O(n * m)

dp(i, j)计算结果制成图如下:
在这里插入图片描述


问:是否可以将dp(i, j) 设置为 str1[i]、str2[j]的最长公共子串

如果假设dp(i, j) 是 str1[i]、str2[j]的最长公共子串
那么,
i ∈ [0, str1.length);
j ∈ [0, str2.length);
在求首元素的时候,str1[0]与str2[0]是否相等,
不相等dp(0, 0) = 0;
相等dp(0, 0) = dp(-1, -1) + 1;

dp二维数组出现了负数,当然,你可以提前判断,如果dp(<0, <0) = 0

假设:str1 = ABC、str2 = ABCA
str1[0]与str2[3]相等,那么dp(0, 3) = dp(-1, 2) + 1 = 0 + 1;
大致也能满足要求

需要注意相应的边界值,并进行相应的修改即可
毕竟,程序是活的

package dynamicProgramming;

/**最长公共子串*/
public class LCSubstring {
	public static void main(String[] args)
	{
		String str1 = "ABCBA";
		String str2 = "BABCA";
		int length = lcsubstring1(str1, str2);
		System.out.println(length);
	}
	
	public static int lcsubstring1(String str1, String str2)
	{
		//临界条件
		if (str1 == null) return 0;
		char[] cs1 = str1.toCharArray();
		if (cs1.length == 0) return 0;
		if (str2 == null) return 0;
		char[] cs2 = str2.toCharArray();
		if (cs2.length == 0) return 0;
		
		int[][] dp = new int[cs1.length + 1][cs2.length + 1];
		int max = 0;
		for (int i = 0; i < cs1.length; i++) {
			for (int j = 0; j < cs2.length; j++) {
				if (cs1[i] == cs2[j]) {//注意这里
					if (i == 0 || j == 0) {
						dp[i][j] = 0 + 1;
					}else {
						dp[i][j] = dp[i - 1][j - 1] + 1;
					}
				}else {
					dp[i][j] = 0;
				}
				max = Math.max(dp[i][j], max);
			}
		}
		return max;
	}
}

思路一样,就是要注意下边界值,然后就是还是第一种写法比较好操作。


优化-一维数组

类似于数据结构与算法—动态规划—最长公共子序列,可以使用短的一方,作为列,然后空间复杂度优化为O(n)级别

package dynamicProgramming;

/**最长公共子串*/
public class LCSubstring {
	public static void main(String[] args)
	{
		String str1 = "ABCBA";
		String str2 = "BABCA";
		int length = lcsubstring2(str1, str2);
		System.out.println(length);
	}
	
	public static int lcsubstring2(String str1, String str2)
	{
		//临界条件
		if (str1 == null) return 0;
		char[] cs1 = str1.toCharArray();
		if (cs1.length == 0) return 0;
		if (str2 == null) return 0;
		char[] cs2 = str2.toCharArray();
		if (cs2.length == 0) return 0;
		
		char[] rowStr = cs1;
		char[] colStr = cs2;
		if (cs1.length < cs2.length) {
			rowStr = cs2;
			colStr = cs1;
		}
		
		int[] dp = new int[colStr.length + 1];
		int max = 0;
		for (int row = 1; row <= rowStr.length; row++) {//变量名定义为row、col更容易理解
			int cur = 0;
			for (int col = 1; col <= colStr.length; col++) {//尽量不要使用i,j这些无意义的变量名
				int leftTop = cur;
				cur = dp[col];
				if (rowStr[row - 1] == colStr[col - 1]) {//注意这里
					dp[col] = leftTop + 1;
				}else {
					dp[col] = 0;
				}
				max = Math.max(dp[col], max);
			}
		}
		return max;
	}
}

复杂度分析

时间复杂度:O(n * m)
空间复杂度:O(min{n, m})


优化-一维数组-倒叙

在使用倒叙的时候,就不需要保存leftTop,更加优化

package dynamicProgramming;

/**最长公共子串*/
public class LCSubstring {
	public static void main(String[] args)
	{
		String str1 = "ABCBA";
		String str2 = "BABCA";
		int length = lcsubstring3(str1, str2);
		System.out.println(length);
	}
	
	public static int lcsubstring3(String str1, String str2)
	{
		//临界条件
		if (str1 == null) return 0;
		char[] cs1 = str1.toCharArray();
		if (cs1.length == 0) return 0;
		if (str2 == null) return 0;
		char[] cs2 = str2.toCharArray();
		if (cs2.length == 0) return 0;
		
		char[] rowStr = cs1;
		char[] colStr = cs2;
		if (cs1.length < cs2.length) {
			rowStr = cs2;
			colStr = cs1;
		}
		
		int[] dp = new int[colStr.length + 1];
		int max = 0;
		for (int row = 1; row <= rowStr.length; row++) {//变量名定义为row、col更容易理解
			for (int col = colStr.length; col >= 1; col--) {//尽量不要使用i,j这些无意义的变量名
				if (rowStr[row - 1] == colStr[col - 1]) {//注意这里
					dp[col] = dp[col - 1] + 1;
				}else {
					dp[col] = 0;
				}
				max = Math.max(dp[col], max);
			}
		}
		return max;
	}
}

复杂度分析

时间复杂度:O(n * m)
空间复杂度:O(min{n, m})

  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值