持续创作,加速成长!这是我参与「掘金日新计划 · 6 月更文挑战」的第1天,点击查看活动详情
前言
掘金的日新计划更文挑战还是挺不错的!算起来这是第三次参加征文挑战了。不知不觉中也写了很多技术文章,解了很多道LeetCode题目,学到了很多!
题目
把字符串 s 看作是 “abcdefghijklmnopqrstuvwxyz” 的无限环绕字符串,所以 s 看起来是这样的:
"...zabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcd...." .
现在给定另一个字符串 p 。返回 s 中 唯一 的 p 的 非空子串 的数量 。
示例 1:
输入: p = "a"
输出: 1
解释: 字符串 s 中只有一个"a"子字符。
示例 2:
输入: p = "cac"
输出: 2
解释: 字符串 s 中的字符串“cac”只有两个子串“a”、“c”。.
示例 3:
输入: p = "zab"
输出: 6
解释: 在字符串 s 中有六个子串“z”、“a”、“b”、“za”、“ab”、“zab”。
提示:
1 <= p.length <= 10^5p由小写英文字母构成
思考
本题是今天的LeetCode每日一题的题目,难度中等。
首先是读懂题意。我们可以看到,字符串 s是有序的字符串,从a到z无限循环。我们需要给出字符串p的非空子串的数量,也就是说,假如字符串p为cac,非空子串包括a和c,因此答案是2。
考虑到字符串s是有序的,且字符串p可以是cac、abcz、zabx等情况,我们可以定义数组dp,其中dp[α] 表示 p 中以字符 α 结尾且在 s 中的子串的最长长度,为了方便,我们以dp[0]对应字母a,其他字母依此类推。我们知道了以字符 α 结尾的字符串的最长长度,也就知道了字符 α 结尾的不同的子串的个数。
考虑dp[i]和dp[i+1],当dp[i+1]对应的是dp[i]的下一个字母时,我们可以将dp[i]的计数+1,否则计数重新开始,即为1。考虑到字符串p中的字符可以重复,dp[i]我们应该取对应字符的最大长度。
最后,对dp数组的元素进行求和,即为答案。
解答
方法一:动态规划
/**
* @param {string} p
* @return {number}
*/
var findSubstringInWraproundString = function(p) {
const dp = new Array(26).fill(0)
let k = 0
for (let i = 0; i < p.length; ++i) {
// 字符之差为 1 或 -25,比如 ab 或 za
if (i > 0 && (p[i].charCodeAt() - p[i - 1].charCodeAt() + 26) % 26 === 1) {
++k
} else {
k = 1
}
// p 中以字符 c 结尾且在 s 中的子串有 dp[c] 个,字母a对应的是dp[0]
// 当出现重复的字符时,我们要取最大值
dp[p[i].charCodeAt() - 'a'.charCodeAt()] = Math.max(dp[p[i].charCodeAt() - 'a'.charCodeAt()], k)
}
return dp.reduce((prev, current) => prev + current, 0)
}
// 执行用时:68 ms, 在所有 JavaScript 提交中击败了81.48%的用户
// 内存消耗:43 MB, 在所有 JavaScript 提交中击败了46.30%的用户
// 通过测试用例:81 / 81
复杂度分析:
- 时间复杂度:O(n),其中 n 是字符串 p 的长度。
- 空间复杂度:O(|Σ|),其中 |Σ| 为字符集合的大小,本题中字符均为小写字母,故 |Σ|=26。