动态规划-两个数组的dp问题2


1. 不同的子序列(115)

题目描述:
在这里插入图片描述

状态表示:
根据题意这里的dp数组可以定义为二维,并且dp[i][j]表示字符串t的0到i的区间的子串在字符串s的0到j区间的子串的子序列中出现的次数也就是匹配次数。
状态转移方程:
将这里的状态分为两种情况,第一种情况就是当s的0到j这个区间的子序列不以j位置元素为结尾,那么dp[i][j]可以表示为dp[i][j]=dp[i][j-1],因为在这种情况下s的子序列不以j位置为结尾,那么直接将字符串r的指定区间内的子串和s的0到j-1区间的子序列进行匹配即可。第二种情况就是s字符串的0到j的区间内的子序列以j位置元素为结尾,那么当j位置元素与i位置元素相等时就可以得到dp[i][j]=dp[i][j],因为在此时t的子串最后一位元素已经和s的0到j区间的子序列最后一位元素相等了,那么直接去考虑前面的情况,正好前面的情况可以直接表示。在这种状态分类下,两者的结果要相加,具体看代码。
初始化:
初始化为了防止越界,给dp这个二维数组加上一行一列。然后这一行一列从逻辑上很好给他们赋值,因为加上dp的是第0行和第0列,所以逻辑上可以将其理解为s和t分别为空串的情况,当t为空串时,s中可以和其匹配只有一种可能那就是也是空串,所以第0行都赋为1。当s为空串时,其中肯定没有跟t匹配的子序列了,因此第一列直接全赋为0即可。.
然后还有一个细节问题就是字符串元素的映射,因为我们加长了数组,所以可以在字符串前面都加上一个空白字符来达到准确的字符串字符的映射。
填表顺序:
根据状态转移方程可以得到填表顺序为从上到下,从左到右。
返回值:
返回值返回dp[m][n]即可,这就是题目要求得到的结果。
代码如下:

class Solution {
    public int numDistinct(String s, String t) {
        int m = t.length();
        int n = s.length();

        int[][] dp = new int[m + 1][n + 1];

        for (int i = 0; i <= n; i++) {
            dp[0][i] = 1;
        }

        t = " " + t;
        s = " " + s;

        for (int i = 1; i <= m; i++) {
            for (int j = 1; j <= n; j++) {
                dp[i][j] += dp[i][j - 1];
                if (t.charAt(i) == s.charAt(j)) {
                    dp[i][j] += dp[i - 1][j - 1];
                }

            }
        }

        return dp[m][n];

    }
}

题目链接
时间复杂度:O(N^2)
空间复杂度:O(N^2)

2. 通配符匹配(44)

题目描述:
在这里插入图片描述

状态表示:
使用二维数组dp,使用dp[i][j]表示s在区间0-i上的子串能否和p在0-j上的子串相匹配。
状态转移方程:
对于这种两个数组的dp问题,都会对两个子串的最后一个元素进行考虑,因此这里的状态转移可以分为三种情况。第一种情况当模式串p的子串最后一个元素是一个普通的字符时,那么当s[i]==p[j]时,dp[i][j]=dp[i-1][j-1]。第二种情况当模式串的子串最后一个元素是?时,?可以匹配任何一个单个字符,所以此时的状态转移方程为dp[i][j]=dp[i-1][j-1]。第三种情况时模式串p的子串最后一个元素为*时,因为可以匹配任意个子符,所以理论上这里有很多种情况,当不匹配字符时,dp[i][j]=dp[i][j-1],当匹配一个字符时,dp[i][j]=dp[i-1][j-1],当匹配两个字符时dp[i][j]=dp[i-2][j-1]等等。
事实上第三种情况可以使用循环处理,但是效率不高,因此这里使用数学的方法对第三种情况进行优化。通过上面的分析我们知道在第三种情况下dp[i][j]=dp[i][j-1] || dp[i-1][j-1] || dp[i-2][j-1]…那么这里我们再分析dp[i-1][j]的一个状态转移方程,再第三种情况下,不难发现dp[i-1][j]=dp[i-1][j-1] || dp[i-2][j-1] || dp[i-3][j-1]…通过这两个式子的相似之处我们就可以得到dp[i][j]=dp[i][j-1] || dp[i-1][j]。

