引言

子序列和子字符串或者连续子集的不同之处在于,子序列不需要是原序列上连续的值。

对于子序列的题目,大多数需要用到DP的思想,因此,状态转移是关键。

这里摘录两个常见子序列问题及其解法。

例题1, 最长公共子序列

我们知道最长公共子串的求法,先温习一下,它的求法也是使用DP思想,对于 字符串s1 和字符串s2,令 m[i][j] 表示 s1上以s1[i]结尾的子串和s2上s2[j]结尾的子串的最长公共子串长度,因为公共子串必须是连续的,因此状态转移方程:m[i, j] = (s1[i] == s2[j] ? m[i-1, j-1] + 1 : 0)。因为m[i, j]的计算只需要用到 m[i-1, j-1],再之前的就用不着了,因此我们不必用一个二维数组来保存整个m[s1.length()][s2.length()],只需要保存并不断更新m[i-1, j-1]就可以了。

代码:

  1. char *LCString(const char* s1, const char* s2){
  2. if(NULL == s1 || NULL == s2)
  3. return NULL;
  4. int size1 = , size2 = ;
  5. const char* head1 = s1; const char* head2 = s2;
  6. while(*(head1++) != '\0') size1++;
  7. while(*(head2++) != '\0') size2++;
  8. printf("%d, %d\n", size1, size2);
  9.  
  10. int maxlen = , maxend = , i = , j = , tmpPre = ;
  11. int m2[size2];
  12. for(i = ; i < size2; m2[i] = , ++i);
  13. for(i = ; i < size1; ++i){
  14. for(j = ; j < size2; ++j){
  15. int len = ((s1[i] == s2[j] ? : ) + (j > ? tmpPre : ));
  16. if(len > maxlen) { maxlen = len; maxend = i;}
  17. tmpPre = m2[j];
  18. m2[j] = len;
  19. }
  20. }
  21.  
  22. if(maxlen > ){//提出最长子字符串
  23. char* lcs = new char[maxlen + ];
  24. for(i = ; i < maxlen; lcs[maxlen - i - ] = s1[maxend - i], i++);
  25. lcs[maxlen] = '\0';
  26. return lcs;
  27. }
  28. return NULL;
  29. }

那么,对于最长公共子序列,如何去求呢?

首先,如果用m[][]来存长度,最后提出最长子序列要麻烦一些,因为子序列是不连续的。不过虽然麻烦,依旧可行。

接着,依然假设m[i, j]表示 s1[i]结尾的子串 和s2[j]结尾的子串的 最长公共子序列的长度。

那么:

若s1[i] == s2[j],m[i,j] = m[i-1][j-1] + 1;

若s1[i] != s2[j],m[i,j] = Max(m[i-1][j], m[i][j-1])。

这里因为求 m[i,j] 时,m[i-1][j], m[i][j-1], m[i-1][j-1]都有可能用到,因此咱还是老实一点用 二维数组吧。。

  1. template <typename T> T* Lcseq(T* list1, int size1, T* list2, int size2){
  2. if(NULL == list1 || NULL == list2)
  3. return NULL;
  4. int** m = new int*[size1];
  5. int i = , j = ;
  6. int max = , maxi = , maxj = ;
  7. for(; i < size1; i++){
  8. m[i] = new int[size2];
  9. for(j = ; j < size2; j++){
  10. if(i == && j == ) m[][] = (list1[] == list2[] ? : );
  11. else if(i == ) m[i][j] = (list1[i] == list2[j] ? : m[i][j-]);
  12. else if(j == ) m[i][j] = (list1[i] == list2[j] ? : m[i-][j]);
  13. else m[i][j] = (list1[i] == list2[j] ? m[i-][j-] + : (m[i][j-] > m[i-][j] ? m[i][j-] : m[i-][j]));
  14. if(m[i][j] > max){
  15. max = m[i][j];
  16. maxi = i;
  17. maxj = j;
  18. }
  19. }
  20. }
  21. //printf("%d, %d, %d\n", max, maxi, maxj);
  22.  
  23. //提取最大公共子序列
  24. int p1 = maxi, p2 = maxj, p = max;
  25. T* sub = new T[max];
  26. while(p1 >= && p2 >= ){
  27.  
  28. if(list1[p1] == list2[p2]){
  29. sub[--p] = list1[p1];
  30. //printf("p: %d, p1: %d, p2: %d\n", p, p1, p2);
  31. p1--;
  32. p2--;
  33. }
  34. else{
  35. if(p1 == ) p2--;
  36. else if(p2 == ) p1--;
  37. else{
  38. if(m[p1-][p2] < m[p1][p2-]) p2--;
  39. else p1--;
  40. }
  41. }
  42.  
  43. }
  44. return sub;
  45. }

