清華OJ:PA3-3 重名剔除(Deduplicate)難題精解
分析:
題目涉及字串,提示用雜湊。不難想到雜湊碼轉換,根據鄧公教材上的方法,字串的雜湊碼取作,其中常數a>=2,但若用這一方法,得到的值會超出int範圍上限,不值得。故將上式改為,這樣遠遠不會達到int範圍上限。至於雜湊函式的設計,一般用除餘法即可,故可在得到雜湊碼後,直接除餘,餘數即散列表長,一般選用不小於輸入規模上限的素數即可,此題是600001。轉換後的雜湊碼可能重複,等效於不同關鍵碼(字串)對映到同一地址,故需要解決衝突。
解決衝突的方法很多,常用的是開放定址法的線性探測法,但是這一方法會導致超時,原因在於當出現衝突時,最壞情況下需要遍歷之前所有的非空桶單元,因此此法不妥。此題一般適用的方法是獨立鏈法,即將衝突的關鍵碼組織成列表放在對映到的桶單元中,發生衝突時,最壞情況下只需遍歷當前桶單元的所有衝突關鍵碼,相較於前一方法,時間成本得到了極大降低。
除了上述方法正確之外,還需要結合題目,此題要求重複的字串只輸出1次,故需要增設額外標誌,一般是在桶單元的每個槽位(衝突單元)中設定,可以設為布林型,初始化為假,若當前輸入的字串與對映到的字串重複,且當前對應的槽位標誌為假,則將其改為真,輸出這個字串,這樣之後重複的字串不會被輸出。
有了正確的基本方法和結合題目的特殊方法,一般可以通過。至於每個槽位中的資料域的選取,一般選取字串,而為了賦值方便,一般使用C++的string型別,但是這樣做通過後,會發現最壞用例耗時1200+ms。不難發現,其原因在於每次字串的賦值導致時間成本的迅速升高。於是,將資料域取字元指標,於是資料域的每次賦值只需O(1)的時間。經測試,此方法最壞用例僅耗時200+ms,相較於前一方法,效率提升了近5倍!
若槽位資料域選用字元指標,則需要注意,你需要在主函式之外定義一個二維字元陣列,原因有兩點,一是字元(串)指標需要取一個字元二維陣列的第1維,這樣就指向了一個字串,而這個字串的首字元地址是唯一的,這樣可以保證每個槽位字元指標不至於重複。二是陣列若在主函式之內定義,執行會出錯。最後注意陣列第2維度長度即字串最大長度,需至少為41,原因是字串需要有結束符。
掌握基本方法,利用好題目條件,注意和處理好和題目相關的所有細節,定能AC!
程式碼:
#include<cstdio> #include<cstring> #define HASHSIZE 600001 using namespace std; struct Slot {//每個桶對應的槽位,儲存衝突,即對映到同一地址且不重複的字串(實則字元指標) char* data;//資料項,儲存字元指標 bool repeat;//標誌,判別字串是否重複 Slot* succ;//後繼 }buckets[HASHSIZE];//桶陣列(散列表) char name[HASHSIZE][41];//字元二維陣列(必須開頭定義),儲存輸入字串,注意二維長度為40+1個結束符=41 void Insert(int addr, char* s) {//在相應地址中插入衝突的字串(實則字元指標) Slot* t = new Slot; t->data = s; t->repeat = false;//初始化當前字串從未重複 t->succ = buckets[addr].succ;//連結串列頭插法 buckets[addr].succ = t; } int HashCode(char* s) {//雜湊碼轉換(字串轉數字) int sum = 0, len = strlen(s); for (int i = 0; i<len; i++)//多項式求和 sum += (i + 1)*(s[i] - 'a'+1); return sum; } int main() { int n; scanf("%d", &n); for (int i = 0; i < n; i++){ scanf("%s", name[i]); int addr = HashCode(name[i]) % HASHSIZE;//獲得對映到的地址 Slot* p = buckets[addr].succ;//從當前桶的第1個槽位開始 while (p)//遍歷所有槽位(衝突的單元) if (!strcmp(p->data, name[i])) {//若當前槽位的字串重複 if (!p->repeat) {//檢查當前槽位的字串是否重複 p->repeat = true;//若未重複,則標誌為已重複過 puts(name[i]);//輸出重複字串 }break;//若重複過,則忽略,無論是否重複過,皆終止遍歷 } else p = p->succ; if (!p)Insert(addr, name[i]);//若當前槽位空,則進行插入 } return 0; }