1、LCS:最长公共子序列问题
LCS问题的描述为:给定两个整数序列a和b,找到它们所有的公共子序列中最长的序列,输出其长度。
例:a:6 4 8 1 3 2、b:4 7 6 2 3 8 6 1
最长公共子序列有:6 8 1、4 8 1,长度均为3,因此结果为3。
解决LCS问题同样是动态规划思想。LCS问题的结果同时受到两个字符串的影响,所以状态需要用二维表示。我们以f[i][j]表示a中从1到i、b中从1到j范围内的最长公共子序列的长度,那么考虑对于状态f[i][j]可以由哪些状态转移过来,在每一阶段我们能做出的决策有以下几种:
若a[i]=b[j],则a[i]、b[j]同时属于公共子序列:
- f[i][j]由f[i-1][j-1]转移而来,f[i][j]=f[i-1][j-1]+1;
若a[i]!=b[j],则a[i]、b[j]不能同时属于公共子序列:
- a[i]不属于公共子序列:f[i][j]由f[i-1][j]转移而来,f[i][j]=f[i-1][j];
- b[j]不属于公共子序列:f[i][j]由f[i][j-1]转移而来,f[i][j]=f[i][j-1];
- a[i]、b[j]都不属于公共子序列:f[i][j]=f[i-1][j-1],这种决策可以舍去,因为只要f[i][j]发生了更新,都是由f[i-1][j-1]+1得到,其一定>f[i-1][j-1];
该情形下舍去最后一项决策后,f[i][j]=max(f[i-1][j], f[i][j-1]);
综上所述,状态转移方程为:
f[a.length][b.length]即为最终要求得的结果,示例代码如下:
public int Lcs(int[] a, int[] b) {
int[][] f = 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])
f[i][j]=f[i-1][j-1]+1;
else
f[i][j]=Math.max(f[i-1][j], f[i][j-1]);
}
}
return f[a.length()][b.length()];
}
上面的代码需要两重循环,时间复杂度为。
2、LCS问题的优化
当序列a中的元素都互不相同的时候,LCS存在一种优化方案,即将其转化为LIS问题求解,而LIS问题可以优化到O(nlogn)的时间复杂度内解决(最长上升子序列(LIS)问题的解决及优化)。那么如何将LCS问题转化为LIS呢,我们进行如下分析:
我们对序列a中的元素按其下标进行标号,因为a中的元素都互不相同,所以每个元素的标号都是唯一的且递增的。而最长公共子序列它属于a的子序列,那么它的元素的标号也是递增的,同时它也属于b的子序列,反过来说也就是当b的某个子序列按a的标号方式呈递增的时候,它也属于a的子序列,即它是a、b的公共子序列。我们可以对b中的元素按a的标号方式标号,剩下的就是从里面寻找递增子序列,又要找最长的那个,那问题就变成了最长上升子序列即LIS问题。
例:a:6 4 8 1 3 2 ,b:4 7 6 2 3 8 6 1
标号为: 0 1 2 3 4 5,则序列b变为:1 0 5 4 2 0 3
标号后的b序列的最长上升子序列为:0 2 3,1 2 3
对应在序列a中得到最长公共子序列为:6 8 1,4 8 1
public int LCS(int[] a, int[] b) {
HashMap<Integer,Integer> map = new HashMap<>();
for(int i=0;i<a.length;++i)
map.put(a[i],i);
ArrayList<Integer> tmp = new ArrayList<>();
for(int i=0;i<b.length;++i) {
if(map.containsKey(b[i]))
tmp.add(map.get(b[i]));
}
//借助二分优化的LIS得到结果
return LIS(tmp.stream().mapToInt(Integer::intValue).toArray());
}
3、最长公共子串问题
最长公共子串问题的描述为:给定两个字符串a和b,输出两个字符串的最长公共子串(最长公共子串存在且唯一)。
该问题与最长公共子序列问题属于同种类型,其区别仅在于一个是子串要求连续,另一个是子序列不要求连续。所以其状态表示与状态转移与LCS问题是几乎一样的,我们以f[i][j]表示以a中i位置结尾、b中j位置结尾的最长公共子串长度,对于状态f[i][j],其可能由以下决策转移而来:
若a[i]=b[j],则a[i]、b[j]同时属于公共子串:
- f[i][j]由f[i-1][j-1]转移而来,f[i][j]=f[i-1][j-1]+1;
若a[i]!=b[j],破坏了公共子串的连续性,则a[i]、b[j]都不属于公共子串:
- f[i][j]=0;
综上,状态转移方程为:
public String LCS (String s1, String s2) {
int dp[][] = new int[s1.length()+1][s2.length()+1];
int max = 0, ans = 0;
for(int i=1;i<=s1.length();++i){
for(int j=1;j<=s2.length();++j){
if(s1.charAt(i-1)==s2.charAt(j-1))
dp[i][j]=dp[i-1][j-1]+1;
else dp[i][j]=0;
if(dp[i][j]>max){
max = dp[i][j];
ans = i;
}
}
}
return s1.substring(ans-max,ans);
}