LeetCode探索(73):467-环绕字符串中唯一的子字符串

153 阅读1分钟

持续创作,加速成长!这是我参与「掘金日新计划 · 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^5
  • p 由小写英文字母构成

思考

本题是今天的LeetCode每日一题的题目,难度中等。

首先是读懂题意。我们可以看到,字符串 s是有序的字符串,从a到z无限循环。我们需要给出字符串p的非空子串的数量,也就是说,假如字符串p为cac,非空子串包括a和c,因此答案是2。

考虑到字符串s是有序的,且字符串p可以是cacabczzabx等情况,我们可以定义数组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。

参考