本文涉及知识点
滚动哈希
LeetCode2156. 查找给定哈希值的子串
给定整数 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” 更晚出现。
提示:
1 <= k <= s.length <= 2 * 104
1 <= power, modulo <= 109
0 <= hashValue < modulo
s 只包含小写英文字母。
测试数据保证一定 存在 满足条件的子串。
滚动哈希
vP[i] = poweri
vHash[i] = s[0]*vP[i-1] + s[1]*vP[i-2]
⋯
\cdots
⋯ s[i-1]*vP[0] 即s[0…i-1]逆序的hash。
即
∑
j
:
0
i
−
1
(
s
[
j
]
×
v
P
[
i
−
1
−
j
]
)
\sum_{j:0}^{i-1}(s[j]\times vP[i-1-j])
∑j:0i−1(s[j]×vP[i−1−j])
vHash[i]*vP[len] =
∑
j
:
0
i
−
1
(
s
[
j
]
∗
v
P
[
i
−
1
+
l
e
n
−
j
]
)
\sum_{j:0}^{i-1}(s[j]*vP[i-1+len-j])
∑j:0i−1(s[j]∗vP[i−1+len−j]) 式子一
vHash[i+len-1] =
∑
j
:
0
i
+
l
e
n
−
1
(
s
[
j
]
×
v
P
[
i
+
l
e
n
−
1
−
j
]
)
\sum_{j:0}^{i+len-1}(s[j]\times vP[i+len-1-j])
∑j:0i+len−1(s[j]×vP[i+len−1−j]) 式子二
式子二减去式子一
∑
j
:
i
i
+
l
e
n
−
1
(
s
[
j
]
×
v
P
[
i
+
l
e
n
−
1
−
j
]
)
\sum_{j:i}^{i+len-1}(s[j]\times vP[i+len-1-j])
∑j:ii+len−1(s[j]×vP[i+len−1−j])
令k = j - i
∑
k
:
0
l
e
n
−
1
(
s
[
k
+
i
]
×
v
P
[
l
e
n
−
k
−
1
]
)
\sum_{k:0}^{len-1}(s[k+i] \times vP[len-k-1])
∑k:0len−1(s[k+i]×vP[len−k−1])
令i1 = len-1
删除s的前i个字符:
即:
∑
j
:
0
i
1
(
s
[
k
]
×
v
P
[
i
1
−
j
]
)
\sum_{j:0}^{i1}(s[k]\times vP[i1-j])
∑j:0i1(s[k]×vP[i1−j]) 即删除前i+1字符后vHash[len-1]
即hash(s.substr(i,len), p, m) 即s[i…i+len-1]的逆序的哈希。
令s的逆序为s1,则s[l…i+len-1]在s1对应的下标为[n -1- (i+len-1),n-1-i] 换算成左闭右开就是:[n -1- (i+len-1),n-1-i+1)
代码
核心代码
namespace TMP {
class CHashStr {
public:
CHashStr(int mod,string s, int iCodeNum, int iCodeBegin = 1, char chBegin = 'a'):MOD(mod){
m_c = s.length();
m_vP.resize(m_c + 1);
m_vP[0] = 1;
m_vHash.resize(m_c + 1);
const int P = iCodeBegin + iCodeNum;
for (int i = 0; i < m_c; i++)
{
m_vHash[i + 1] = ( ((long long)m_vHash[i] * P)%MOD + s[i] - chBegin + iCodeBegin)% MOD;
m_vP[i + 1] =((long long) m_vP[i] * P) % MOD;
}
}
int GetHashExincludeRight(int left, int right)
{
const auto i1 = ((long long)m_vHash[left] * m_vP[right - left]) % MOD;
const int i2 = (m_vHash[right] - i1 + MOD) % MOD;
return i2;
}
int m_c;
vector<int> m_vP;
vector<int> m_vHash;
const int MOD;
};
}
class Solution {
public:
string subStrHash(string s, int power,const int modulo, int k, int hashValue) {
const int N = s.length();
TMP::CHashStr has(modulo,string(s.rbegin(),s.rend()),power-1);
for (int i = 0; i + k <= N; i++) {
const auto cur = has.GetHashExincludeRight(N-1-(i+k-1),N-1-i+1);
if (cur == hashValue) {
return s.substr(i, k);
}
}
return "";
}
};
单元测试
template<class T1, class T2>
void AssertEx(const T1& t1, const T2& t2)
{
Assert::AreEqual(t1, t2);
}
template<class T>
void AssertEx(const vector<T>& v1, const vector<T>& v2)
{
Assert::AreEqual(v1.size(), v2.size());
for (int i = 0; i < v1.size(); i++)
{
Assert::AreEqual(v1[i], v2[i]);
}
}
template<class T>
void AssertV2(vector<vector<T>> vv1, vector<vector<T>> vv2)
{
sort(vv1.begin(), vv1.end());
sort(vv2.begin(), vv2.end());
Assert::AreEqual(vv1.size(), vv2.size());
for (int i = 0; i < vv1.size(); i++)
{
AssertEx(vv1[i], vv2[i]);
}
}
namespace UnitTest
{
string s;
int power, modulo, k, hashValue;
TEST_CLASS(UnitTest)
{
public:
TEST_METHOD(TestMethod01)
{
s = "eet", power = 7, modulo = 20, k = 2, hashValue = 0;
auto res = Solution().subStrHash(s, power, modulo, k, hashValue);
AssertEx(string("ee"), res);
}
TEST_METHOD(TestMethod00)
{
s = "lee", power = 7, modulo = 20, k = 2, hashValue = 0;
auto res = Solution().subStrHash(s, power, modulo, k, hashValue);
AssertEx(string("ee"), res);
}
TEST_METHOD(TestMethod0)
{
s = "leetcode", power = 7, modulo = 20, k = 2, hashValue = 0;
auto res = Solution().subStrHash(s, power, modulo, k, hashValue);
AssertEx(string("ee"), res);
}
TEST_METHOD(TestMethod1)
{
s = "fbxzaad", power = 31, modulo = 100, k = 3, hashValue = 32;
auto res = Solution().subStrHash(s, power, modulo, k, hashValue);
AssertEx(string("fbx"), res);
}
};
}
扩展阅读
视频课程
先学简单的课程,请移步CSDN学院,听白银讲师(也就是鄙人)的讲解。
https://edu.csdn.net/course/detail/38771
如何你想快速形成战斗了,为老板分忧,请学习C#入职培训、C++入职培训等课程
https://edu.csdn.net/lecturer/6176
相关推荐
我想对大家说的话 |
---|
《喜缺全书算法册》以原理、正确性证明、总结为主。 |
按类别查阅鄙人的算法文章,请点击《算法与数据汇总》。 |
有效学习:明确的目标 及时的反馈 拉伸区(难度合适) 专注 |
闻缺陷则喜(喜缺)是一个美好的愿望,早发现问题,早修改问题,给老板节约钱。 |
子墨子言之:事无终始,无务多业。也就是我们常说的专业的人做专业的事。 |
如果程序是一条龙,那算法就是他的是睛 |
测试环境
操作系统:win7 开发环境: VS2019 C++17
或者 操作系统:win10 开发环境: VS2022 C++17
如无特殊说明,本算法用**C++**实现。