关于两个字符串的匹配转化问题

最近遇到好几个这种类型的问题,主要就是给你两个字符串,然后进行字符串自己的匹配或者转化,这类问题就是采用动态规划,二维的和一维的,现在对这一类问题做一个总结

题目一[leetcode72]https://leetcode.com/problems/edit-distance/

给了两个字符串word1和word2,使用删除,添加,替换操作从word1转化到word2,每个操作代价为1,需要找到最小代价的转化方法。

算法原理&算法步骤

方法一:二维dp

使用一个二维的数组dp[][],其中dp[i+1][j+1]表示word1[0,i]转化到word2[0,j]需要的最小代价,那么现在来求解动态规划方程

考虑现在word1[i]和Word2[j],
如果word1[i]=word2[j],那么dp[i+1][j+1]=dp[i][j]
否则word1[i]!=word2[j]时,有这么几种情况
1. abcd abce 替换 dp[i+1][j+1]=dp[i][j]
2.abc abcd 添加 dp[i+1][j+1]=dp[i+1][j]
3.adcd abd 删除 dp[i+1][j+1]=dp[i][j+1]

有了上述动态规划的方程,接下来的问题就简单多了
需要特别注意的是dp[][]数组的第一行和第一列的初始化
第一行表示word1取了"",那么dp[0][j+1]=j+1(添加操作)
第一列表示word2去了"",那么dp[i+1][0]=i+1(删除操作)

代码:

public class Solution {
    public int minDistance(String word1, String word2) {
        int len1=word1.length();
        int len2=word2.length();
        int[][] dp=new int[len1+1][len2+1];//dp[i+1][j+1]表示word1[0,i],word2[0,j]的cnovert需要进行的编辑次数
        
        for(int j=0;j<len2;j++){
            dp[0][j+1]=j+1;
        }
        for(int i=0;i<len1;i++){
            dp[i+1][0]=i+1;
        }
        for(int i=0;i<len1;i++){
            for(int j=0;j<len2;j++){
                if(word1.charAt(i)==word2.charAt(j)){
                    dp[i+1][j+1]=dp[i][j];
                }
                else{
                    dp[i+1][j+1]=Math.min(dp[i][j],Math.min(dp[i][j+1],dp[i+1][j]))+1;
                                         //abcc abcd        abcd abc    abc abcd
                }
            }
        }
        return dp[len1][len2];
    }
}

方法二:一维dp

现在回过头来看看二维动态规划方程,

dp[i+1][j+1]=dp[i][j],word1[i]=word2[j]
dp[i+1][j+1]=min(dp[i][j],dp[i+1][j],dp[i][j+1])

dp[i+1][j+1]只与dp[i][j]、dp[i+1][j]、dp[i][j+1]有关,由于计算顺序也是一行一行的来,所以可以考虑进行复用,只使用一个一维的数组dp,但是假设在计算j-1的时候就更新了dp[j ],那么 上一行的dp[j]就不见了,在计算dp[j+1]的时候是需要上一行的dp[j]的,所以这里之前先不更新,在计算了dp[j+1]之后再去更新dp[j],并且用一个变量prev记忆dp[j],这样

dp[j+1]=dp[j],word1[i]=word2[j]
dp[j+1]=min(dp[j],dp[j+1],prev)  word1[i]!=word2[j],
其中dp[j+1]代表当前行j位置,dp[j]代表上一行j-1位置,prev代表当前行j-1位置

也是需要注意的是,word1取""的时候,也就是dp最开始的初始化
还有word2取"",也就是求解每一行的时候开始的时候的prev的初始化。
另外一点,由于dp[j+1]是在下一列才更新,所以最后一列在循环中没有得到更新,在一行计算完成后,需要给dp[word2.length]=prev单独赋值

public class Solution {
    public int minDistance(String word1, String word2) {
        int len1=word1.length();
        int len2=word2.length();
        int[] dp=new int[len2+1];
        for(int j=0;j<len2;j++){
            dp[j+1]=j+1;
        }
        int prev;
        int cur;
        for(int i=0;i<len1;i++){
            prev=i+1;
            for(int j=0;j<len2;j++){
                if(word1.charAt(i)==word2.charAt(j))
                    cur=dp[j];
                else{
                    cur=Math.min(dp[j+1],Math.min(dp[j],prev))+1;
                }
                dp[j]=prev;
                prev=cur;
            }
            dp[len2]=prev;
        }
        return dp[len2];
    }
}

题目二

[leetcode10]https://leetcode.com/problems/regular-expression-matching/
表达式匹配,给了字符串s和p,需要对s和p进行匹配,其中p的"."表示任意一个字符,p中的"*",表示0个或多个前边的字符

