[路飞]_leetcode第278周竞赛(中)

156 阅读3分钟

「这是我参与2022首次更文挑战的第16天,活动详情查看:2022首次更文挑战

2156. 查找给定哈希值的子串

题目

给定整数 p 和 m ,一个长度为 k 且下标从 0 开始的字符串 s 的哈希值按照如下函数计算:

hash(s,p,m)=(val(s[0])p0+val(s[1])p1+...+val(s[k1])pk1)hash(s, p, m) = (val(s[0]) * p0 + val(s[1]) * p1 + ... + val(s[k-1]) * pk-1)mod mm;

其中 val(s[i])val(s[i]) 表示 s[i] s[i]  在字母表中的下标,从 val(a)=1val('a') = 1 到 val(z)=26 val('z') = 26 

给你一个字符串 ss 和整数 powermodulok 和 hashValuepower,modulo,k 和 hashValue 。请你返回 ss  中 第一个 长度为 kk  的 子串 subsub  ,满足 hash(sub,power,modulo)==hashValuehash(sub, power, modulo) == hashValue 。

测试数据保证一定 存在 至少一个这样的子串。 子串 定义为一个字符串中连续非空字符组成的序列。

示例1

输入:s = "leetcode", power = 7, modulo = 20, k = 2, hashValue = 0
输出:"ee"
解释:"ee" 的哈希值为 hash("ee", 7, 20) = (5 * 1 + 5 * 7) mod 20 = 40 mod 20 = 0"ee" 是长度为 2 的第一个哈希值为 0 的子串,所以我们返回 "ee"

示例2

输入:s = "fbxzaad", power = 31, modulo = 100, k = 3, hashValue = 32
输出:"fbx"
解释:"fbx" 的哈希值为 hash("fbx", 31, 100) = (6 * 1 + 2 * 31 + 24 * 312) mod 100 = 23132 mod 100 = 32"bxz" 的哈希值为 hash("bxz", 31, 100) = (2 * 1 + 24 * 31 + 26 * 312) mod 100 = 25732 mod 100 = 32"fbx" 是长度为 3 的第一个哈希值为 32 的子串,所以我们返回 "fbx" 。
注意,"bxz" 的哈希值也为 32 ,但是它在字符串中比 "fbx" 更晚出现。

前言

题目描述是真的难懂,看了示例才明白这题要干什么。吐槽一下,哈哈

题解

滑动窗口 + 取余规则 + 倒序

分析示例:s="leetcode",power=7,modulo=20,k=2,hashValue=0s = "leetcode", power = 7, modulo = 20, k = 2, hashValue = 0

s="leetcode"==>s=[12,5,5,20,3,16,4,5]s = "leetcode" ==> s = [12,5,5,20,3,16,4,5]
因为要取连续非空字符组成的序列,所以滑动窗口大小为 kk

le=>(12p0+5+p1)%mle=> (12 * p^0 + 5 + p^1) \% m

ee=>(5p0+5+p1)%mee=> (5 * p^0 + 5 + p^1) \% m

简单模拟的情况是每次移动窗口,从新计算窗口中的值,但是这个操作时间复杂度 O(nk)O(n*k) ,数据量不允许使用这种方式

咋么办?

看一下计算公式:x1p0+x2p1+x3p3+...+xk1pk1x_1 * p^0 + x_2 * p^1 + x_3 * p^3 + ... + x_{k-1} * p^{k-1}
这里面 p0,p1,...,pk1p^0 ,p^1 ,..., p^{k-1} 这么又规律,可不可以应用一下

实验一下:

s=x1x2x3=[x1,x2,x3]s = 'x_1x_2x_3' = [x_1,x_2,x_3],窗口长度为2

x1x2=x1p0+x2p1x_1x_2 = x_1 * p^0 + x_2 * p^1
x2x3=x2p0+x3p1=(x1x2x1)/p+x3p1x_2x_3 = x_2 * p^0 + x_3 * p^1 =( x_1x_2 - x_1 )/p + x3 * p^1

看来是可以用到 p0,p1,...,pk1p^0 ,p^1 ,..., p^{k-1} 这个规律的;但是这里又引入了一个问题

x2x3=x2p0+x3p1=(x1x2x1)/p+x3p1x_2x_3 = x_2 * p^0 + x_3 * p^1 =( x_1x_2 - x_1 )/p + x3 * p^1 这个公式中含有除法
(x2x3=x2p0+x3p1)%m!=((x1x2)%m(x1%m))/p%m+(x3p1)%mx_2x_3 = x_2 * p^0 + x_3 * p^1)\%m != ( (x_1x_2)\%m - (x_1 \%m) )/p \%m + (x3 * p^1 )\%m

取余里面不能有除法,做除法时先mod再除再mod结果会改变。竞赛时就卡在这上面,一直没通过

如何去除公式中的除法?很简单,倒序字符串

x2x3=(x2p0+x3p1)%mx_2x_3 = (x_2 * p^0 + x_3 * p^1) \%m
x1x1=(x1p0+x2p1)%m=((x2x3)%m(x3p1)%m)p+x1x_1x_1 = (x_1 * p^0 + x_2 * p^1) \%m = \left((x_2x_3) \% m - (x_3 * p^1)\%m \right) * p + x_1

公式中已经消除了除法,求余结果不受影响了

其他需要注意的点:

  • 题目给出的数据量太大,js编辑代码要使用 BigInt这个方法存放大数
  • pk1p^k-1 不要使用 Math.powMath.pow方法,结果太大,js放不下,最好使用迭代
  • 多个符合条件的字符串返回第一个,倒序枚举到0的位置再输出
  • 倒序记录最后一个符合条件的字符串下标即可,在输出的时候根据下标获取字符串,否则可能会超时

根据上述思路编辑代码如下:

var subStrHash = function (s, power, modulo, k, hashValue) {
  const p = BigInt(power)
  const m = BigInt(modulo)
  const h = BigInt(hashValue)

  let base = 1n
  for (let i = 0; i < k - 1; i++) {
    base = (base * p) % m
  }

  const len = s.length
  let total = 0n
  let index = len - 1
  for (let i = len - 1; i >= 0; i--) {
    const n = val(s[i])
    total = (total * p + n) % m
    if (i <= len - k) {
      if (total % m === h) {
        index = i
      }
      total = (total - base * val(s[i + k - 1])) % m
      while (total < 0n) {
        total += m
      }
    }
  }
  return s.substr(index, k)

  function val(s) {
    return BigInt(s.charCodeAt() - 'a'.charCodeAt() + 1)
  }
}