LCR095-LCR097双序列问题
剑指offer(专项突破版)14.3 双序列问题
LCR095.最长公共子序列
分析
由于输入有两个字符串,因此状态转移方程有两个参数。用函数f(i,j)表示第1个字符串中下标从0到i的子字符串(记为s1[0..i])和第2个字符串中下标从0到j的子字符串(记为s2[0..j])的最长公共子序列的长度。如果第1个字符串的长度是m,第2个字符串的长度是n,那么f(m-1,n-1)就是整个问题的解。
如果第1个字符串中下标为i的字符(记为s1[i])与第2个字符串中下标为j(记为s2[j])的字符相同,那么f(i,j)相当于在s1[0..i-1]和s2[0..j-1]的最长公共子序列的后面添加一个公共字符,也就是f(i,j)=f(i-1,j-1)+1。
如果字符s1[i]与字符s2[j]不相同,则这两个字符不可能同时出现在s1[0..i]和s2[0..j]的公共子序列中。此时s1[0..i]和s2[0..j]的最长公共子序列要么是s1[0..i-1]和s2[0..j]的最长公共子序列,要么是s1[0..i]和s2[0..j-1]的最长公共子序列。也就是说,此时f(i,j)是f(i-1,j)和f(i,j-1)的最大值。
1 |
|
- 时间复杂度:O(mn)
- 空间复杂度:O(mn)
优化空间效率,只保存表格中的两行
接着尝试优化空间效率。需要注意的是,f(i,j)的值依赖于表格中左上角f(i-1,j-1)的值、正上方f(i-1,j)的值和同一行左边f(i,j-1)的值。由于计算f(i,j)的值时只需要使用上方一行的值和同一行左边的值,因此实际上只需要保存表格中的两行就可以。
1 |
|
- 时间复杂度:O(mn)
- 空间复杂度:O(n)
LCR096.交错字符串
分析
如果字符串s1的长度为m,字符串s2的长度为n,那么它们交织得到的字符串s3的长度一定是m+n。可以用函数f(i,j)表示字符串s1的下标从0到i的子字符串(记为s1[0..i],长度为i+1)和字符串s2的下标从0到j的子字符串(记为s2[0..j],长度为j+1)能否交织得到字符串s3的下标从0到i+j+1(记为s3[0..i+j+1],长度为i+j+2)的子字符串。f(m-1,n-1)就是整个问题的解。
按照字符串的交织规则,字符串s3的下标为i+j+1的字符(s3[i+j+1])既可能是来自字符串s1的下标为i的字符(s1[i]),也可能是来自字符串s2的下标为j的字符(s2[j])。如果s3[i+j+1]和s1[i]相同,只要s1[0..i-1]和s2[0..j]能交织得到子字符串s3[i+j],那么s1[0..i]一定能和s2[0..j]交织得到s3[0..i+j+1]。也就是说,当s3[i+j+1]和s1[i]相同时,f(i,j)的值等于f(i-1,j)的值。类似地,当s3[i+j+1]和s2[j]相同时,f(i,j)的值等于f(i,j-1)的值。如果s1[i]和s2[j]都和s3[i+j+1]相同,此时只要f(i-1,j)和f(i,j-1)有一个值为true,那么f(i,j)的值为true。
由此可知,f(i,j)的值依赖于f(i-1,j)和f(i,j-1)的值。如果i等于0,那么f(0,j)的值依赖于f(-1,j)和f(0,j-1)的值。状态转移方程中的i是指字符串s1中当前处理的子字符串的最后一个字符的下标。当i等于0时,当前处理的字符串s1的子字符串中只有一个下标为0的字符。那么当i等于-1时,当前处理的字符串s1的子字符串中一个字符也没有,是空的。f(-1,j)的含义是当字符串s1的子字符串是空字符串的时候,它和字符串s2从下标从0到j的子字符串(即s2[0..j])能否交织出字符串s3中下标从0到j的子字符串(即s3[0..j])。由于空字符和s2[0..j]交织的结果一定还是s2[0..j],因此f(-1,j)的值其实取决于子字符串s2[0..j]和s3[0..j]是否相同。如果s2[j]和s3[j]不同,那么f(-1,j)的值为false;如果s2[j]和s3[j]相同,那么f(-1,j)的值等于f(-1,j-1)的值。
类似地,f(i,-1)的含义是当字符串s2的子字符串是空字符串时,它和s1[0..i]能否交织得到s3[0..i],因此f(i,-1)的值取决于子字符串s1[0..i]和s3[0..i]是否相同。如果s1[i]和s3[i]不同,那么f(i,-1)的值为false;如果s1[i]和s3[i]相同,那么f(i,-1)的值等于f(i-1,-1)的值。
当i和j都等于-1时,f(-1,-1)的值的含义是两个空字符串能否交织得到一个空字符串。这显然是可以的,因此f(-1,-1)的值为true。
1 |
|
- 时间复杂度:O(mn)
- 空间复杂度:O(mn)
优化空间效率
1 |
|
- 时间复杂度:O(mn)
- 空间复杂度:O(n)
LCR097.不同的子序列
分析
由于这个问题的输入有两个字符串,因此状态转移方程有两个参数。用f(i,j)表示字符串S下标从0到i的子字符串(记为S[0..i])中等于字符串T下标从0到j的子字符串(记为T[0..j])的子序列的数目。如果字符串S的长度是m,字符串T的长度是n,那么f(m-1,n-1)就是字符串S中等于字符串T的子序列的数目。
当字符串S的长度小于字符串T的长度时,字符串S中不可能存在等于字符串T的子序列,所以当i小于j时f(i,j)的值都等于0。
如果字符串S中下标为i的字符(记为S[i])等于字符串T中下标为j的字符(记为T[j]),那么对S[i]有两个选择:一个是用S[i]去匹配T[j],那么S[0..i]中等于T[0..j]的子序列的数目等于S[0..i-1]中等于T[0..j-1]的子序列的数目;另一个是舍去S[i],那么S[0..i]中等于T[0..j]的子序列的数目等于S[0..i-1]中等于T[0..j]的子序列的数目。因此,当S[i]等于T[j]时,f(i,j)等于f(i-1,j-1)+f(i-1,j)。
如果S[i]和T[j]不相同,则只能舍去S[i],此时f(i,j)等于f(i-1,j)。
接着考虑字符串S和T为空的情形。由于f(0,j)表示S[0..0](子字符串的长度为1)中等于T[0..j]的子序列的数目,因此f(-1,j)表示字符串S为空。同理,f(i,-1)表示字符串T为空。
当字符串S、T都为空时,两个字符串匹配,因此f(-1,-1)等于1。如果字符串S为空而字符串T不为空,那么字符串S中不可能存在等于字符串T的子序列,即当j大于或等于0时f(-1,j)等于0。如果字符串S不为空而字符串T为空,那么字符串S的空子序列(舍去字符串S的所有字符)等于字符串T,即当i大于或等于0时f(i,-1)等于1。
1 |
|
注意:dp用int型会溢出。
- 时间复杂度:O(mn)
- 空间复杂度:O(mn)
优化空间效率
由于计算f(i,j)只依赖位于它上一行的f(i-1,j-1)和f(i-1,j),并不依赖位于它左边的f(i,j-1),因此不一定要按照从左到右的顺序计算f(i,j)。如果按照从右到左的顺序,则先计算f(i,j)再计算f(i,j-1)。
1 |
|
上述代码省略了S[i]和T[j]不相等的情况。在开始计算f(i,j)之前,f(i-1,j)保存在“dp[j+1]”中。当S[i]和T[j]不相等时,f(i,j)等于f(i-1,j)。因此,此时“dp[j+1]”的值也是f(i,j)的值,并不需要任何改动。
- 时间复杂度:O(mn)
- 空间复杂度:O(n)
总结
和单序列问题不同,双序列问题的输入有两个或更多的序列,通常是两个字符串或数组。由于输入是两个序列,因此状态转移方程通常有两个参数,即f(i,j),定义第1个序列中下标从0到i的子序列和第2个序列中下标从0到j的子序列的最优解(或解的个数)。一旦找到了f(i,j)与f(i-1,j-1)、f(i-1,j)和f(i,j-1)的关系,通常问题也就迎刃而解。
由于双序列的状态转移方程有两个参数,因此通常需要使用一个二维数组来保存状态转移方程的计算结果。但在大多数情况下,可以优化代码的空间效率,只需要保存二维数组中的一行就可以完成状态转移方程的计算,因此可以只用一个一维数组就能实现二维数组的缓存功能。