参与本项目,贡献其他语言版本的代码,拥抱开源,让更多学习算法的小伙伴们收益!
> 在一个串中查找是否出现过另一个串,这是KMP的看家本领。 # 28. 实现 strStr() [力扣题目链接](https://leetcode.cn/problems/find-the-index-of-the-first-occurrence-in-a-string/) 实现 strStr() 函数。 给定一个 haystack 字符串和一个 needle 字符串,在 haystack 字符串中找出 needle 字符串出现的第一个位置 (从0开始)。如果不存在,则返回 -1。 示例 1: 输入: haystack = "hello", needle = "ll" 输出: 2 示例 2: 输入: haystack = "aaaaa", needle = "bba" 输出: -1 说明: 当 needle 是空字符串时,我们应当返回什么值呢?这是一个在面试中很好的问题。 对于本题而言,当 needle 是空字符串时我们应当返回 0 。这与C语言的 strstr() 以及 Java的 indexOf() 定义相符。 # 思路 本题是KMP 经典题目。 以下文字如果看不进去,可以看我的B站视频: * [帮你把KMP算法学个通透!B站(理论篇)](https://www.bilibili.com/video/BV1PD4y1o7nd/) * [帮你把KMP算法学个通透!(求next数组代码篇)](https://www.bilibili.com/video/BV1M5411j7Xx) KMP的经典思想就是:**当出现字符串不匹配时,可以记录一部分之前已经匹配的文本内容,利用这些信息避免从头再去做匹配。** 本篇将以如下顺序来讲解KMP, * 什么是KMP * KMP有什么用 * 什么是前缀表 * 为什么一定要用前缀表 * 如何计算前缀表 * 前缀表与next数组 * 使用next数组来匹配 * 时间复杂度分析 * 构造next数组 * 使用next数组来做匹配 * 前缀表统一减一 C++代码实现 * 前缀表(不减一)C++实现 * 总结 读完本篇可以顺便把leetcode上28.实现strStr()题目做了。 # 什么是KMP 说到KMP,先说一下KMP这个名字是怎么来的,为什么叫做KMP呢。 因为是由这三位学者发明的:Knuth,Morris和Pratt,所以取了三位学者名字的首字母。所以叫做KMP # KMP有什么用 KMP主要应用在字符串匹配上。 KMP的主要思想是**当出现字符串不匹配时,可以知道一部分之前已经匹配的文本内容,可以利用这些信息避免从头再去做匹配了。** 所以如何记录已经匹配的文本内容,是KMP的重点,也是next数组肩负的重任。 其实KMP的代码不好理解,一些同学甚至直接把KMP代码的模板背下来。 没有彻底搞懂,懵懵懂懂就把代码背下来太容易忘了。 不仅面试的时候可能写不出来,如果面试官问:**next数组里的数字表示的是什么,为什么这么表示?** 估计大多数候选人都是懵逼的。 下面Carl就带大家把KMP的精髓,next数组弄清楚。 # 什么是前缀表 写过KMP的同学,一定都写过next数组,那么这个next数组究竟是个啥呢? next数组就是一个前缀表(prefix table)。 前缀表有什么作用呢? **前缀表是用来回退的,它记录了模式串与主串(文本串)不匹配的时候,模式串应该从哪里开始重新匹配。** 为了清楚地了解前缀表的来历,我们来举一个例子: 要在文本串:aabaabaafa 中查找是否出现过一个模式串:aabaaf。 请记住文本串和模式串的作用,对于理解下文很重要,要不然容易看懵。所以说三遍: 要在文本串:aabaabaafa 中查找是否出现过一个模式串:aabaaf。 要在文本串:aabaabaafa 中查找是否出现过一个模式串:aabaaf。 要在文本串:aabaabaafa 中查找是否出现过一个模式串:aabaaf。 如动画所示:  动画里,我特意把 子串`aa` 标记上了,这是有原因的,大家先注意一下,后面还会说到。 可以看出,文本串中第六个字符b 和 模式串的第六个字符f,不匹配了。如果暴力匹配,发现不匹配,此时就要从头匹配了。 但如果使用前缀表,就不会从头匹配,而是从上次已经匹配的内容开始匹配,找到了模式串中第三个字符b继续开始匹配。 此时就要问了**前缀表是如何记录的呢?** 首先要知道前缀表的任务是当前位置匹配失败,找到之前已经匹配上的位置,再重新匹配,此也意味着在某个字符失配时,前缀表会告诉你下一步匹配中,模式串应该跳到哪个位置。 那么什么是前缀表:**记录下标i之前(包括i)的字符串中,有多大长度的相同前缀后缀。** # 最长公共前后缀? 文章中字符串的**前缀是指不包含最后一个字符的所有以第一个字符开头的连续子串**。 **后缀是指不包含第一个字符的所有以最后一个字符结尾的连续子串**。 **正确理解什么是前缀什么是后缀很重要**! 那么网上清一色都说 “kmp 最长公共前后缀” 又是什么回事呢? 我查了一遍 算法导论 和 算法4里KMP的章节,都没有提到 “最长公共前后缀”这个词,也不知道从哪里来了,我理解是用“最长相等前后缀” 更准确一些。 **因为前缀表要求的就是相同前后缀的长度。** 而最长公共前后缀里面的“公共”,更像是说前缀和后缀公共的长度。这其实并不是前缀表所需要的。 所以字符串a的最长相等前后缀为0。 字符串aa的最长相等前后缀为1。 字符串aaa的最长相等前后缀为2。 等等.....。 # 为什么一定要用前缀表 这就是前缀表,那为啥就能告诉我们 上次匹配的位置,并跳过去呢? 回顾一下,刚刚匹配的过程在下标5的地方遇到不匹配,模式串是指向f,如图:
* 时间复杂度:O(m*n)
* 空间复杂度:O(1)
* 注:n为haystack的长度,m为needle的长度
*/
public int strStr(String haystack, String needle) {
int m = needle.length();
// 当 needle 是空字符串时我们应当返回 0
if (m == 0) {
return 0;
}
int n = haystack.length();
if (n < m) {
return -1;
}
int i = 0;
int j = 0;
while (i < n - m + 1) {
// 找到首字母相等
while (i < n && haystack.charAt(i) != needle.charAt(j)) {
i++;
}
if (i == n) {// 没有首字母相等的
return -1;
}
// 遍历后续字符,判断是否相等
i++;
j++;
while (i < n && j < m && haystack.charAt(i) == needle.charAt(j)) {
i++;
j++;
}
if (j == m) {// 找到
return i - j;
} else {// 未找到
i -= j - 1;
j = 0;
}
}
return -1;
}
}
```
```java
// 方法一
class Solution {
public void getNext(int[] next, String s){
int j = -1;
next[0] = j;
for (int i = 1; i < s.length(); i++){
while(j >= 0 && s.charAt(i) != s.charAt(j+1)){
j=next[j];
}
if(s.charAt(i) == s.charAt(j+1)){
j++;
}
next[i] = j;
}
}
public int strStr(String haystack, String needle) {
if(needle.length()==0){
return 0;
}
int[] next = new int[needle.length()];
getNext(next, needle);
int j = -1;
for(int i = 0; i < haystack.length(); i++){
while(j>=0 && haystack.charAt(i) != needle.charAt(j+1)){
j = next[j];
}
if(haystack.charAt(i) == needle.charAt(j+1)){
j++;
}
if(j == needle.length()-1){
return (i-needle.length()+1);
}
}
return -1;
}
}
```
```Java
class Solution {
//前缀表(不减一)Java实现
public int strStr(String haystack, String needle) {
if (needle.length() == 0) return 0;
int[] next = new int[needle.length()];
getNext(next, needle);
int j = 0;
for (int i = 0; i < haystack.length(); i++) {
while (j > 0 && needle.charAt(j) != haystack.charAt(i))
j = next[j - 1];
if (needle.charAt(j) == haystack.charAt(i))
j++;
if (j == needle.length())
return i - needle.length() + 1;
}
return -1;
}
private void getNext(int[] next, String s) {
int j = 0;
next[0] = 0;
for (int i = 1; i < s.length(); i++) {
while (j > 0 && s.charAt(j) != s.charAt(i))
j = next[j - 1];
if (s.charAt(j) == s.charAt(i))
j++;
next[i] = j;
}
}
}
```
Python3:
(版本一)前缀表(减一)
```python
class Solution:
def getNext(self, next, s):
j = -1
next[0] = j
for i in range(1, len(s)):
while j >= 0 and s[i] != s[j+1]:
j = next[j]
if s[i] == s[j+1]:
j += 1
next[i] = j
def strStr(self, haystack: str, needle: str) -> int:
if not needle:
return 0
next = [0] * len(needle)
self.getNext(next, needle)
j = -1
for i in range(len(haystack)):
while j >= 0 and haystack[i] != needle[j+1]:
j = next[j]
if haystack[i] == needle[j+1]:
j += 1
if j == len(needle) - 1:
return i - len(needle) + 1
return -1
```
(版本二)前缀表(不减一)
```python
class Solution:
def getNext(self, next: List[int], s: str) -> None:
j = 0
next[0] = 0
for i in range(1, len(s)):
while j > 0 and s[i] != s[j]:
j = next[j - 1]
if s[i] == s[j]:
j += 1
next[i] = j
def strStr(self, haystack: str, needle: str) -> int:
if len(needle) == 0:
return 0
next = [0] * len(needle)
self.getNext(next, needle)
j = 0
for i in range(len(haystack)):
while j > 0 and haystack[i] != needle[j]:
j = next[j - 1]
if haystack[i] == needle[j]:
j += 1
if j == len(needle):
return i - len(needle) + 1
return -1
```
(版本三)暴力法
```python
class Solution(object):
def strStr(self, haystack, needle):
"""
:type haystack: str
:type needle: str
:rtype: int
"""
m, n = len(haystack), len(needle)
for i in range(m):
if haystack[i:i+n] == needle:
return i
return -1
```
(版本四)使用 index
```python
class Solution:
def strStr(self, haystack: str, needle: str) -> int:
try:
return haystack.index(needle)
except ValueError:
return -1
```
(版本五)使用 find
```python
class Solution:
def strStr(self, haystack: str, needle: str) -> int:
return haystack.find(needle)
```
Go:
```go
// 方法一:前缀表使用减1实现
// getNext 构造前缀表next
// params:
// next 前缀表数组
// s 模式串
func getNext(next []int, s string) {
j := -1 // j表示 最长相等前后缀长度
next[0] = j
for i := 1; i < len(s); i++ {
for j >= 0 && s[i] != s[j+1] {
j = next[j] // 回退前一位
}
if s[i] == s[j+1] {
j++
}
next[i] = j // next[i]是i(包括i)之前的最长相等前后缀长度
}
}
func strStr(haystack string, needle string) int {
if len(needle) == 0 {
return 0
}
next := make([]int, len(needle))
getNext(next, needle)
j := -1 // 模式串的起始位置 next为-1 因此也为-1
for i := 0; i < len(haystack); i++ {
for j >= 0 && haystack[i] != needle[j+1] {
j = next[j] // 寻找下一个匹配点
}
if haystack[i] == needle[j+1] {
j++
}
if j == len(needle)-1 { // j指向了模式串的末尾
return i - len(needle) + 1
}
}
return -1
}
```
```go
// 方法二: 前缀表无减一或者右移
// getNext 构造前缀表next
// params:
// next 前缀表数组
// s 模式串
func getNext(next []int, s string) {
j := 0
next[0] = j
for i := 1; i < len(s); i++ {
for j > 0 && s[i] != s[j] {
j = next[j-1]
}
if s[i] == s[j] {
j++
}
next[i] = j
}
}
func strStr(haystack string, needle string) int {
n := len(needle)
if n == 0 {
return 0
}
j := 0
next := make([]int, n)
getNext(next, needle)
for i := 0; i < len(haystack); i++ {
for j > 0 && haystack[i] != needle[j] {
j = next[j-1] // 回退到j的前一位
}
if haystack[i] == needle[j] {
j++
}
if j == n {
return i - n + 1
}
}
return -1
}
```
JavaScript版本
> 前缀表统一减一
```javascript
/**
* @param {string} haystack
* @param {string} needle
* @return {number}
*/
var strStr = function (haystack, needle) {
if (needle.length === 0)
return 0;
const getNext = (needle) => {
let next = [];
let j = -1;
next.push(j);
for (let i = 1; i < needle.length; ++i) {
while (j >= 0 && needle[i] !== needle[j + 1])
j = next[j];
if (needle[i] === needle[j + 1])
j++;
next.push(j);
}
return next;
}
let next = getNext(needle);
let j = -1;
for (let i = 0; i < haystack.length; ++i) {
while (j >= 0 && haystack[i] !== needle[j + 1])
j = next[j];
if (haystack[i] === needle[j + 1])
j++;
if (j === needle.length - 1)
return (i - needle.length + 1);
}
return -1;
};
```
> 前缀表统一不减一
```javascript
/**
* @param {string} haystack
* @param {string} needle
* @return {number}
*/
var strStr = function (haystack, needle) {
if (needle.length === 0)
return 0;
const getNext = (needle) => {
let next = [];
let j = 0;
next.push(j);
for (let i = 1; i < needle.length; ++i) {
while (j > 0 && needle[i] !== needle[j])
j = next[j - 1];
if (needle[i] === needle[j])
j++;
next.push(j);
}
return next;
}
let next = getNext(needle);
let j = 0;
for (let i = 0; i < haystack.length; ++i) {
while (j > 0 && haystack[i] !== needle[j])
j = next[j - 1];
if (haystack[i] === needle[j])
j++;
if (j === needle.length)
return (i - needle.length + 1);
}
return -1;
};
```
TypeScript版本:
> 前缀表统一减一
```typescript
function strStr(haystack: string, needle: string): number {
function getNext(str: string): number[] {
let next: number[] = [];
let j: number = -1;
next[0] = j;
for (let i = 1, length = str.length; i < length; i++) {
while (j >= 0 && str[i] !== str[j + 1]) {
j = next[j];
}
if (str[i] === str[j + 1]) {
j++;
}
next[i] = j;
}
return next;
}
if (needle.length === 0) return 0;
let next: number[] = getNext(needle);
let j: number = -1;
for (let i = 0, length = haystack.length; i < length; i++) {
while (j >= 0 && haystack[i] !== needle[j + 1]) {
j = next[j];
}
if (haystack[i] === needle[j + 1]) {
if (j === needle.length - 2) {
return i - j - 1;
}
j++;
}
}
return -1;
};
```
> 前缀表不减一
```typescript
// 不减一版本
function strStr(haystack: string, needle: string): number {
function getNext(str: string): number[] {
let next: number[] = [];
let j: number = 0;
next[0] = j;
for (let i = 1, length = str.length; i < length; i++) {
while (j > 0 && str[i] !== str[j]) {
j = next[j - 1];
}
if (str[i] === str[j]) {
j++;
}
next[i] = j;
}
return next;
}
if (needle.length === 0) return 0;
let next: number[] = getNext(needle);
let j: number = 0;
for (let i = 0, length = haystack.length; i < length; i++) {
while (j > 0 && haystack[i] !== needle[j]) {
j = next[j - 1];
}
if (haystack[i] === needle[j]) {
if (j === needle.length - 1) {
return i - j;
}
j++;
}
}
return -1;
}
```
Swift 版本
> 前缀表统一减一
```swift
func strStr(_ haystack: String, _ needle: String) -> Int {
let s = Array(haystack), p = Array(needle)
guard p.count != 0 else { return 0 }
// 2 pointer
var j = -1
var next = [Int](repeating: -1, count: needle.count)
// KMP
getNext(&next, needle: p)
for i in 0 ..< s.count {
while j >= 0 && s[i] != p[j + 1] {
//不匹配之后寻找之前匹配的位置
j = next[j]
}
if s[i] == p[j + 1] {
//匹配,双指针同时后移
j += 1
}
if j == (p.count - 1) {
//出现匹配字符串
return i - p.count + 1
}
}
return -1
}
//前缀表统一减一
func getNext(_ next: inout [Int], needle: [Character]) {
var j: Int = -1
next[0] = j
// i 从 1 开始
for i in 1 ..< needle.count {
while j >= 0 && needle[i] != needle[j + 1] {
j = next[j]
}
if needle[i] == needle[j + 1] {
j += 1;
}
next[i] = j
}
print(next)
}
```
> 前缀表右移
```swift
func strStr(_ haystack: String, _ needle: String) -> Int {
let s = Array(haystack), p = Array(needle)
guard p.count != 0 else { return 0 }
var j = 0
var next = [Int].init(repeating: 0, count: p.count)
getNext(&next, p)
for i in 0 ..< s.count {
while j > 0 && s[i] != p[j] {
j = next[j]
}
if s[i] == p[j] {
j += 1
}
if j == p.count {
return i - p.count + 1
}
}
return -1
}
// 前缀表后移一位,首位用 -1 填充
func getNext(_ next: inout [Int], _ needle: [Character]) {
guard needle.count > 1 else { return }
var j = 0
next[0] = j
for i in 1 ..< needle.count-1 {
while j > 0 && needle[i] != needle[j] {
j = next[j-1]
}
if needle[i] == needle[j] {
j += 1
}
next[i] = j
}
next.removeLast()
next.insert(-1, at: 0)
}
```
> 前缀表统一不减一
```swift
func strStr(_ haystack: String, _ needle: String) -> Int {
let s = Array(haystack), p = Array(needle)
guard p.count != 0 else { return 0 }
var j = 0
var next = [Int](repeating: 0, count: needle.count)
// KMP
getNext(&next, needle: p)
for i in 0 ..< s.count {
while j > 0 && s[i] != p[j] {
j = next[j-1]
}
if s[i] == p[j] {
j += 1
}
if j == p.count {
return i - p.count + 1
}
}
return -1
}
//前缀表
func getNext(_ next: inout [Int], needle: [Character]) {
var j = 0
next[0] = j
for i in 1 ..< needle.count {
while j>0 && needle[i] != needle[j] {
j = next[j-1]
}
if needle[i] == needle[j] {
j += 1
}
next[i] = j
}
}
```
PHP:
> 前缀表统一减一
```php
function strStr($haystack, $needle) {
if (strlen($needle) == 0) return 0;
$next= [];
$this->getNext($next,$needle);
$j = -1;
for ($i = 0;$i < strlen($haystack); $i++) { // 注意i就从0开始
while($j >= 0 && $haystack[$i] != $needle[$j + 1]) {
$j = $next[$j];
}
if ($haystack[$i] == $needle[$j + 1]) {
$j++;
}
if ($j == (strlen($needle) - 1) ) {
return ($i - strlen($needle) + 1);
}
}
return -1;
}
function getNext(&$next, $s){
$j = -1;
$next[0] = $j;
for($i = 1; $i < strlen($s); $i++) { // 注意i从1开始
while ($j >= 0 && $s[$i] != $s[$j + 1]) {
$j = $next[$j];
}
if ($s[$i] == $s[$j + 1]) {
$j++;
}
$next[$i] = $j;
}
}
```
> 前缀表统一不减一
```php
function strStr($haystack, $needle) {
if (strlen($needle) == 0) return 0;
$next= [];
$this->getNext($next,$needle);
$j = 0;
for ($i = 0;$i < strlen($haystack); $i++) { // 注意i就从0开始
while($j > 0 && $haystack[$i] != $needle[$j]) {
$j = $next[$j-1];
}
if ($haystack[$i] == $needle[$j]) {
$j++;
}
if ($j == strlen($needle)) {
return ($i - strlen($needle) + 1);
}
}
return -1;
}
function getNext(&$next, $s){
$j = 0;
$next[0] = $j;
for($i = 1; $i < strlen($s); $i++) { // 注意i从1开始
while ($j > 0 && $s[$i] != $s[$j]) {
$j = $next[$j-1];
}
if ($s[$i] == $s[$j]) {
$j++;
}
$next[$i] = $j;
}
}
```
Rust:
> 前缀表统一不减一
```Rust
impl Solution {
pub fn get_next(next: &mut Vec