字符串哈希

html

 

http://acm.uestc.edu.cn/#/problem/show/1092ios

韓爺的夢

Time Limit: 200/100MS (Java/Others)     Memory Limit: 1300/1300KB (Java/Others)
 

一天,韓爺去百度面試,面試官給了他這麼一個問題。面試

給你2萬個字符串,每一個字符串長度都是100,而後把2萬個字符串丟入一個 set< string >g 中,問最終set裏含有多少個元素?
g 是一個用來存儲字符串、具備去重功能的容器,即相同字符串在 g 中只能保留一個。
兩個字符串相等,當且僅當,長度同樣且對應位置的字符都同樣。

韓爺前晚沒睡好,隨手寫了一個程序交給面試官,而後就gg了。安全

#include<iostream> #include<string> #include<set> using namespace std; string s; set<string>g; int main(){ for(int k=1;k<=20000;k++){ cin>>s; g.insert(s); } cout<<g.size()<<endl; return 0; }

韓爺醒來以後,發現這只是一個夢(還好只是個夢)。他回憶起夢中的面試官給他的內存限制和時間限制很是低,這麼作確定過不了,那麼,如今你不在夢中,你能解決這個問題麼?函數

Input

單case測試

每一個case有且只有2萬行,每一行包含一個字符串,每行字符串的長度都爲100 (樣例除外)spa

字符集:大寫英文字母(A-Z),小寫英文字母(a-z),數字(0-9)code

Output

輸出一個整數,表示最終set裏含有多少個元素。htm

Sample input and output

Sample Input Sample Output
aaAa
aaAa
bbbb
1234
bbbb
bbbb
ee09
4

Hint

樣例只是樣例,不在test中blog

注意時間限制和內存限制很是低

 

 

思路:這道題目難點在於時間與內存限制很苛刻,通常的方法不能奏效,這裏只能採用hash。即把每一個字符串hash爲一個數字,對數字進行比對,題目就ac了。還有個問題就是,hash函數的選取。我第一次選的hash函數就產生了衝突,這個能夠屢次選擇進行測試,也能夠直接採用更復雜的hash函數。我偷懶了下,選的是前者的方法,第二發就ac了。

這裏說下關於hash的知識:

求一個字符串的hash值:

•如今咱們但願找到一個hash函數,使得每個字符串都可以映射到一個整數上
•好比hash[i]=(hash[i-1]*p+idx(s[i]))%mod
•字符串:abc,bbc,aba,aadaabac
•字符串下標從0開始
•先把a映射爲1,b映射爲2,c->3,d->4,即idx(a)=1, idx(b)=2, idx(c)=3,idx(d)=4;
•好!開始對字符串進行hash

假設咱們取p=13 ,mod=101

先把abc映射爲一個整數

hash[0]=1,表示 a 映射爲1

hash[1]=(hash[0]*p+idx(b))%mod=15,表示 ab 映射爲 15

hash[2]=(hash[1]*p+idx(c))%mod=97

這樣,咱們就把 abc 映射爲 97 這個數字了。

•用一樣的方法,咱們能夠把bbc,aba,aadaabac都映射到一個整數
•用一樣的hash函數,獲得以下結果
• abc  ->  97
• bbc  ->  64
• aba  ->  95
• aadaabac  ->  35
•那麼,咱們發現,這是一個字符串到整數的映射
•這樣子,咱們就能夠記錄下每一個字符串對應的整數,當下一次出現了一個已經出現的字符串時,查詢整數是否出現過,就能夠知道 字符串是否重複出現。
•如今要判斷兩個字符串是否一致,怎麼辦呢?直接用它們的hash值判斷便可,若hash值一致,則認爲字符串一致;若hash值不一致,則認爲是不一樣的字符串。
•咱們要判斷兩個字符串是否一致,沒有那麼麻煩,直接先判斷長度是否一致,而後再判斷每一個對應的字符是否一致便可。
•但,若是要判斷多個字符串裏有多少個不一樣的字符串,怎麼辦呢?
•兩兩字符串都進行比較?時間複雜度過高
•把每一個字符串hash成一個整數,而後把全部整數進行一個去重操做,便可知道答案了。
當遇到衝突時,咱們能夠想辦法調整p和mod,使得衝突機率減少之又小。咱們通常認爲p和mod通常取素數,p取一個較大的素數便可(6位到8位),mod取一個大素數,好比1e9+7,或者1e9+9。
 
如何求一個子串的hash值?
•在以前,咱們求出了hash[i],表示第i個前綴的hash值。如今怎麼求出每一個子串的

   hash值呢?

•咱們看下hash的公式:
• hash[i]=(hash[i-1]*p+idx(s[i]))%mod
•這表示第 i 個前綴的hash值,是一個hash的前綴和。
•hash[i]=(hash[i-1]*p+idx(s[i]))%p;
•那麼,我要求S[l…r]這個子串的hash值
• hash[l..r]=(hash[r]-hash[l-1]*(p^(r-1+1)))%mod(假設字符串下標從1開始)
•但注意下取模時候的問題!
•hash[l..r]=(hash[r]-hash[l-1]*(p^(r-1+1)))%mod
• hash[l..r]是否是可能有負數?
•怎麼辦呢?當獲得的hash[l..r]<0的時候,hash[l..r]+=mod,就好啦。
•這樣就能夠保證每一個子串的hash值在[0, mod-1]的範圍內,準確地用hash值來處理字符串
 
經常使用的幾個字符串hash法
•1. unsigned long long hash[N];
     hash[i]=hash[i-1]*p(自動取模)
解釋:

unsigned long long hash[N];

定義一個unsigned long long類型的變量,它的範圍是在[0, 2^64) 內,這就至關於,當數超不過2^64-1後,它會溢出!這就至關於一個數模2^64的過程。

那麼hash函數能夠理解爲:

       hash[i]=(hash[i-1]*p)%(2^64)

P取一個大素數,通常習慣取1e9+7或1e9+9

安全指數:三星(因此並非很安全)

 

•2. hash[i]=(hash[i-1]*p+idx(s[i]))%mod
解釋:

這個以前已經提到過了。   

 hash[i]=(hash[i-1]*p+idx(s[i]))%mod

p取一個6到8位的素數,mod取一個大素數,通常取1e9+7或1e9+9
安全指數:四星 (還能夠)
 
•3. 雙hash

     hash1[i]=(hash1[i-1]*p+idx(s[i]))%mod1

     hash2[i]=(hash2[i-1]*p+idx(s[i]))%mod2

     pair<hash1,hash2>表示一個字符串!

解釋:

double hash
即取兩個mod值,mod1和mod2

 hash1[i]=(hash1[i-1]*p+idx(s[i]))%mod1

 hash2[i]=(hash2[i-1]*p+idx(s[i]))%mod2

 mod1通常取1e9+7,mod2通常取1e9+9爲何這麼取?

1000000007和1000000009是一對孿生素數,取它們,衝突的機率極低!

安全指數:五星!(很是穩!)
 
小結:
•能夠這麼說,hash某種程度上就是亂搞,把hash函數弄的越沒有規律越好,使得衝突的機率小到 大部分數據都卡不掉。
•若是你開心,你想triple hash,ultra hash,rampage hash… 都沒有問題!

 但請注意,hash的維度越高,耗時越高,耗內存越大!通常狀況下,single hash能夠被hack掉,但double hash極難被hack掉, 用double hash足以解決問題

相關文章
相關標籤/搜索