例题2,求子序列的个数,LeetCode

Distinct Subsequences

Given a string S and a string T, count the number of distinct subsequences of T in S.

A subsequence of a string is a new string which is formed from the original string by deleting some (can be none) of the characters without disturbing the relative positions of the remaining characters. (ie, "ACE" is a subsequence of "ABCDE" while "AEC" is not).

Here is an example:
S = "rabbbit"T = "rabbit"

Return 3.

  1. class Solution {
  2. public:
  3. int numDistinct(string S, string T) {
  4. }
  5. };

如果不用DP,用带记忆的递归也能做,就是时间比较长,而且递归需要额外的栈空间。

  1. class Solution {
  2. public:
  3. int numDistinct(string S, string T) {
  4. if(T.length() == ) return ;
  5. if(S.length() == ) return ;
  6.  
  7. rec = new int*[S.length()];
  8. for(int i = ;i < S.length(); ++i){
  9. rec[i] = new int[T.length()];
  10. for(int j = ;j < T.length(); ++j)
  11. rec[i][j] = -;
  12. }
  13. return numDistinctCore(S, T, ,);
  14. }
  15.  
  16. int numDistinctCore(string S, string T, int p1, int p2) {
  17. if(p2 == T.length()) return ;
  18. if((T.length()-p2) > (S.length()-p1)) return ;
  19. if(rec[p1][p2] >= ) return rec[p1][p2];
  20. int sum = ;
  21. for(int i = p1;i < S.length(); ++i){
  22. if(S[i] == T[p2])
  23. sum += numDistinctCore(S, T, i+, p2+);
  24. }
  25. rec[p1][p2] = sum;
  26. return sum;
  27. }
  28. private:
  29. int **rec;
  30. };

AC时间 388ms。

引入DP思想的话,我们依旧用rec[i][j] 表示 "S[i]结尾子串" 中包含 "T[j]结尾子串" 的 sequence 个数。

因为S[i]子串 包含了S[i-1]子串,所以rec[i][j] 至少等于rec[i-1][j];同时,如果S[i] == T[j],那么还可以让 S[i]和T[j] 匹配,这种情况下,sequence个数就是rec[i-1][j-1]。

rec[i][j] = rec[i-1][j] + (S[i] == T[j] ? rec[i-1][j-1] : 0)

代码:

  1. class Solution {
  2. public:
  3. int numDistinct(string S, string T) {
  4. int slen = S.length(), tlen = T.length();
  5. if(slen < tlen) return ;
  6. int **rec = new int*[slen+];
  7. int i, j;
  8. for(i = ; i <= slen; ++i){
  9. rec[i] = new int[tlen+];
  10. for(j = ; j <= tlen; ++j){
  11. rec[i][j] = ;
  12. }
  13. }
  14. for(i = ; i <= slen; rec[i++][] = );
  15.  
  16. for(i = ; i <= slen; ++i){
  17. for(j = ; j <= tlen; ++j){
  18. rec[i][j] = (rec[i-][j] + (S[i-] == T[j-] ? rec[i-][j-] : ));
  19. }
  20. }
  21.  
  22. return rec[slen][tlen];
  23. }
  24. };

AC时间 52ms。大幅提高。

上面的解法用到了二维数组。后来搜到了小磊哥关于这道题的解。让 j 从T末尾遍历,这样rec[i][j] 要么依旧等于 rec[i-1][j],也就是不变,要么加上 rec[i-1][j-1],因为j是从末尾遍历到前面,因此  rec[i-1][j-1] 不会被覆盖。这样做,省去了二维数组,直接一维数组搞定。用match[] 表示 T[j]结尾的子串 的sequence个数。

代码:

  1. class Solution {
  2. public:
  3. int numDistinct(string S, string T) {
  4. if(S.size() < T.size()) return ;
  5. int match[T.size()+];
  6. int i, j;
  7. for(match[] = , i = ; i < T.size(); match[++i] = );
  8. for(i = ; i <= S.size(); ++i)
  9. for(j = T.size(); j >= ; --j)
  10. if(S[i-] == T[j-])
  11. match[j] += match[j-];
  12. return match[T.size()];
  13. }
  14. };

这里也用到了上一篇文章中利用从后往前遍历避免值被覆盖的思想。

16ms AC,只能说,碉堡了。。

