字符串匹配的sunday算法
28. 找出字符串中第一个匹配项的下标
给你两个字符串 haystack
和 needle
,请你在 haystack
字符串中找出 needle
字符串的第一个匹配项的下标(下标从 0 开始)。如果 needle
不是 haystack
的一部分,则返回 -1
。
示例 1:
输入:haystack = "sadbutsad", needle = "sad" 输出:0 解释:"sad" 在下标 0 和 6 处匹配。 第一个匹配项的下标是 0 ,所以返回 0 。
示例 2:
输入:haystack = "leetcode", needle = "leeto" 输出:-1 解释:"leeto" 没有在 "leetcode" 中出现,所以返回 -1 。
使用hash字符串,利用滑动窗口求解字符串查找:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 | class Solution: def strStr( self , haystack: str , needle: str ) - > int : def hash_s(s, start, end): h = 0 for i in range (start, end + 1 ): h = (h * 26 + ord (s[i]) - ord ( 'a' )) % MOD return h m, n = len (needle), len (haystack) if m > n: return - 1 MOD = ( 1 << 63 ) - 1 p = pow ( 26 , m - 1 , MOD) h, h2 = hash_s(needle, 0 , m - 1 ), hash_s(haystack, 0 , m - 1 ) if h2 = = h: return 0 for i in range (m, n): h2 = h2 - ( ord (haystack[i - m]) - ord ( 'a' )) * p % MOD h2 = (h2 * 26 + ord (haystack[i]) - ord ( 'a' )) % MOD if h2 = = h: return i - m + 1 return - 1 |
这个算法是基于哈希的字符串查找算法,也被称为Rabin-Karp算法。其基本思路如下:
1. 首先,计算目标字符串(needle)的哈希值。
2. 然后,计算源字符串(haystack)中与目标字符串长度相同的子串的哈希值。
3. 如果这两个哈希值相等,那么就找到了目标字符串的位置。
4. 如果这两个哈希值不相等,那么就将源字符串的滑动窗口向右移动一位,然后重新计算新的子串的哈希值。
5. 重复步骤3和4,直到找到目标字符串或者源字符串的滑动窗口到达末尾。
这个算法的关键在于如何快速计算新的子串的哈希值。这里使用了一个技巧,即通过减去滑动窗口最左边的字符的哈希值并添加新字符的哈希值,可以在常数时间内计算新的子串的哈希值。
这个算法的时间复杂度是O(n),其中n是源字符串的长度。这是因为每个字符只需要被计算一次哈希值。
利用sunday算法:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 | class Solution( object ): def strStr( self , haystack, needle): """ :type haystack: str :type needle: str :rtype: int """ skipped = dict () sub_len = len (needle) for pos, ch in enumerate (needle): skipped[ch] = sub_len - pos i = 0 str_len = len (haystack) while i < = str_len - sub_len: if haystack[i: i + sub_len] = = needle: return i if i + sub_len > = str_len: break i + = skipped.get(haystack[i + sub_len], sub_len + 1 ) return - 1 |
20231004又写了一次:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 | class Solution: def strStr( self , haystack: str , needle: str ) - > int : # sunday skipped = dict () m, n = len (needle), len (haystack) for i,c in enumerate (needle): skipped[c] = m - i i = 0 while i < n: if haystack[i:i + m] = = needle: return i if i + m > = n: break i + = skipped.get(haystack[i + m], m + 1 ) return - 1 |
链接:https://leetcode.cn/problems/find-the-index-of-the-first-occurrence-in-a-string/solution/python3-sundayjie-fa-9996-by-tes/
一、Sunday 匹配机制
匹配机制非常容易理解:
目标字符串 haystack
模式串 Pattern当前查询索引 idx (初始为0)
待匹配字符串 haystack [ idx : idx + len(Pattern) ]
每次匹配都会从 目标字符串中 提取 待匹配字符串与 模式串 进行匹配:
若匹配,则返回当前 idx
不匹配,则查看 待匹配字符串 的后一位字符 c:
若c存在于Pattern中,则 idx = idx + 偏移表[c]
否则,idx = idx + len(pattern) + 1
Repeat Loop 直到 idx + len(pattern) > len(String)
举例:
String: checkthisout
Pattern: this
k
不在 Pattern 里- 所以查看 偏移表,
idx = idx + 5
sunday算法核心思想:启发式移动搜索步长!
SUNDAY 算法描述:
字符串查找算法中,最著名的两个是KMP算法(Knuth-Morris-Pratt)和BM算法(Boyer-Moore)。这里介绍一种比BM算法更快一些的sunday查找算法。
例如我们要在"substring searching algorithm"查找"search",刚开始时,把子串与文本左边对齐:
substring searching algorithm
search
^
结果在第二个字符处发现不匹配,于是要把子串往后移动。但是该移动多少呢?这就是各种算法各显神通的地方了,最简单的做法是移动一个字符位 置;KMP是利用已经匹配部分的信息来移动;BM算法是做反向比较,并根据已经匹配的部分来确定移动量。这里要介绍的方法是看紧跟在当前子串之后的那个字 符(上图中的 'i')。
显然,不管移动多少,这个字符是肯定要参加下一步的比较的,也就是说,如果下一步匹配到了,这个字符必须在子串内。所以,可以移动子串,使子串中的 最右边的这个字符与它对齐。现在子串'search'中并不存在'i',则说明可以直接跳过一大片,从'i'之后的那个字符开始作下一步的比较,如下图:
substring searching algorithm
search
^
比较的结果,第一个字符就不匹配,再看子串后面的那个字符,是'r',它在子串中出现在倒数第三位,于是把子串向前移动三位,使两个'r'对齐,如下:
substring searching algorithm
search
^
哈!这次匹配成功了!回顾整个过程,我们只移动了两次子串就找到了匹配位置,是不是很神啊?!可以证明,用这个算法,每一步的移动量都比BM算法要大,所以肯定比BM算法更快。
因此,对于leetcode上的解题:
https://leetcode.com/problems/implement-strstr/
参考:http://blog.csdn.net/kankan231/article/details/22406823
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 记一次.NET内存居高不下排查解决与启示
· 探究高空视频全景AR技术的实现原理
· 理解Rust引用及其生命周期标识(上)
· 浏览器原生「磁吸」效果!Anchor Positioning 锚点定位神器解析
· 没有源码,如何修改代码逻辑?
· 全程不用写代码,我用AI程序员写了一个飞机大战
· MongoDB 8.0这个新功能碉堡了,比商业数据库还牛
· 记一次.NET内存居高不下排查解决与启示
· 白话解读 Dapr 1.15:你的「微服务管家」又秀新绝活了
· DeepSeek 开源周回顾「GitHub 热点速览」