DP(一种方法KO三道困难难度的DP问题,感觉自己棒棒哒) 72.编辑距离 97.交错字符串 44.通配符匹配 10.正则表达式匹配

72.编辑距离

给你两个单词 word1 和 word2,请你计算出将 word1 转换成 word2 所使用的最少操作数 。

你可以对一个单词进行如下三种操作:

插入一个字符
删除一个字符
替换一个字符

(这题也是字节提前批的一道面试题)刚开始想的是求两个字符串的最长公共子序列,这些是不会变的,然后用较长的一个去减,就是需要改动的最少操作次数,这个方法在想的时候就注意到会有错位的问题,但是没思考好,比如 a**b和*a*b,我的答案是4-2=2,但是实际上a**b必须经过3次操作才能变成*a*b。所以这道题还是直接记录需要修改的次数是最简单直接的。

转移方程跟  最长公共子序列  差不多,倒是可以一起思考。

f[i][j]=min(min(f[i-1][j]+1,f[i][j-1]+1),word1[i-1]==word2[j-1]?f[i-1][j-1]:f[i-1][j-1]+1);
class Solution {
public:
    int minDistance(string word1, string word2) {
        int len1=word1.length(),len2=word2.length();
        int f[len1+10][len2+10];
        memset(f,0,sizeof(f));
        for (int j=1;j<=len2;++j)
        {
            f[0][j]=j;
        }
         for (int i=1;i<=len1;++i)
        {
            f[i][0]=i;
        }
        for (int j=1;j<=len2;++j)
            for (int i=1;i<=len1;++i)
            {
                f[i][j]=min(min(f[i-1][j]+1,f[i][j-1]+1),word1[i-1]==word2[j-1]?f[i-1][j-1]:f[i-1][j-1]+1);
            }
            
        return f[len1][len2];
    }
};

97.交错字符串

这边插播一道相同类型的字符串匹配处理的题目,也是直接定义f[i][j]为s1[0..i-1]和s2[0..j-1]是否能够交错组成s3[0..i+j-1],最后f[lens][lenp]即为答案。

class Solution {
public:
    bool isInterleave(string s1, string s2, string s3) {
        int len1=s1.length(),len2=s2.length(),len3=s3.length();
        if (len1+len2!=len3) return false;
        vector<vector<bool>> f(len1+10,vector<bool>(len2+10,false));
        f[0][0]=true;
        for (int i=1;i<=len1;i++)
        if (s1[i-1]==s3[i-1]) f[i][0]=f[i-1][0];
        for (int j=1;j<=len2;j++)
        if (s2[j-1]==s3[j-1]) f[0][j]=f[0][j-1];

        for (int i=1;i<=len1;i++)
        for (int j=1;j<=len2;j++)
        {
            if (s1[i-1]==s3[i+j-1]) f[i][j]=f[i-1][j];
            if (s2[j-1]==s3[i+j-1]) f[i][j]=f[i][j] || f[i][j-1];
        }
        return f[len1][len2];
    }
};

44.通配符匹配

这题的思路其实跟上面这道题一样,f[i][j]就是直接记录s[0..i-1]和p[0..j-1]能否匹配,最后输出f[lens][lenp]即可。

上面这道距离编辑也是,f[i][j]就是直接记录s[0..i-1]和p[0..j-1]的编辑距离,最后输出f[lens][lenp]即可,一模一样可以说是。

当然这两题的初始化边界条件是不一样的,需要自己摸索调试。

class Solution {
public:
    bool isMatch(string s, string p) {
        int lens=s.length(),lenp=p.length();
        if (lens==0 && lenp==0) return true;
        if (lenp==0) return false;
        bool f[lens+10][lenp+10];
        memset(f,0,sizeof(f));
        f[0][0]=true;
        int index=0;
        while (index<lenp && p[index]=='*') {f[0][++index]=true;}
        for (int j=1;j<=lenp;j++)
            for (int i=1;i<=lens;i++)   
            {
                if (s[i-1]==p[j-1] || '?'==p[j-1]) f[i][j]=f[i-1][j-1];
                else 
                {
                    if ('*'==p[j-1]) f[i][j]=f[i-1][j] || f[i][j-1];
                    else 
                    f[i][j]=false;
                }
            }
        return f[lens][lenp];
    }
};

