真实面试案例分析(下)
摘要
- 字符串查找strStr
- 为什么不需要学KMP算法
- Rabin-karp算法与哈希函数Hash Function
- 为什么我们不需要学习贪心法
- 三个你必须知道的语言知识
- 如何判断两个字符串是否相等
- 如何遍历字符串
- null和
""
有什么区别
- 十个你必须掌握的字符串处理函数
13 · 字符串查找
O(n^2) Best Practise
/**
* @param source:
* @param target:
* @return: return the index
*/
func StrStr(haystack string, needle string) int {
// 在剩余长度不足时直接退出,是否加1可以代特殊值
for i := 0; i < len(haystack)-len(needle)+1; i++ {
equal := true
// 写 for 循环比较,不需要额外空间
for j := 0; j < len(needle); j++ {
if haystack[i+j] != needle[j] {
equal = false
break
}
}
if equal {
return i
}
}
return -1
}
594 · 字符串查找 II
Robin karp
时间复杂度 O(n+m)
- 哈希函数:
- 通过将字符串转换为一个基于位置的数值来计算哈希值。为了避免溢出,这里使用了模运算(模数选择为101,一个质数)。
h
是一个辅助变量,用于调整窗口哈希值的计算。
- 初始哈希值计算:
- 计算
needle
和haystack
的初始窗口的哈希值。窗口大小等于needle
的长度。
- 计算
- 滑动窗口:
- 遍历
haystack
,对每个窗口的哈希值进行比较。如果两个哈希值相等,则逐字符检查两个字符串是否完全匹配。 - 如果哈希值不同,直接计算下一个窗口的哈希值,而不必重新计算整个窗口,从而提高效率。
- 遍历
- 哈希冲突:
- 虽然哈希值相同并不一定意味着两个字符串相同(存在哈希冲突),但对于大多数场景,这种算法是有效的。当哈希值相等时,逐字符比较确保准确性。
Rabin-Karp算法特别适合用于在大文本中搜索多个模式,因为哈希值的比较通常比逐字符比较更快。
func StrStr2(haystack string, needle string) int {
// 如果 needle 是空字符串,返回0
if len(needle) == 0 {
return 0
}
// 如果 haystack 的长度小于 needle 的长度,返回-1
if len(haystack) < len(needle) {
return -1
}
// 定义哈希相关的常量
const base = 256
const mod = 101 // 选择一个合适的质数作为模数
// 计算 needle 的哈希值
needleHash := 0
haystackHash := 0
h := 1
// 为了防止乘出来的数太大,边乘边模
for i := 0; i < len(needle)-1; i++ {
h = (h * base) % mod
}
for i := 0; i < len(needle); i++ {
needleHash = (base*needleHash + int(needle[i])) % mod
haystackHash = (base*haystackHash + int(haystack[i])) % mod
}
// 滑动窗口进行匹配
for i := 0; i <= len(haystack)-len(needle); i++ {
// 如果哈希值相同,进一步检查实际的字符串
if needleHash == haystackHash {
match := true
for j := 0; j < len(needle); j++ {
if haystack[i+j] != needle[j] {
match = false
break
}
}
if match {
return i
}
}
// 计算下一个窗口的哈希值
if i < len(haystack)-len(needle) {
haystackHash = (base*(haystackHash-int(haystack[i])*h) + int(haystack[i+len(needle)])) % mod
if haystackHash < 0 {
haystackHash += mod
}
}
}
return -1
}
本作品采用知识共享署名-非商业性使用-相同方式共享 4.0 国际许可协议进行许可。