leetcode1044. 最長重複子串
題意
給出一個字符串 S,考慮其所有重複子串(S 的連續子串,出現兩次或多次,可能會有重疊)。
返回任何具有最長可能長度的重複子串。(如果 S 不含重複子串,那麼答案爲 “”。)
思路
這裏兩個相同子串的最大長度滿足遞增性, 所以可以用二分的枚舉這個長度值m。
那麼問題就變成在一個字符串裏枚舉是否有兩個長度爲m的相同子串。
枚舉過程,我們可以想象是就是一個大小爲m的窗口滑動的過程。 總的時間複雜度是 滑動過程乘以比較窗口內字符串是否出現 O(len(S)*len(S))。
在這裏新學習Rabin-Karp算法可以實現,O(1)計算出窗口內字符串的hash值,從而判斷是否出現過。
具體的Rabin-Karp
算法就是將一個字符串用公式計算成唯一的hash數值,原理很簡單。
計算公式如下:
其中a
是每個字符串單個字符的種類,這樣就能保證每個字符串hash值是唯一的,比如全是小寫字母的字符串,a可以是26。
當窗口滑動時,新的字符串hash計算公式也很簡單,如下:
代碼
class Solution:
def rabin_karp_check(self, nums, a ,m, n) :
p = pow(a , m-1, self.mod)
import functools
cur = functools.reduce(lambda x,y: (x*a+y) % self.mod, nums[:m])
seed = {cur}
for index in range(m, n):
cur = ((cur - p * nums[index-m])* a + nums[index])%self.mod
if cur in seed :
return index - m +1
seed.add(cur)
return -1
def longestDupSubstring(self, S: str) -> str:
self.mod = 2**63 -1
l , r = 1, len(S)
nums = [ord(c) - ord('a') for c in S] #把字符映射爲數值
pos = 0
while l <=r :
mid = int((l+r) /2)
index = self.rabin_karp_check(nums, 26, mid, len(S))
if index != -1:
l= mid + 1
pos = index
else : r = mid - 1
return S[pos: pos +l-1]
這題補充了functools.reduce()
的用法。
另外注意這題數據量很大,我用c++寫的版本,mod 取到最大還是hash後還是會衝突,過不了。
Wrong的代碼先貼在這,有時間再糾結怎麼改對…
class Solution {
public:
int rabin_karp_check(vector<int>nums, long long a, int m, int n){
set<int> cot;
long long cur = 0, mod = 6*(1<<20)+1;
long long p =1;
for(int i=0;i<m;i++){
cur = (nums[i] + cur*a%mod) % mod;
p = p*a % mod;
}
cot.insert(cur);
for(int i = m;i<n;i++){
cur = (cur * a % mod- nums[i-m]*p %mod + nums[i]) % mod;
//cout<<cur<<endl;
if(cot.count(cur)>0){
return i-m+1;
}
else{
cot.insert(cur);
}
}
return -1;
}
string longestDupSubstring(string S) {
int l =1 ,r= S.length();
vector<int> nums;
for(char c:S){
nums.push_back(c - 'a');
}
int res =0;
while(l<=r){
int mid = (l+r) *0.5;
int index = rabin_karp_check(nums, 26, mid, S.length());
if(index != -1){
res = index;
l = mid +1;
}
else{
r= mid - 1;
}
}
return S.substr(res, res+l-1);
}
};