子序列 sub sequence问题,例:最长公共子序列,[LeetCode] Distinct Subsequences(求子序列个数)的更多相关文章

  1. [Leetcode] distinct subsequences 不同子序列

    Given a string S and a string T, count the number of distinct subsequences of T in S. A subsequence ...

  2. 【线型DP模板】最上上升子序列(LIS),最长公共子序列(LCS),最长公共上升子序列(LCIS)

    BEGIN LIS: 一个数的序列bi,当b1 < b2 < … < bS的时候,我们称这个序列是上升的.对于给定的一个序列(a1, a2, …, aN),我们可以得到一些上升的子序 ...

  3. 经典算法-最长公共子序列(LCS)与最长公共子串(DP)

    public static int lcs(String str1, String str2) { int len1 = str1.length(); int len2 = str2.length() ...

  4. 最长公共前缀 leetcode 14

    方法一(纵向扫描) 解题思路 先计算出数组中最小的字符串长度,这样就避免了越界的情况,思路更加明确,但同时时间复杂度就相应的上升了. 先计算所有字符串在同一列上的字符是否相同,然后依次向后延伸. 代码 ...

  5. 14. 最长公共前缀----LeetCode

    编写一个函数来查找字符串数组中的最长公共前缀. 如果不存在公共前缀,返回空字符串 "". 示例 1: 输入: ["flower","flow" ...

  6. [LeetCode] Distinct Subsequences 不同的子序列

    Given a string S and a string T, count the number of distinct subsequences of T in S. A subsequence ...

  7. DP————LIS(最长上升子序列)和LCS(最长公共子序列)问题

    LIS问题 https://www.acwing.com/problem/content/898/ 思路:首先数组a中存输入的数(原本的数),开辟一个数组f用来存结果,最终数组f的长度就是最终的答案: ...

  8. [LeetCode] Increasing Subsequences 递增子序列

    Given an integer array, your task is to find all the different possible increasing subsequences of t ...

  9. 115 Distinct Subsequences 不同子序列

    给定一个字符串 S 和一个字符串 T,求 S 的不同的子序列中 T 出现的个数.一个字符串的一个子序列是指:通过删除一些(也可以不删除)字符且不干扰剩余字符相对位置所组成的新字符串.(譬如," ...

随机推荐

  1. 中国剩余定理---FZU 1402 猪的安家

    J - 猪的安家 Time Limit:1000MS     Memory Limit:32768KB     64bit IO Format:%I64d & %I64u Submit Sta ...

  2. HDU 5433 Xiao Ming climbing 动态规划

    题目链接: http://acm.hdu.edu.cn/showproblem.php?pid=5433 Xiao Ming climbing Time Limit: 2000/1000 MS (Ja ...

  3. 关于初装kali linux 2.0时DEB文件安装失败的问题

    kali linux 是一个基于debian 的linux发行版本,支持deb文件格式的图形化安装. 刚装上kali linux时安装程序总是失败,提示处理时错误. 经过一番爬贴,是软件源的原因,解决 ...

  4. Linux的压缩/解压缩文件处理 zip & unzip

    Linux的压缩/解压缩命令详解及实例 压缩服务器上当前目录的内容为xxx.zip文件 zip -r xxx.zip ./* 解压zip文件到当前目录 unzip filename.zip 另:有些服 ...

  5. TDDL实践

    使用入门-数据源配置 数据源配置,tddl的入口,从datasource切入 <bean id="tddlDataSource" class="com.taobao ...

  6. Kafka设计解析

    Kafka剖析(一):Kafka背景及架构介绍 Kafka设计解析(二):Kafka High Availability (上) Kafka设计解析(三):Kafka High Availabilit ...

  7. Ajax修改全局变量问题解决方法(Zepto版)

    前两天项目遇到一个用ajax修改全局变量的案例,一开始无法给这个全局变量修改赋值,在网上查了一下,解决如下: 修改前: var word=1; $.ajax({ url:"myJSON.js ...

  8. 【Python】Python的time和datetime模块

    time 常用的有time.time()和time.sleep()函数. import time print(time.time()) 1499305554.3239055 上面的浮点数称为UNIX纪 ...

  9. HDU2993_MAX Average Problem

    题目要求你在n个数的序列中,找出一段连续的长度不小于k的连续的序列,使得这个序列的平均数最大.输出这个平均数. 典型的优先队列.首先我们需要根据输入的序列,制造一个和序列. 然后从k开始往后面走,其实 ...

  10. wp开发(三)--赚取收益篇

    App开发完毕了,是否有赚取收益的想法呢?下面很浅显地介绍两种常用赚取收益的方法. 一. 收费 在发布应用时,可以对应用进行定价,发布到商城之后,用户付费才可以下载,当然也可以提供试用版.收益状况可以 ...