另外附个官方题解:https://leetcode-cn.com/problems/wildcard-matching/solution/tong-pei-fu-pi-pei-by-leetcode-solution/

里面的方法二讲述了 贪心算法 ,相比动态规划节省了空间的开销,而且后面有关于AC自动机的讨论,有时间看看。

下面再来一道进阶版的吧,难度提升。

10.正则表达式匹配

模式 p 中的任意一个字符都不是独立的,会和前(后)的字符互相关联,形成一个新的匹配模式。因此,本题的状态转移方程需要考虑的情况会多一些。简单思考了一下,".*"居然可以匹配ab,相当于0个或多个".",那".**"的效果其实也是这样,即多个"*"没有意义。这时候我们发现,即单个的"."相当于上面的"?",而".*'相当于上面的"*",多个“*”其实可以简化为一个“*”。现在最后一个问题是“a*”怎么处理,然后其实我们发现,但凡出现‘*’我们肯定要去考虑*前面的那个,题解是遇到*处理,我们这里是判断i+1是否为*处理当前的,是一样的效果。

但是标准答案有一个没处理好:就是她它默认正则表达式不以“*”开头(以*开头正则表达式就不合法了,即不存在以*开头的正则表达式),不然就会发生数组越界。这里我还处理完了好久,基本是跟 通配符 的处理方式是一样的。

class Solution {
public:
    bool isMatch(string s, string p) {
        int lens=s.length(),lenp=p.length();
        if (lens==0 && lenp==0) return true;
        if (lenp==0) return false;
        bool f[lens+10][lenp+10];
        memset(f,0,sizeof(f));
        f[0][0]=true;
        //int index=0;
        //while (index<lenp && p[index]=='*') {f[0][++index]=true;}
        //if (index==lenp) return false;    

        for (int j=1;j<=lenp;j++)
            for (int i=0;i<=lens;i++)
            {
                if (p[j-1]=='*')
                {
                    f[i][j] = f[i][j-2];
                    if (i>0 && (p[j-2]==s[i-1] || p[j-2]=='.'))
                        f[i][j] |=f[i-1][j];
                }
                else
                {
                    if (i>0 && (p[j-1]==s[i-1] || p[j-1]=='.'))
                        f[i][j] = f[i-1][j-1];
                    else 
                        f[i][j]=false;

                    
                }
            }
            return f[lens][lenp];

        
    }
};

这个写法比较好,因为是从*开始判断的,1.比较简洁,2.能处理i=0的初始化问题,3.单独处理a*的a时,虽然进行了无效转化,但是处理a*的*时,是从j-2转移过来的,所以不影响。这里也不考虑**的问题,所以其实初始化可以把p串的开头的*去掉,并且把连续的*去掉

这里记录c++11新特新,即lamda表达式,不用在函数外再写一个大函数了,,,

auto matches = [&](int i, int j) 
{
    if (i == 0) 
        return false;
    if (p[j - 1] == '.') 
        return true;
    return s[i - 1] == p[j - 1];
};

使用引用捕获一切的Lambda表达式,Lambda表达式的结构 [ capture list ] (parameters) -> return-type { method definition}内部会用到一些外部的变量,这个叫做捕获,可以显式写在[]内,也可以隐式捕获它们。 []内有不同的情况,空的表示不捕获,[=]表示采取copy的方式捕获一切,[&]表示采取引用的方式捕获一切。 详情可以参考

https://www.cnblogs.com/kekec/p/10904802.html

to be continued......

猜你喜欢

转载自blog.csdn.net/hbhhhxs/article/details/107759285