Dynamic Programming学习笔记 (35) - 统计只差一个字符的子串数目 (力扣# 1638)

181 阅读2分钟

本题出自力扣题库第1638题。题面大意如下:

给定两个字符串s和t,找出s中的非空子串的数目,这些子串满足替换 一个不同字符以后,是t的子串。 换言之,请你找到s和t串中恰好只有一个字符不同的子字符串对的数目。

示例:

输入:s = "aba", t = "baba"
输出:6
s中的第一个a,t中的第一个b
s中的第一个a,t中的第二个b
s中的第二个a,t中的第一个b
s中的第二个a,t中的第二个b
s中的b,t中的第一个a
s中的b,t中的第二个a

题解:

本题是双字符串输入类的DP应用, 根据题意,我们可以得到如下的DP表达式

F(i,j)
  = F(i - 1, j - 1), s[i]和t[j]相同
  = 1 + 从s[i]和t[j]往前倒数,相同的字符的个数, s[i]和t[j]不同

这里i和j分别时s与t的字符串数组下标,F(i,j)返回的是s中前i个字符的字串与t中前j个字符的字串之间符合题目要求的子串的数量。从题面出发,当s[i]和t[j]两个字符相同时,F(i,j)的返回值就是F(i - 1, j - 1),也就是说s中前i-1个字符的字串与t中前j-1个字符的字串之间符合题目要求的子串的数量;而当s[i]和t[j]两个字符不同时,我们就要从s[i]和t[j]往前倒数,看有多少个相同的字符,加上1后,就是有多少个符合题目要求的子串的数量。

从以上表达式出发,我们定义一个二维数组,并使用双层循环依据s和t的字符串数组下标从小到大依次计算DP数组中各个元素的值,循环完成后,对应s和t最后一个字符位置的DP数组元素中的数值就是问题的答案。

Java代码如下:

class Solution {
    public int countSubstrings(String s, String t) {
        char[] sChars = s.toCharArray();
        char[] tChars = t.toCharArray();

        int sLength = sChars.length;
        int tLength = tChars.length;

        int dp[][] = new int[sLength + 1][tLength + 1];

        int count = 0;
        for (int i = 1; i <= sLength; i ++) {
            for (int j = 1; j <= tLength; j ++) {
                if (sChars[i - 1] == tChars[j - 1]) {
                    dp[i][j] = dp[i - 1][j - 1];
                } else {
                    dp[i][j] = 1;
                    int m = i - 1, n = j -1;
                    while (m >= 1 && n >= 1) {
                        if (sChars[m - 1] == tChars[n - 1]) {
                            dp[i][j] ++;
                            m --;
                            n --;
                        } else {
                            break;
                        }
                    }
                }
                count += dp[i][j];
            }
        }
        return count;
    }
}

以上算法的进一步优化是使用另一个DP数组来减少s[i]和t[j]不同时的计算次数。