持续创作,加速成长!这是我参与「掘金日新计划 · 6 月更文挑战」的第10天,点击查看活动详情
问题
把字符串 s 看作是 “abcdefghijklmnopqrstuvwxyz” (也就是a-z字母连续)的无限环绕字符串,所以 s 看起来是这样的:
"...zabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcd...." . 现在给定另一个字符串 p 。返回 s 中 唯一 的 p 的 非空子串 的数量。
题解
这个题目需要我们找出字符串 p 中有多少个子串在 s 中,由于 s 是按a-z顺序周期排列的,所以可以理解为求 p 中有多少个子串是连续的。
由于 s 是周期字符串,对于在 s 中的子串,只要知道子串的最后一个字符和子串长度,就能确定这个子串。例如子串以 ‘b’ 开始,长度为 3,那么该子串为 “bcd”。
题目要求不同的子串个数,那么对于两个以同一个字符结尾的子串,长的那个子串必然包含短的那个。例如 “abcd” 和 “bcd” 均以 ‘d’ 结尾,“bcd” 是 “abcd” 的子串。所以两个子串间是有相关联的,因此我们可以使用 动态规划 的方法来解决这个问题。
dp数组
首先定义dp数组,我们用 dp[i] 代表 p 中以字符 i 结尾且在 s 中的子串的最长长度,知道了最长长度,也就知道了不同的子串的个数。 因为字母有26个,所以 dp.length 为 26,而且当 i === 0时代表字符 ‘a’ ,i === 25 时,字符为‘z’,可以通过 charCodeAt方法获取到字符的Unicode 编码,减去 ‘a’ 的Unicode 编码,就是这个字符在 dp中的位置。
初始时,dp[0] ~ dp[25] 的值为 0
状态转移方程
因为dp[i] 存的是最长长度,所以我们的状态转移方程可以为
dp[i] = Math.max(dp[i], 当前连续子串长度cur)
cur我们可以通过遍历字符串 p 来计算,如果当前字符串和前一个字符串是连续的,就使 cur++,否则cur重制为 1
代码
var findSubstringInWraproundString = function(p) {
const dp = new Array(26).fill(0);
let cur = 0;
for (let i = 0; i < p.length; i++) {
if(i > 0 && (p[i].charCodeAt() - p[i-1].charCodeAt() === 1 || p[i].charCodeAt() - p[i-1].charCodeAt() === -25)) {
// 如果当前字符 比 上一个字符的编码多 1,就代表,他们是连续的,
// 如果他们之间的差是 -25,就代表 当前是 a 上一个是 z,也是连续的
cur++
} else {
cur = 1;
}
const aCode = 'a'.charCodeAt(); // 初始的 unicode 编码
dp[p[i].charCodeAt() - aCode] = Math.max(dp[p[i].charCodeAt() - aCode], cur)
}
const sum = dp.reduce((a,b) => a+b, 0)
return sum
};