初始化:
为了避免数组越界也就是方便我们进行运算,给dp二维数组加上一行和一列,对于第一行在逻辑上的意味就是对于s子串是空串的情况下是否能够和模式串p进行匹配,模式串p的子串是空串只有在它的字符全部是*的情况下才可以成立,因此第一行我们使用一个循环来进行处理。对于第一列意味着p的子串为空串的情况,此时当然是无法匹配的,全部赋为false即可。第一行和第一列的交界处比较特殊,此时p的子串和s的子串都是空串,因此是匹配的,所以dp[0][0]赋为true。

填表顺序:
从上到下,从左至右。
返回值:
dp[m][n]。
代码如下:

class Solution {
    public boolean isMatch(String s, String p) {
        int m = s.length();
        int n = p.length();

        boolean[][] dp = new boolean[m + 1][n + 1];
        dp[0][0] = true;

        s = " " + s;
        p = " " + p;

        for (int i = 1; i <= n; i++) {
            if (p.charAt(i) == '*') {
                dp[0][i] = true;
            } else {
                break;
            }
        }

        for (int i = 1; i <= m; i++) {

            for (int j = 1; j <= n; j++) {
                if (p.charAt(j) == '?') {
                    dp[i][j] = dp[i - 1][j - 1];
                } else if (p.charAt(j) == '*') {

                    if (dp[i][j - 1] || dp[i - 1][j]) {
                        dp[i][j] = true;
                    }

                } else {
                    if (s.charAt(i) == p.charAt(j)) {
                        dp[i][j] = dp[i - 1][j - 1];
                    }
                }

            }

        }
        return dp[m][n];
    }
}

题目链接
时间复杂度:O(N^2)
空间复杂度:O(N^2)

3. 正则表达式匹配(10)

题目描述:
在这里插入图片描述

状态表示:
建立一个二维数组dp,使用dp[i][j]表示在字符串s的0到i区间内子串是否能够与字符串p的0到j区间的子串相匹配。
状态转移方程:
状态转移方程这里分为三种情况,第一种情况是p字符串的最后一个元素为小写字母,如果p[j]==s[i],那么dp[i][j]=dp[i-1][j-1],因为此时两个末尾元素已经匹配掉了,所以只需要考虑前面的元素即可。第二种情况是p字符串的最后一个元素为.字符,因为.字符可以替换为任意一个字母,所以此时dp[i][j]可以表示为dp[i-1][j-1].

第三种情况为p字符串的最后一个元素为*字符,此时又要分为两种情况,第一种情况j-1位置的元素为.字符,此时j和j-1两个位置的字符可以去匹配空串,那么dp[i][j]=dp[i][j-2],或者去匹配s字符串的倒数第一个字符dp[i][j]=dp[i-1][j-2],或者去匹配s字符串的倒数两个字符dp[i][j]=dp[i-2][j-2]等等,通过数学方法可以将此种情况总结为dp[i][j]=dp[i][j-2] || dp[i-1][j]。第二种情况是况j-1位置的元素为普通的字母字符,分析跟前一种情况类似,首先是最后两个字符来匹配空串,然后是最后两个字符去匹配s串的倒数第一个元素,最后两个字符去匹配s串的倒数后两个元素等等,不过这里和前面不同的是,这里的匹配首先要保证元素相等,比如p串的最后两个字符去匹配s串的倒数第一个字符,就需要p串的倒数第二个字符和s串的倒数第一个字符相等。总之状态转移方程为dp[i][j]=(s[i]==p[j])&&dp[i-1][j]。

初始化:
这里的初始化还是一样,就是给dp数组加上一行和一列,分别代表s和p串为空串时的情况。当p串为空时,s肯定是无法匹配的,所以第一列赋为false。当s串为空时就比较特殊了,要考虑*号出现的情况,如果满足条件需要赋为true,具体看代码。dp[0][0]表示s和p都为空串时的情况,所以自然为true。

