子序列问题两例,第二例也用到了从后往前遍历思想

引言

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

对于子序列的题目,大多数需要用到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]就可以了。

代码:

char *LCString(const char* s1, const char* s2){
    if(NULL == s1 || NULL == s2)
        return NULL;
    int size1 = 0, size2 = 0;
    const char* head1 = s1; const char* head2 = s2;
    while(*(head1++) != ‘\0‘) size1++;
    while(*(head2++) != ‘\0‘) size2++;
    printf("%d, %d\n", size1, size2);

    int maxlen = 0, maxend = 0, i = 0, j = 0, tmpPre = 0;
    int m2[size2];
    for(i = 0; i < size2; m2[i] = 0, ++i);
    for(i = 0; i < size1; ++i){
        for(j = 0; j < size2; ++j){
            int len = ((s1[i] == s2[j] ? 1 : 0) + (j > 0 ? tmpPre : 0));
            if(len > maxlen) { maxlen = len; maxend = i;}
            tmpPre = m2[j];
            m2[j] = len;
        }
    }

    if(maxlen > 0){//提出最长子字符串
        char* lcs = new char[maxlen + 1];
        for(i = 0; i < maxlen; lcs[maxlen - i - 1] = s1[maxend - i], i++);
        lcs[maxlen] = ‘\0‘;
        return lcs;
    }
    return NULL;
}

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

首先,如果用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]都有可能用到,因此咱还是老实一点用 二维数组吧。。

template <typename T> T* Lcseq(T* list1, int size1, T* list2, int size2){
    if(NULL == list1 || NULL == list2)
        return NULL;
    int** m = new int*[size1];
    int i = 0, j = 0;
    int max = 0, maxi = 0, maxj = 0;
    for(; i < size1; i++){
        m[i] = new int[size2];
        for(j = 0; j < size2; j++){
            if(i == 0 && j == 0) m[0][0] = (list1[0] == list2[0] ? 1 : 0);
            else if(i == 0) m[i][j] = (list1[i] == list2[j] ? 1 : m[i][j-1]);
            else if(j == 0) m[i][j] = (list1[i] == list2[j] ? 1 : m[i-1][j]);
            else m[i][j] = (list1[i] == list2[j] ? m[i-1][j-1] + 1 : (m[i][j-1] > m[i-1][j] ? m[i][j-1] : m[i-1][j]));
            if(m[i][j] > max){
                max = m[i][j];
                maxi = i;
                maxj = j;
            }
        }
    }
    //printf("%d, %d, %d\n", max, maxi, maxj);

    //提取最大公共子序列
    int p1 = maxi, p2 = maxj, p = max;
    T* sub = new T[max];
    while(p1 >= 0 && p2 >= 0){

        if(list1[p1] == list2[p2]){
            sub[--p] = list1[p1];
            //printf("p: %d, p1: %d, p2: %d\n", p, p1, p2);
            p1--;
            p2--;
        }
        else{
            if(p1 == 0) p2--;
            else if(p2 == 0) p1--;
            else{
                if(m[p1-1][p2] < m[p1][p2-1]) p2--;
                else p1--;
            }
        }

    }
    return sub;
}

例题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.

class Solution {
public:
    int numDistinct(string S, string T) {
    }
};

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

class Solution {
public:
    int numDistinct(string S, string T) {
        if(T.length() == 0) return 1;
        if(S.length() == 0) return 0;

        rec = new int*[S.length()];
        for(int i = 0;i < S.length(); ++i){
            rec[i] = new int[T.length()];
            for(int j = 0;j < T.length(); ++j)
                rec[i][j] = -1;
        }
        return numDistinctCore(S, T, 0 ,0);
    }

    int numDistinctCore(string S, string T, int p1, int p2) {
        if(p2 == T.length()) return 1;
        if((T.length()-p2) > (S.length()-p1)) return 0;
        if(rec[p1][p2] >= 0) return rec[p1][p2];
        int sum = 0;
        for(int i = p1;i < S.length(); ++i){
            if(S[i] == T[p2])
                sum += numDistinctCore(S, T, i+1, p2+1);
        }
        rec[p1][p2] = sum;
        return sum;
    }
private:
    int **rec;
};

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)

代码:

class Solution {
public:
    int numDistinct(string S, string T) {
        int slen = S.length(), tlen = T.length();
        if(slen < tlen) return 0;
        int **rec = new int*[slen+1];
        int i, j;
        for(i = 0; i <= slen; ++i){
            rec[i] = new int[tlen+1];
            for(j = 0; j <= tlen; ++j){
                rec[i][j] = 0;
            }
        }
        for(i = 0; i <= slen; rec[i++][0] = 1);

        for(i = 1; i <= slen; ++i){
            for(j = 1; j <= tlen; ++j){
                rec[i][j] = (rec[i-1][j] + (S[i-1] == T[j-1] ? rec[i-1][j-1] : 0));
            }
        }

        return rec[slen][tlen];
    }
};

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个数。

代码:

