647. 回文子串 (palindromic substrings)

3,324 阅读3分钟

"回文子串"

647. 回文子串 题目描述:给你一个字符串 ss ,请你统计并返回这个字符串中 回文子串 的数目。

回文字符串 是正着读和倒过来读一样的字符串。子字符串 是字符串中的由连续字符组成的一个序列。具有不同开始位置或结束位置的子串,即使是由相同的字符组成,也会被视作不同的子串。1<=s.length<=10001 <= s.length <= 1000

示例1示例2
输入s = "abc"
输出33
解释:三个回文子串: "a", "b", "c"
输入s = "aaa"
输出66
解释66 个回文子串: "a", "a", "a", "aa", "aa", "aaa"

中规中矩的动态规划

1、确定 dp 状态数组

定义 dp[i][j]dp[i][j] 为字符串 ss[i,j][i,j] 区间內的子串是否是回文子串,其中 0ij<n 0 \le i \le j \lt nn=s.lengthn = s.length

2、确定 dp 状态方程

需要比较 s[i]s[i]s[j]s[j] 对应的字符是否相等:

s[i]!=s[j]s[i] != s[j] 时,那么字符串 ss[i,j][i,j] 区间內的子串 一定不是 回文子串,即 dp[i][j]=falsedp[i][j] = false

s[i]==s[j]s[i] == s[j] 时,需要比较索引 iijj

  • i==ji == j 时,代表指向同一个字符(且仅有一个字符),那么字符串 ss[i,j][i,j] 区间內的子串 一定是 回文子串(单独的字符一定是回文串),即 dp[i][j]=truedp[i][j]=true

  • i=j1i = j - 1 时,代表索引 iijj 指向相邻的字符,那么字符串 ss[i,j][i,j] 区间內的子串 一定是 回文子串(相邻字符相同,一定也是回文串),即 dp[i][j]=truedp[i][j]=true

  • ji>1j - i > 1 时,代表索引 iijj 之间的元素(不包括 iijj 指向的元素)至少有一个,那么此时字符串 ss[i,j][i,j] 区间內的子串是否是回文子串(dp[i][j]dp[i][j]),完全取决于字符串 ss[i+1,j1][i+1,j-1] 区间內的子串是否是回文子串(dp[i+1][j1]dp[i+1][j - 1]),即 dp[i][j]=dp[i+1][j1]dp[i][j] = dp[i + 1][j - 1]

3、确定 dp 初始状态

声明一个 n×nn \times n 的数组,起始每个元素均为 falsefalse

NOTE: 我们不需要初始化 dp[k][k]=truedp[k][k] = true,其中 k[0,n)k \in [0,n),因为在状态方程递推关系中会逐一计算的。

4、确定遍历顺序

在状态方程递推关系中,dp[i][j]dp[i][j] 依赖了 dp[i+1][j1]dp[i+1][j -1] 这个元素,因此遍历 ii 时,应 倒序遍历。而遍历 jj 时应正序,但要保证 jij \ge i

  • 外层循环倒序遍历 ii,即从 i=n1i = n -1i=0i = 0

  • 内层循环正序遍历 jj,即从 j=ij = ij=n1j = n - 1

举个例子,计算 dp[0][3]dp[0][3] 时,可能要判断 dp[1][2]dp[1][2],如果是正序遍历,此时并没有计算到 dp[1][2]dp[1][2],那么 dp[1][2]dp[1][2] 还是初始值 falsefalse,这样计算的结果一定是错误的。

5、确定最终返回值

返回 dpdp 数组中为 truetrue 的元素个数。

6、代码示例

/**
 * 空间复杂度 O(n^2),n是字符串的长度
 * 时间复杂度 O(n^2)
 */
 function countSubstrings(s: string): number {
    const n = s.length;
    const dp = Array.from({ length: n }, () => new Array(n).fill(false));

    for (let i = n - 1; i >= 0; i--) {
        for (let j = i; j < n; j++) {
            if (s[i] !== s[j]) continue;

            if (j - i <= 1) {
                dp[i][j] = true;
            } else if (dp[i + 1][j - 1]) {
                dp[i][j] = true;
            }
        }
    }

    return dp.reduce((acc, arr) => acc + arr.filter(Boolean).length, 0);
};

NOTE: 全局记录一个 resres,初始值为 00,当 dp[i][j]=truedp[i][j] = true 时,执行 res++res++,最后返回 resres,这样就无需再遍历一遍 dpdp 状态数组来查询值为 truetrue 的元素个数。

参考

# 重识动态规划