「这是我参与2022首次更文挑战的第16天,活动详情查看:2022首次更文挑战」
2156. 查找给定哈希值的子串
题目
给定整数 p 和 m ,一个长度为 k 且下标从 0 开始的字符串 s 的哈希值按照如下函数计算:
hash(s,p,m)=(val(s[0])∗p0+val(s[1])∗p1+...+val(s[k−1])∗pk−1)mod m;
其中 val(s[i]) 表示 s[i] 在字母表中的下标,从 val(′a′)=1 到 val(′z′)=26 。
给你一个字符串 s 和整数 power,modulo,k 和 hashValue 。请你返回 s 中 第一个 长度为 k 的 子串 sub ,满足 hash(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=0
s="leetcode"==>s=[12,5,5,20,3,16,4,5]
因为要取连续非空字符组成的序列,所以滑动窗口大小为 k
le=>(12∗p0+5+p1)%m
ee=>(5∗p0+5+p1)%m
简单模拟的情况是每次移动窗口,从新计算窗口中的值,但是这个操作时间复杂度 O(n∗k) ,数据量不允许使用这种方式
咋么办?
看一下计算公式:x1∗p0+x2∗p1+x3∗p3+...+xk−1∗pk−1
这里面 p0,p1,...,pk−1 这么又规律,可不可以应用一下
实验一下:
s=′x1x2x3′=[x1,x2,x3],窗口长度为2
x1x2=x1∗p0+x2∗p1
x2x3=x2∗p0+x3∗p1=(x1x2−x1)/p+x3∗p1
看来是可以用到 p0,p1,...,pk−1 这个规律的;但是这里又引入了一个问题
x2x3=x2∗p0+x3∗p1=(x1x2−x1)/p+x3∗p1 这个公式中含有除法
(x2x3=x2∗p0+x3∗p1)%m!=((x1x2)%m−(x1%m))/p%m+(x3∗p1)%m
取余里面不能有除法,做除法时先mod再除再mod结果会改变。竞赛时就卡在这上面,一直没通过
如何去除公式中的除法?很简单,倒序字符串
x2x3=(x2∗p0+x3∗p1)%m
x1x1=(x1∗p0+x2∗p1)%m=((x2x3)%m−(x3∗p1)%m)∗p+x1
公式中已经消除了除法,求余结果不受影响了
其他需要注意的点:
- 题目给出的数据量太大,js编辑代码要使用 BigInt这个方法存放大数
- pk−1 不要使用 Math.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)
}
}