傳說這是一道知名外企的筆試題
但是看了一些文章,都只是單純的轉了那個算法,弱弱的說一句,那個算法中把'\0'寫成了'/0',會導致在
while ('/0' != *pTemp) { hashTable[*pTemp] = 1; ++ pTemp; }
這一步的時候,一直循環下去,直到系統中斷。不過調試的時候發現了一個很有意思的事情,就是之前pTemp之前是指向aeiou,這就是傳入的第二個參數,當遍歷完這個參數之后,pTemp會繼續指向棧中的下一個地址,也就是第一個參數they are students的地址,這個正好映證了《深入理解計算機系統》上的內容,也說明了這玩意是多么多么的不安全啊。
說了這么多,還是看看具體的這道題目吧。
題目:輸入兩個字符串,從第一字符串中刪除第二個字符串中所有的字符。例如,輸入”They are students.”和”aeiou”,則刪除之后的第一個字符串變成”Thy r stdnts.”。
首 先我們考慮如何在字符串中刪除一個字符。由於字符串的內存分配方式是連續分配的。我們從字符串當中刪除一個字符,需要把后面所有的字符往前移動一個字節的 位置。但如果每次刪除都需要移動字符串后面的字符的話,對於一個長度為n的字符串而言,刪除一個字符的時間復雜度為O(n)。而對於本題而言,有可能要刪 除的字符的個數是n,因此該方法就刪除而言的時間復雜度為O(n^2)。
這個算法我也弱弱的實現了一下,一會兒一起貼出來。
事 實上,我們並不需要在每次刪除一個字符的時候都去移動后面所有的字符。我們可以設想,當一個字符需要被刪除的時候,我們把它所占的位置讓它后面的字符來填 補,也就相當於這個字符被刪除了。在具體實現中,我們可以定義兩個指針(pFast和pSlow),初始的時候都指向第一字符的起始位置。當pFast指 向的字符是需要刪除的字符,則pFast直接跳過,指向下一個字符。如果pFast指向的字符是不需要刪除的字符,那么把pFast指向的字符賦值給 pSlow指向的字符,並且pFast和pStart同時向后移動指向下一個字符。這樣,前面被pFast跳過的字符相當於被刪除了。用這種方法,整個刪 除在O(n)時間內就可以完成。
這一步其實還是蠻不好理解的,或者我太菜了,第一次沒有理解清楚,看了調試的過程,才明白了,這段話是什么意思。這段話對應的就是下面這段代碼
while ('\0' != *pFast) { if(1 != hashTable[*pFast]) { *pSlow = *pFast; ++ pSlow; } ++pFast; }
初始時,pFast和pSlow都指向 they are students 的第一個字符,之后如果pFast指向的不是要刪除的字符(這是if判斷的內容),則pFast和pSlow一起移動,知道他們都指向e,這時候,不進入if,pFast繼續移動,pSlow還是指向e,當pFast指向y的時候,進入if,這時候,就將pSlow指向的e,替換成了pFast指向的y,也就是這段話的含義。
接下來就是分析,如何更快的確定pFast指向的字符,是在要刪除的字符集中,這里使用了Hash的思想。
接下來我們考慮如何在一個字符串中查找一個字符。當然,最簡單的辦法就是從頭到尾掃描整個字符串。顯然,這種方法需要一個循環,對於一個長度為n的字符串,時間復雜度是O(n)。
由 於字符的總數是有限的。對於八位的char型字符而言,總共只有28=256個字符。我們可以新建一個大小為256的數組,把所有元素都初始化為0。然后 對於字符串中每一個字符,把它的ASCII碼映射成索引,把數組中該索引對應的元素設為1。這個時候,要查找一個字符就變得很快了:根據這個字符的 ASCII碼,在數組中對應的下標找到該元素,如果為0,表示字符串中沒有該字符,否則字符串中包含該字符。此時,查找一個字符的時間復雜度是O(1)。 其實,這個數組就是一個hash表。
完整的代碼如下:
// 從str中,刪除一個指定的字符 void deleteOneChar(char* str, char toBeDelete) { if(str == NULL) return; int length = strlen(str); char* p = str; // 遍歷str,刪除和toBeDelete相等的字符 for(int i=0; str[i]!='\0'&&i<length; i++) { // 如果相等則用后面的元素進行覆蓋 if(str[i] == toBeDelete) { int j=i; for(; str[j]!='\0'&&j<length-1;j++) str[j]=str[j+1]; str[j]='\0'; } } return; } // 使用第一種方法實現 復雜度O(n^2) void deleteChars1(char* str,const char* chars) { if(str==NULL) return; if(chars == NULL) return; // 依次刪除chars中的每一個元素 for(int i=0; i<strlen(chars); i++) deleteOneChar(str,chars[i]); return; } // 使用第二種方法實現復雜度O(n) void deleteChars2(char* pStrSource, const char* pStrDelete) { if(NULL == pStrSource || NULL == pStrDelete) return; const unsigned int nTableSize = 256; int hashTable[nTableSize]; memset(hashTable, 0, sizeof(hashTable)); const char* pTemp = pStrDelete; while ('\0' != *pTemp) { hashTable[*pTemp] = 1; ++ pTemp; } char* pSlow = pStrSource; char* pFast = pStrSource; while ('\0' != *pFast) { if(1 != hashTable[*pFast]) { *pSlow = *pFast; ++ pSlow; } ++pFast; } *pSlow = '\0'; } int main() { char* str = "they are students"; char* p = (char*)malloc((strlen(str)+1)*sizeof(char)); strcpy(p,str); const char*q = "aeiou"; deleteChars2(p,q); cout<<p<<endl; free(p); p = NULL; }