算法原理&算法步骤

同样是使用一个二维动态规划数组dp[][],其中dp[i+1][j+1]表示s[0,i],p[0,j]时候能够匹配,那么现在来求解动态规划方程

假如s[i]==p[j]||p[j]=='.'  那么 dp[i+1][j+1]=dp[i][j]
否则,如果p[j]=='*',如果s[i]!=p[j-1]&&p[j-1]!='.',直接抛弃a*,dp[i+1][j+1]=dp[i+1][j-1]
      否则,表明当前位置p s不同,前一个位置可以匹配,那么有以下几种可能
            abc abc* 直接抛弃* dp[i+1][j+1]=dp[i+1][j]
            abcd abc* *代表一个或多个字符 dp[i+1][j+1]=dp[i][j+1]
            abcccc abc* *把前边一个也拿走 dp[i+1][j+1]=dp[i+1][j-1]                                   

至此,动态规划方程已经得到
同样第一行的初始化,代表s为""p只有为aA这样的才可以匹配。
而第一列的话,如果p为空的话是一定不能匹配的,所以默认为false.

代码:

public class Solution {
    public boolean isMatch(String s, String p) {
        if(s==null||p==null)
            return false;
        boolean[][] dp=new boolean[s.length()+1][p.length()+1];//dp[i][j]表示以i-1为结尾位置的s子串和以j-1为结尾位置的p子串是否能够match
        dp[0][0]=true;
        for(int j=0;j<p.length();j++){//s取出子串为“”,如果p  1*2*3*这样子类型就能够匹配
            if(p.charAt(j)=='*'&&dp[0][j-1])
                dp[0][j+1]=true;
        }
        for(int i=0;i<s.length();i++){
            for(int j=0;j<p.length();j++){
                if(p.charAt(j)=='.'||p.charAt(j)==s.charAt(i)){
                    dp[i+1][j+1]=dp[i][j];    
                }
                else if(p.charAt(j)=='*'){
                    if(p.charAt(j-1)!='.'&&p.charAt(j-1)!=s.charAt(i)){//直接丢弃前边一个字符  a*之间被丢弃
                        dp[i+1][j+1]=dp[i+1][j-1];
                    }
                    else{
                    //aaa a*
                        dp[i+1][j+1]=dp[i+1][j]||dp[i+1][j-1]||dp[i][j+1];//abc abc* abcc* 
                        //a*          当做a        当做“”        当做多个a(注意这里不能是dp[i][j]这样的话就只是当做了一个a)
                    }
                }
            }
        }
        return dp[s.length()][p.length()];
    }
}

第三题

[leetcode44]https://leetcode.com/problems/wildcard-matching/
同上题大同小异,这次"?"代表任意一个字符,"*"代表任意一个字符序列,可以为空。

方法一:dp解法

和上一题一样的思路,而且分析比上一题简单多,所以这里不再详述

public class Solution {
    public boolean isMatch(String s, String p) {
        if(s==null||p==null)
            return false;
        boolean[][] dp=new boolean[s.length()+1][p.length()+1];
        dp[0][0]=true;
        for(int j=0;j<p.length();j++){
            if(p.charAt(j)=='*'&&dp[0][j])
                dp[0][j+1]=true;
        }
        for(int i=0;i<s.length();i++){
            for(int j=0;j<p.length();j++){
                if(p.charAt(j)==s.charAt(i)||p.charAt(j)=='?')
                    dp[i+1][j+1]=dp[i][j];
                else if(p.charAt(j)=='*'){
                    dp[i+1][j+1]=dp[i+1][j]||dp[i][j+1];
                    //            直接去除*   *代替一个或多个其他字符
                }
            }
        }
        return dp[s.length()][p.length()];
    }
}

方法二:

由于这里和前边的元素没有关系了,所以如果前面出现了不匹配,那么直接就可以判定不匹配,不需要就行后边的操作,所以直观上,上述动态规划的方法就存在很大的时间浪费,这里看到一种更有效率的方法。本质思想就是遇到之后,你走不通了那么这一段走不通的都丢给*来代替。

public class Solution {
    public boolean isMatch(String str, String pattern) {
        if(str==null||pattern==null)
            return false;
        int s=0;
        int p=0;
        int match=0;//*所代表的范围的结尾
        int starIdx=-1;//*的位置
        while(s<str.length()){
            if(p<pattern.length()&&(pattern.charAt(p)=='?'||pattern.charAt(p)==str.charAt(s))){
                s++;
                p++;
            }
            else if(p<pattern.length()&&pattern.charAt(p)=='*'){
                starIdx=p;
                match=s;
                p++;//s不增加,因为有可能*代表空
            }
            else if(starIdx!=-1){
                p=starIdx+1;
                match++;
                s=match;
            }else{
                return false;
            }
        }
        while(p<pattern.length()&&pattern.charAt(p)=='*'){//如果后边都是*的话,可以当做什么也没有
            p++;
        }
        return p==pattern.length();
    }
}

