"回文子串"
647. 回文子串 题目描述:给你一个字符串 ,请你统计并返回这个字符串中 回文子串 的数目。
回文字符串 是正着读和倒过来读一样的字符串。子字符串 是字符串中的由连续字符组成的一个序列。具有不同开始位置或结束位置的子串,即使是由相同的字符组成,也会被视作不同的子串。
示例1 | 示例2 |
---|---|
输入:s = "abc" 输出: 解释:三个回文子串: "a" , "b" , "c" | 输入:s = "aaa" 输出: 解释: 个回文子串: "a" , "a" , "a" , "aa" , "aa" , "aaa" |
中规中矩的动态规划
1、确定 dp 状态数组
定义 为字符串 在 区间內的子串是否是回文子串,其中 ,。
2、确定 dp 状态方程
需要比较 与 对应的字符是否相等:
当 时,那么字符串 在 区间內的子串 一定不是 回文子串,即 ;
当 时,需要比较索引 与 :
-
当 时,代表指向同一个字符(且仅有一个字符),那么字符串 在 区间內的子串 一定是 回文子串(单独的字符一定是回文串),即 ;
-
当 时,代表索引 与 指向相邻的字符,那么字符串 在 区间內的子串 一定是 回文子串(相邻字符相同,一定也是回文串),即 ;
-
当 时,代表索引 与 之间的元素(不包括 与 指向的元素)至少有一个,那么此时字符串 在 区间內的子串是否是回文子串(),完全取决于字符串 在 区间內的子串是否是回文子串(),即 。
3、确定 dp 初始状态
声明一个 的数组,起始每个元素均为 。
NOTE: 我们不需要初始化 ,其中 ,因为在状态方程递推关系中会逐一计算的。
4、确定遍历顺序
在状态方程递推关系中, 依赖了 这个元素,因此遍历 时,应 倒序遍历。而遍历 时应正序,但要保证 。
-
外层循环倒序遍历 ,即从 到 ;
-
内层循环正序遍历 ,即从 到 。
举个例子,计算 时,可能要判断 ,如果是正序遍历,此时并没有计算到 ,那么 还是初始值 ,这样计算的结果一定是错误的。
5、确定最终返回值
返回 数组中为 的元素个数。
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: 全局记录一个 ,初始值为 ,当 时,执行 ,最后返回 ,这样就无需再遍历一遍 状态数组来查询值为 的元素个数。