class Solution {
public:
    int numDistinct(string S, string T) {
        if(S.size() < T.size()) return 0;
        int match[T.size()+1];
        int i, j;
        for(match[0] = 1, i = 0; i < T.size(); match[++i] = 0);
        for(i = 1; i <= S.size(); ++i)
            for(j = T.size(); j >= 1; --j)
                if(S[i-1] == T[j-1])
                    match[j] += match[j-1];
        return match[T.size()];
    }
};

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

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

子序列问题两例,第二例也用到了从后往前遍历思想

时间: 2024-11-05 21:36:18

子序列问题两例,第二例也用到了从后往前遍历思想的相关文章

[hadoop]hadoop权威指南例第二版3-1、3-2

hadoop版本1.2.1 jdk1.7.0 例3-1.通过URLStreamHandler实例以标准输出方式显示Hadoop文件系统的文件 hadoop fs -mkdir input 在本地创建两个文件file1,file2,file1的内容为hello world,file2内容为hello Hadoop,然后上传到input,具体方法如Hadoop集群(第6期)_WordCount运行详解中 2.1.准备工作可以看到. 完整代码如下: 1 import org.apache.hadoop

python 爬虫第二例--百度贴吧

python 第二例,爬取百度贴吧的帖子,获取帖子的标题,内容,所在楼层,发布时间 其中存在一个问题,当该帖子是手机端发布的帖子,此时在页面中会有标识,因此多一个span标签,与楼层和发布时间的标签一样 解决方法: 目潜想到的解决方法是通过判断爬到的值来进行选择,但解决方案效率肯定低,因此未使用,等知识体系丰富后再进行改进 附爬取的代码: # -*- coding: utf-8 -*- import urllib2 import urllib import re class Tool: # 去除

C语言程序设计百例之第二例

题目:企业发放的奖金根据利润提成.利润(I)低于或等于10万元时,奖金可提10%:利润高 于10万元,低于20万元时,低于10万元的部分按10%提成,高于10万元的部分,可可提 成7.5%:20万到40万之间时,高于20万元的部分,可提成5%:40万到60万之间时高于 40万元的部分,可提成3%:60万到100万之间时,高于60万元的部分,可提成1.5%,高于100万元时,超过100万元的部分按1%提成,从键盘输入当月利润I,求应发放奖金总数? 1.程序分析:请利用数轴来分界,定位.注意定义时需

AJAX第二例(发送POST请求)

第二例:发送POST请求(如果发送请求时需要带有参数,一般都用POST请求) * open:xmlHttp.open("POST" ....); * 添加一步:设置Content-Type请求头: > xmlHttp.setRequestHeader("Content-Type", "application/x-www-form-urlencoded"); * send:xmlHttp.send("username=zhangSa

用Javascript取float型小数点后两位,例22.127456取成22.13,如何做?

1. 最笨的办法....... [我就怎么干的.........] 1function get()2{3    var s = 22.127456 + "";4    var str = s.substring(0,s.indexOf(".") + 3);5    alert(str);6} 2. 正则表达式效果不错 1<script type="text/javascript">2onload = function(){3    v

单例多例

单例多例需要搞明白两个问题:1. 什么是单例多例:2. 如何产生单例多例:3. 为什么要用单例多例4. 什么时候用单例,什么时候用多例:1. 什么是单例多例:所谓单例就是所有的请求都用一个对象来处理,比如我们常用的service和dao层的对象通常都是单例的,而多例则指每个请求用一个新的对象来处理,比如action; 2. 如何产生单例多例:    在通用的SSH中,单例在spring中是默认的,如果要产生多例,则在配置文件的bean中添加scope="prototype"; 3. 为

web 单例 多例

单例多例需要搞明白两个问题:1. 什么是单例多例:2. 如何产生单例多例:3. 为什么要用单例多例4. 什么时候用单例,什么时候用多例:1. 什么是单例多例:所谓单例就是所有的请求都用一个对象来处理,比如我们常用的service和dao层的对象通常都是单例的,而多例则指每个请求用一个新的对象来处理,比如action; 2. 如何产生单例多例:    在通用的SSH中,单例在spring中是默认的,如果要产生多例,则在配置文件的bean中添加scope="prototype"; 3. 为

Spring中单例多例面试题分析

面试题 1.Spring是单例还是多例,怎么修改? Spring的bean默认是单例的(sigleton)可以修改为多例(prototype), 在此bean节点中添加一个属性,scope="prototype"; 例如<bean id="xxx" class="全类名" scope="prototype"></bean> <?xml version="1.0" encodin

Spring中构造器、init-method、@PostConstruct、afterPropertiesSet孰先孰后,自动注入发生时间以及单例多例的区别

首先明白,spring的IOC功能需要是利用反射原理,反射获取类的无参构造方法创建对象,如果一个类没有无参的构造方法spring是不会创建对象的.在这里需要提醒一下,如果我们在class中没有显示的声明构造方法,默认会生成一个无参构造方法,但是当我们显示的声明一个有参构造方法的时候,JVM不会帮我们生成无参构造方法,所以我们声明一个带参数的构造方法也需要声明一个无参构造方法.(题外话:如果父类声明一个有参构造方法,子类需要在构造方法第一行显示的调用父类构造方法,因为子类的对象也是父类的对象,所以