题目四:

[leetcode97]https://leetcode.com/problems/interleaving-string/
给了三个字符串str1 str2 str3 str3是不是str1和str2的交叉

public class Solution {
    public boolean isInterleave(String s1, String s2, String s3) {
        int len1=s1.length();
        int len2=s2.length();
        int len3=s3.length();
        if(len1+len2!=len3)
            return false;
        boolean[][] dp=new boolean[len1+1][len2+1];//dp[i+1][j+1]表示s1[0,i] s2[0,j]时候满足条件交接到s3[0,i+j+1]
        dp[0][0]=true;
        for(int j=0;j<len2;j++){
            if(s2.charAt(j)==s3.charAt(j))
                dp[0][j+1]=true;
            else
                break;
        }
        for(int i=0;i<len1;i++){
            if(s1.charAt(i)==s3.charAt(i))
                dp[i+1][0]=true;
            else
                break;
        }
        char c1;
        char c2;
        char c3;
        for(int i=0;i<len1;i++){
            c1=s1.charAt(i);
            for(int j=0;j<len2;j++){
                c2=s2.charAt(j);
                c3=s3.charAt(i+j+1);
                if(c1==c3&&c2==c3)
                    dp[i+1][j+1]=dp[i][j+1]||dp[i+1][j];
                else if(c2==c3)
                    dp[i+1][j+1]=dp[i+1][j];
                else if(c1==c3)
                    dp[i+1][j+1]=dp[i][j+1];
            }
        }
        return dp[len1][len2];
    }
}

题目五

[leetcode115]https://leetcode.com/problems/distinct-subsequences/
一个字符串中包含多少个另一个字符串

public class Solution {
    public int numDistinct(String s, String t) {
        int len1=s.length();
        int len2=t.length();
        int[][] dp=new int[len1+1][len2+1];
        dp[0][0]=1;
        for(int i=0;i<len1;i++){
            dp[i+1][0]=1;
        }
        for(int i=0;i<len1;i++){
            for(int j=0;j<len2;j++){
                if(s.charAt(i)==t.charAt(j)){
                    dp[i+1][j+1]=dp[i][j]+dp[i][j+1];
                }
                else
                    dp[i+1][j+1]=dp[i][j+1];
            }
        }
        return dp[len1][len2];
    }
}
public class Solution {
    public int numDistinct(String s, String t) {
        int len1=s.length();
        int len2=t.length();
        int[] dp=new int[len2+1];
        int prev;
        int cur;
        for(int i=0;i<len1;i++){
            dp[0]=1;
            prev=1;
            char c=s.charAt(i);
            for(int j=0;j<len2;j++){
                if(t.charAt(j)==c){
                    cur=dp[j+1]+dp[j];
                }
                else
                    cur=dp[j+1];
                dp[j]=prev;
                prev=cur;
            }
            dp[len2]=prev;
        }
        return dp[len2];
    }
}
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 203,456评论 5 477
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,370评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 150,337评论 0 337
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,583评论 1 273
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,596评论 5 365
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,572评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,936评论 3 395
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,595评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,850评论 1 297
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,601评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,685评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,371评论 4 318
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,951评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,934评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,167评论 1 259
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 43,636评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,411评论 2 342

推荐阅读更多精彩内容

  • 动态规划(Dynamic Programming) 本文包括: 动态规划定义 状态转移方程 动态规划算法步骤 最长...
    廖少少阅读 3,252评论 0 18
  • 背景 一年多以前我在知乎上答了有关LeetCode的问题, 分享了一些自己做题目的经验。 张土汪:刷leetcod...
    土汪阅读 12,719评论 0 33
  • 引言 字符串匹配一直是计算机科学领域研究和应用的热门领域,算法的改进研究一直是一个十分困难的课题。作为字符串匹配中...
    潮汐行者阅读 1,624评论 2 6
  • 基础概念 字符串:S[0..n],S是一个字符串,长度为n。S本质上是一个字符数组,数组的每个元素都是一个字符; ...
    RobotBerry阅读 1,151评论 0 3
  • 分治方法 将问题划分成互不相交的子问题 递归地求解子问题 将子问题的解组合起来 动态规划(两个要素:最优子结构、子...
    superlj666阅读 491评论 0 0