填表顺序:
从上至下,从左至右。
返回值:
dp[m][n]。
代码如下:

class Solution {
    public boolean isMatch(String s, String p) {
        int m = s.length();
        int n = p.length();

        boolean[][] dp = new boolean[m + 1][n + 1];
        dp[0][0] = true;

        for (int i = 2; i <= n; i += 2) {
            if (p.charAt(i - 1) == '*') {
                dp[0][i] = true;
            } else {
                break;
            }
        }

        s = " " + s;
        p = " " + p;

        for (int i = 1; i <= m; i++) {
            for (int j = 1; j <= n; j++) {
                if (p.charAt(j) == '*') {
                    if (dp[i][j - 2]) {
                        dp[i][j] = true;
                        continue;
                    }
                    if (p.charAt(j - 1) == '.' || p.charAt(j - 1) == s.charAt(i)) {
                        dp[i][j] = dp[i - 1][j];
                    }
                } else if(p.charAt(j)=='.'|| p.charAt(j)==s.charAt(i)){
                    dp[i][j] = dp[i - 1][j - 1];
                }
            }
        }

        return dp[m][n];
    }

}

题目链接
时间复杂度:O(N^2)
空间复杂度:O(N^2)

4. 交错字符串(97)

题目描述:
在这里插入图片描述

状态表示:
根据经验以及题目要求,设置一个二维数组dp,使用dp[i][j]表示在s1的0-i区间内的子串以及s2的0到j区间内的子串是否能交错构成s3的0到i+j区间内的子串,显然dp数组就是一个布尔类型的数组。
状态转移方程:
这里还是按照这种两个数组的dp问题的经典思路,就是说对s1和s2的区间内的子串的最后一个元素来进行分析。当s1的0到i的区间的第i个元素与s3的第i+j个元素相等即s1[i]等于s3[i+j]&&dp[i-1][j],那么就可以得到dp[i][j]=true。当s2的0到j区间的第j个元素和s3的第i+j个元素相等即s3[i+j]等于s2[j]&&dp[i][j-1],那么dp[i][j]=true。
初始化:
在编写代码的一开始,我们将三个字符串前面都加上一个空白字符,方便后面进行运算。另外需要对s1和s2的长度进行判断,要是s1和s2相加不等于s3的长度,那么直接返回false即可。然后dp数组要初始化为m+1n+1的形式,m就是s1的长度,n就是s2的长度。对于dp[0][0]直接赋为true,因为此时意味着s1和s2是空串,那么s3也是空船直接可以拼接。对于第一行此时意味着s1为空串,如果此时随着j的增加出现s2中元素与s3中元素不相等的情况,那么后面的值直接赋为false,前面的赋为true。第一列也是一样,意味着s2为空串,此时跟第一行分析一致。
返回值:
因为dp数组为m+1
n+1规模,所以返回值为dp[m][n]。
代码如下:

class Solution {
    public boolean isInterleave(String s1, String s2, String s3) {

        int m = s1.length();
        int n = s2.length();

        if (m + n != s3.length()) {
            return false;
        }

        s1 = " " + s1;
        s2 = " " + s2;
        s3 = " " + s3;

        boolean[][] dp = new boolean[m + 1][n + 1];
        dp[0][0] = true;

        for (int i = 1; i <= m; i++) {
            if (s1.charAt(i) == s3.charAt(i)) {
                dp[i][0] = true;
            } else {
                break;
            }
        }

        for (int i = 1; i <= n; i++) {
            if (s2.charAt(i) == s3.charAt(i)) {
                dp[0][i] = true;
            } else {
                break;
            }
        }

        for (int i = 1; i <= m; i++) {
            for (int j = 1; j <= n; j++) {
                if (s3.charAt(i + j) == s1.charAt(i) && dp[i - 1][j]) {
                    dp[i][j] = true;
                }

                if (s3.charAt(i + j) == s2.charAt(j) && dp[i][j - 1]) {
                    dp[i][j] = true;
                }
            }
        }

        return dp[m][n];
    }
}

题目链接
时间复杂度:O(N^2)
空间复杂度:O(N^2)

  • 21
    点赞
  • 21
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值