一、算法介紹
D.E. Knuth、 JH.Morris和 R. Pratt(其中 Knuth和Prat共同研究, Morris獨立研究)發表一個模式匹配算法,可以大大避免重複遍歷的情況,我們把它稱之爲克努特一莫里斯一普拉特算法,簡稱KMP算法
兩個我覺得講的可以的視頻
- 算法介紹:https://www.bilibili.com/video/BV1Ys411d7yh
- 介紹&&代碼復現:https://www.bilibili.com/video/BV1hW411a7ys
KMP算法核心部分就是獲取匹配數組(有的叫next,有的叫pattern數組):
代碼實現時需要注意有的用-1表示,有的用0表示開始
二、相關題目
1. LeetCode 28. 實現 strStr()
實現 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() 定義相符。
C代碼實現:
int* get_next(char* needle){
int lenOfneedle=strlen(needle);
int* next=(int *) malloc(sizeof(int)*lenOfneedle);
next[0] = -1;
int j = 0;
int k = -1;
while(j<lenOfneedle - 1){
if(k == -1 || needle[k] == needle[j]){
j++;
k++;
next[j] = k;
}else{
k = next[k];
}
}
return next;
}
int strStr(char* haystack, char* needle){
int lenOfhaystack=strlen(haystack),lenOfneedle=strlen(needle);
if(lenOfneedle==0)
return 0;
int* next = get_next(needle);
int i=0;
int j=0;
while(i < lenOfhaystack && (j <lenOfneedle)){
if(j == -1 || haystack[i] == needle[j]){
i++;
j++;
}else{
j = next[j];
}
}
if(j == lenOfneedle){
return i-j;
}else{
return -1;
}
}
C++代碼實現
class Solution {
public:
vector<int> getNext(string pattern){
int len=pattern.size();
vector<int> next(len);
int i=-1,j=0;
next[0]=-1;
while(j<len-1){
if(i==-1||pattern[i]==pattern[j]){
i++;
j++;
next[j]=i;
}else{
i=next[i];
}
}
return next;
}
int strStr(string haystack, string needle) {
if(needle.size()==0)
return 0;
vector<int> next=getNext(needle);
int i=0,j=0;
int len1=haystack.size(),len2=needle.size();
while(i<len1&&j<len2){
if(j==-1||haystack[i]==needle[j]){
i++;
j++;
}else{
j=next[j];
}
}
if(j==needle.size())
return i-j; //開始的位置
return -1;
}
};
需要注意的是C++中main函數while循環裏的“i<len1&&j<len2”,如果直接用needle.size()會出問題,因爲默認返回值是無符號整數不是int,和-1直接比較結果是不對的,這裏找了半天。
2. 查找字符串最長公共子串
請編碼實現一個命令行工具,找出指定的2個字符串的最長公共子串。
輸入描述:
命令行工具接收兩個字符串參數。輸入字符串的合法字符集爲[a-zA-Z0-9],大小寫敏感,無需考慮異常輸入場景。
輸出描述:
所找到的公共子串;如果存在多個等長的公共子串,則請按字母序排序,依次打印出所有公共子串,每行一個。
示例1
輸入
1234567 12893457
輸出
345
這裏可以用枚舉(順序表)+KMP,也可以用動態規劃,這裏先貼dp的代碼(評論區)
#include<iostream>
#include<string>
#include<math.h>
#include<algorithm>
using namespace std;
void findLCStr(string A, int n, string B, int m) {
int c[n+1][m+1];
pair<int, string> p[min(n,m)];
int i,j,k,res=0,res_end=0,cnt=0;//res 最長公共子串長度,res_end最長公共子串末尾序號
for(i=0;i<=n;i++) c[i][0]=0;
for(j=1;j<=m;j++) c[0][j]=0;
for(i=1;i<=n;i++){
for(j=1;j<=m;j++){
if(A[i-1]==B[j-1]){
c[i][j] = c[i-1][j-1] + 1;
if(res<=c[i][j]){
res = c[i][j];
res_end = i;
string t="";
p[cnt].first=res;
for(k=res_end-1-res+1;k<=res_end-1;++k)
t+=A[k];
p[cnt++].second=t;
}
//res = max(res, c[i][j]);
}
else c[i][j] = 0; //與LCS的區別在這裏
}
}
sort(p,p+cnt);
for(i=0;i<cnt;i++){
if(p[i].first==res)
cout<<p[i].second<<"\n";
}
}
int main(){
string A,B;
cin>>A>>B;
findLCStr(A,A.length(),B,B.length());
}