程序员编程艺术:第二章、字符串是否包含问题
????????????????????????程序員編程藝術(shù):第二章、字符串是否包含及相關(guān)問題擴(kuò)展
作者:July,yansha。
時間:二零一一年四月二十三日。
致謝:老夢,nossiac,Hession,Oliver,luuillu,雨翔,啊菜,及微軟100題實(shí)現(xiàn)小組所有成員。
微博:http://weibo.com/julyweibo。
出處:http://blog.csdn.net/v_JULY_v。
-------------------------------------------
目錄
曲之前奏
第一節(jié)、一道倆個字符串是否包含的問題
? 1.1、O(n*m)的輪詢方法
? 1.2、O(mlogm)+O(nlogn)+O(m+n)的排序方法
? 1.3、O(n+m)的計(jì)數(shù)排序方法
第二節(jié)
? 2.1、O(n+m)的hashtable的方法
? 2.2、O(n+m)的數(shù)組存儲方法
第三節(jié)、O(n)到O(n+m)的素?cái)?shù)方法
第四節(jié)、字符串是否包含問題的繼續(xù)補(bǔ)充
? 4.1、Bit-map
? 4.2、移位操作
第五節(jié)、字符串相關(guān)問題擴(kuò)展
? 5.1、字符串匹配問題
? 5.2、在字符串中查找子串
??? 擴(kuò)展:在一個字符串中找到第一個只出現(xiàn)一次的字符
? 5.3、字符串轉(zhuǎn)換為整數(shù)
? 5.4、字符串拷貝
前奏
??? 前一章,請見這:程序員面試題狂想曲:第一章、左旋轉(zhuǎn)字符串。本章里出現(xiàn)的所有代碼及所有思路的實(shí)現(xiàn),在此之前,整個網(wǎng)上都是沒有的。
??? 文中的思路,聰明點(diǎn)點(diǎn)的都能想到,巧的思路,大師也已奉獻(xiàn)了。如果你有更好的思路,歡迎提供。如果你對此狂想曲系列有任何建議,歡迎微博上交流或來信指導(dǎo)。任何人,有任何問題,歡迎隨時不吝指正。
??? 如果此狂想曲系列對你有所幫助,我會非常之高興,并將讓我有了永久堅(jiān)持寫下去的動力。謝謝。
第一節(jié)、一道倆個字符串是否包含的問題
1.0、題目描述:
假設(shè)這有一個各種字母組成的字符串,假設(shè)這還有另外一個字符串,而且這個字符串里的字母數(shù)相對少一些。從算法是講,什么方法能最快的查出所有小字符串里的字母在大字符串里都有?
比如,如果是下面兩個字符串:
String 1: ABCDEFGHLMNOPQRS
String 2: DCGSRQPOM
答案是true,所有在string2里的字母string1也都有。
?
如果是下面兩個字符串:??
String 1: ABCDEFGHLMNOPQRS??
String 2: DCGSRQPOZ??
答案是false,因?yàn)榈诙€字符串里的Z字母不在第一個字符串里。
??? 點(diǎn)評:
??? 1、題目描述雖長,但題意簡單明了,就是給定一長一短的倆個字符串A,B,假設(shè)A長B短,現(xiàn)在,要你判斷B是否包含在字符串A中,即B?(-A。
??? 2、題意雖簡單,但實(shí)現(xiàn)起來并不輕松,且當(dāng)如果面試官步步緊逼,一個一個否決你能想到的方法,要你給出更好、最好的方案時,你恐怕就要傷不少腦筋了。
??? ok,在繼續(xù)往下閱讀之前,您最好先想個幾分鐘,看你能想到的最好方案是什么,是否與本文最后實(shí)現(xiàn)的方法一致。
1.1、O(n*m)的輪詢方法
判斷string2中的字符是否在string1中?:
String 1: ABCDEFGHLMNOPQRS
String 2: DCGSRQPOM
??? 判斷一個字符串是否在另一個字符串中,最直觀也是最簡單的思路是,針對第二個字符串string2中每一個字符,一一與第一個字符串string1中每個字符依次輪詢比較,看它是否在第一個字符串string1中。
??? 假設(shè)n是字符串string1的長度,m是字符串string2的長度,那么此算法,需要O(n*m)次操作,拿上面的例子來說,最壞的情況下將會有16*8 = 128次操作。
??? 我們不難寫出以下代碼:
#include <iostream> using namespace std; int compare(string str1,string str2) { for (int i=0; i<str2.length(); i++) { for (int j=0; j<str1.length(); j++) //O(n*m) { if (str2[i] == str1[j]) //一一比較 { break; } } if (j==str1.length()) { cout << "false" << endl; return 0; } } cout << "true" << endl; return 1; } int main() { string str1="ABCDEFGHLMNOPQRS"; string str2="DCGSRQPOM"; compare(str1,str2); return 0; }
?上述代碼的時間復(fù)雜度為O(n*m),顯然,時間開銷太大,我們需要找到一種更好的辦法。
?
1.2、O(mlogm)+O(nlogn)+O(m+n)的排序方法
??? 一個稍微好一點(diǎn)的方案是先對這兩個字符串的字母進(jìn)行排序,然后同時對兩個字串依次輪詢。兩個字串的排序需要(常規(guī)情況)O(m log m) + O(n log n)次操作,之后的線性掃描需要O(m+n)次操作。
??? 同樣拿上面的字串做例子,將會需要16*4 + 8*3 = 88加上對兩個字串線性掃描的16 + 8 = 24的操作。(隨著字串長度的增長,你會發(fā)現(xiàn)這個算法的效果會越來越好)
??? 關(guān)于采用何種排序方法,我們采用最常用的快速排序,下面的快速排序的代碼用的是以前寫的,比較好懂,并且,我執(zhí)意不用庫函數(shù)的qsort代碼。唯一的問題是,此前寫的代碼是針對整數(shù)進(jìn)行排序的,不過,難不倒我們,稍微改一下參數(shù),即可,如下:
//copyright@ 2011 July && yansha //July,updated,2011.04.23. #include <iostream> #include <string> using namespace std; //以前的注釋,還讓它保留著 int partition(string &str,int lo,int hi) { int key = str[hi]; //以最后一個元素,data[hi]為主元 int i = lo - 1; for(int j = lo; j < hi; j++) ///注,j從p指向的是r-1,不是r。 { if(str[j] <= key) { i++; swap(str[i], str[j]); } } swap(str[i+1], str[hi]); //不能改為swap(&data[i+1],&key) return i + 1; } //遞歸調(diào)用上述partition過程,完成排序。 void quicksort(string &str, int lo, int hi) { if (lo < hi) { int k = partition(str, lo, hi); quicksort(str, lo, k - 1); quicksort(str, k + 1, hi); } } //比較,上述排序O(m log m) + O(n log n),加上下面的O(m+n), //時間復(fù)雜度總計(jì)為:O(mlogm)+O(nlogn)+O(m+n)。 void compare(string str1,string str2) { int posOne = 0; int posTwo = 0; while (posTwo < str2.length() && posOne < str1.length()) { while (str1[posOne] < str2[posTwo] && posOne < str1.length() - 1) posOne++; //如果和str2相等,那就不能動。只有比str2小,才能動。 if (str1[posOne] != str2[posTwo]) break; //posOne++; //歸并的時候,str1[str1Pos] == str[str2Pos]的時候,只能str2Pos++,str1Pos不可以自增。 //多謝helloword指正。 posTwo++; } if (posTwo == str2.length()) cout << "true" << endl; else cout << "false" << endl; } int main() { string str1 = "ABCDEFGHLMNOPQRS"; string str2 = "DCGDSRQPOM"; //之前上面加了那句posOne++之所以有bug,是因?yàn)?#xff0c;@helloword: //因?yàn)閟tr1如果也只有一個D,一旦posOne++,就到了下一個不是'D'的字符上去了, //而str2有倆D,posTwo++后,下一個字符還是'D',就不等了,出現(xiàn)誤判。 quicksort(str1, 0, str1.length() - 1); quicksort(str2, 0, str2.length() - 1); //先排序 compare(str1, str2); //后線性掃描 return 0; }????
1.3、O(n+m)的計(jì)數(shù)排序方法
??? 此方案與上述思路相比,就是在排序的時候采用線性時間的計(jì)數(shù)排序方法,排序O(n+m),線性掃描O(n+m),總計(jì)時間復(fù)雜度為:O(n+m)+O(n+m)=O(n+m)。
??? 代碼如下:
#include <iostream> #include <string> using namespace std; // 計(jì)數(shù)排序,O(n+m) void CounterSort(string str, string &help_str) { // 輔助計(jì)數(shù)數(shù)組 int help[26] = {0}; // help[index]存放了等于index + 'A'的元素個數(shù) for (int i = 0; i < str.length(); i++) { int index = str[i] - 'A'; help[index]++; } // 求出每個元素對應(yīng)的最終位置 for (int j = 1; j < 26; j++) help[j] += help[j-1]; // 把每個元素放到其對應(yīng)的最終位置 for (int k = str.length() - 1; k >= 0; k--) { int index = str[k] - 'A'; int pos = help[index] - 1; help_str[pos] = str[k]; help[index]--; } } //線性掃描O(n+m) void Compare(string long_str,string short_str) { int pos_long = 0; int pos_short = 0; while (pos_short < short_str.length() && pos_long < long_str.length()) { // 如果pos_long遞增直到long_str[pos_long] >= short_str[pos_short] while (long_str[pos_long] < short_str[pos_short] && pos_long < long_str.length () - 1) pos_long++; // 如果short_str有連續(xù)重復(fù)的字符,pos_short遞增 while (short_str[pos_short] == short_str[pos_short+1]) pos_short++; if (long_str[pos_long] != short_str[pos_short]) break; pos_long++; pos_short++; } if (pos_short == short_str.length()) cout << "true" << endl; else cout << "false" << endl; } int main() { string strOne = "ABCDAK"; string strTwo = "A"; string long_str = strOne; string short_str = strTwo; // 對字符串進(jìn)行計(jì)數(shù)排序 CounterSort(strOne, long_str); CounterSort(strTwo, short_str); // 比較排序好的字符串 Compare(long_str, short_str); return 0; }
?不過上述方法,空間復(fù)雜度為O(n+m),即消耗了一定的空間。有沒有在線性時間,且空間復(fù)雜度較小的方案列?
?
第二節(jié)、尋求線性時間的解法
2.1、O(n+m)的hashtable的方法
??? 上述方案中,較好的方法是先對字符串進(jìn)行排序,然后再線性掃描,總的時間復(fù)雜度已經(jīng)優(yōu)化到了:O(m+n),貌似到了極限,還有沒有更好的辦法列?
??? 我們可以對短字串進(jìn)行輪詢(此思路的敘述可能與網(wǎng)上的一些敘述有出入,因?yàn)槲覀冏詈檬菓?yīng)該把短的先存儲,那樣,會降低題目的時間復(fù)雜度),把其中的每個字母都放入一個Hashtable里(我們始終設(shè)m為短字符串的長度,那么此項(xiàng)操作成本是O(m)或8次操作)。然后輪詢長字符串,在Hashtable里查詢短字符串的每個字符,看能否找到。如果找不到,說明沒有匹配成功,輪詢長字符串將消耗掉16次操作,這樣兩項(xiàng)操作加起來一共只有8+16=24次。
??? 當(dāng)然,理想情況是如果長字串的前綴就為短字串,只需消耗8次操作,這樣總共只需8+8=16次。
??? 或如夢想天窗所說: 我之前用散列表做過一次,算法如下:
?1、hash[26],先全部清零,然后掃描短的字符串,若有相應(yīng)的置1,
?2、計(jì)算hash[26]中1的個數(shù),記為m
?3、掃描長字符串的每個字符a;若原來hash[a] == 1 ,則修改hash[a] = 0,并將m減1;若hash[a] == 0,則不做處理
?4、若m == 0 or 掃描結(jié)束,退出循環(huán)。
??? 代碼實(shí)現(xiàn),也不難,如下:
//copyright@ 2011 yansha //July、updated,2011.04.25。 #include <iostream> #include <string> using namespace std; int main() { string str1="ABCDEFGHLMNOPQRS"; string str2="DCGSRQPOM"; // 開辟一個輔助數(shù)組并清零 int hash[26] = {0}; // num為輔助數(shù)組中元素個數(shù) int num = 0; // 掃描短字符串 for (int j = 0; j < str2.length(); j++) { // 將字符轉(zhuǎn)換成對應(yīng)輔助數(shù)組中的索引 int index = str1[j] - 'A'; // 如果輔助數(shù)組中該索引對應(yīng)元素為0,則置1,且num++; if (hash[index] == 0) { hash[index] = 1; num++; } } // 掃描長字符串 for (int k = 0; k < str1.length(); k++) { int index = str1[k] - 'A'; // 如果輔助數(shù)組中該索引對應(yīng)元素為1,則num--;為零的話,不作處理(不寫語句)。 if(hash[index] ==1) { hash[index] = 0; num--; if(num == 0) //m==0,即退出循環(huán)。 break; } } // num為0說明長字符串包含短字符串內(nèi)所有字符 if (num == 0) cout << "true" << endl; else cout << "false" << endl; return 0; }
?
2.2、O(n+m)的數(shù)組存儲方法
??? 有兩個字符串short_str和long_str。
??? 第一步:你標(biāo)記short_str中有哪些字符,在store數(shù)組中標(biāo)記為true。(store數(shù)組起一個映射的作用,如果有A,則將第1個單元標(biāo)記true,如果有B,則將第2個單元標(biāo)記true,... 如果有Z, 則將第26個單元標(biāo)記true)
??? 第二步:遍歷long_str,如果long_str中的字符包括short_str中的字符則將store數(shù)組中對應(yīng)位置標(biāo)記為false。(如果有A,則將第1個單元標(biāo)記false,如果有B,則將第2個單元標(biāo)記false,... 如果有Z, 則將第26個單元標(biāo)記false),如果沒有,則不作處理。
??? 第三步:此后,遍歷store數(shù)組,如果所有的元素都是false,也就說明store_str中字符都包含在long_str內(nèi),輸出true。否則,輸出false。
??? 舉個簡單的例子好了,如abcd,abcdefg倆個字符串,
??? 1、先遍歷短字符串a(chǎn)bcd,在store數(shù)組中想對應(yīng)的abcd的位置上的單元元素置為true,
??? 2、然后遍歷abcdefg,在store數(shù)組中相應(yīng)的abcd位置上,發(fā)現(xiàn)已經(jīng)有了abcd,則前4個的單元元素都置為false,當(dāng)我們已經(jīng)遍歷了4個元素,等于了短字符串a(chǎn)bcd的4個數(shù)目,所以,滿足條件,退出。
??? (不然,繼續(xù)遍歷的話,我們會發(fā)現(xiàn)efg在store數(shù)組中沒有元素,不作處理。最后,自然,就會發(fā)現(xiàn)store數(shù)組中的元素單元都是false的。)
??? 3、遍歷store數(shù)組,發(fā)現(xiàn)所有的元素都已被置為false,所以程序輸出true。
??? 其實(shí),這個思路和上一節(jié)中,O(n+m)的hashtable的方法代碼,原理是完全一致的,且本質(zhì)上都采用的數(shù)組存儲(hash表也是一個數(shù)組),但我并不認(rèn)為此思路多此一舉,所以仍然貼出來。ok,代碼如下://copyright@ 2011 Hession //July、updated,2011.04.23. #include<iostream> #include<string.h> using namespace std; int main() { char long_ch[]="ABCDEFGHLMNOPQRS"; char short_ch[]="DEFGHXLMNOPQ"; int i; bool store[58]; memset(store,false,58); //前兩個 是 遍歷 兩個字符串, 后面一個是 遍歷 數(shù)組 for(i=0;i<sizeof(short_ch)-1;i++) store[short_ch[i]-65]=true; for(i=0;i<sizeof(long_ch)-1;i++) { if(store[long_ch[i]-65]!=false) store[long_ch[i]-65]=false; } for(i=0;i<58;i++) { if(store[i]!=false) { cout<<"short_ch is not in long_ch"<<endl; break; } if(i==57) cout<<"short_ch is in long_ch"<<endl; } return 0; }
?
第三節(jié)、O(n)到O(n+m)的素?cái)?shù)方法
??? 我想問的是,還有更好的方案么?
??? 你可能會這么想:O(n+m)是你能得到的最好的結(jié)果了,至少要對每個字母至少訪問一次才能完成這項(xiàng)操作,而上一節(jié)最后的倆個方案是剛好是對每個字母只訪問一次。
??? ok,下面給出一個更好的方案:
??? 假設(shè)我們有一個一定個數(shù)的字母組成字串,我給每個字母分配一個素?cái)?shù),從2開始,往后類推。這樣A將會是2,B將會是3,C將會是5,等等。現(xiàn)在我遍歷第一個字串,把每個字母代表的素?cái)?shù)相乘。你最終會得到一個很大的整數(shù),對吧?
??? 然后——輪詢第二個字符串,用每個字母除它。如果除的結(jié)果有余數(shù),這說明有不匹配的字母。如果整個過程中沒有余數(shù),你應(yīng)該知道它是第一個字串恰好的子集了。
思路總結(jié)如下:
1.定義最小的26個素?cái)?shù)分別與字符'A'到'Z'對應(yīng)。
2.遍歷長字符串,求得每個字符對應(yīng)素?cái)?shù)的乘積。
3.遍歷短字符串,判斷乘積能否被短字符串中的字符對應(yīng)的素?cái)?shù)整除。
4.輸出結(jié)果。
??? 至此,如上所述,上述算法的時間復(fù)雜度為O(m+n),時間復(fù)雜度最好的情況為O(n)(遍歷短的字符串的第一個數(shù),與長字符串素?cái)?shù)的乘積相除,即出現(xiàn)余數(shù),便可退出程序,返回false),n為長字串的長度,空間復(fù)雜度為O(1)。如你所見,我們已經(jīng)優(yōu)化到了最好的程度。
??? 不過,正如原文中所述:“現(xiàn)在我想告訴你 —— Guy的方案(不消說,我并不認(rèn)為Guy是第一個想出這招的人)在算法上并不能說就比我的好。而且在實(shí)際操作中,你很可能仍會使用我的方案,因?yàn)樗ㄓ?#xff0c;無需跟麻煩的大型數(shù)字打交道。但從”巧妙水平“上講,Guy提供的是一種更、更、更有趣的方案。”
??? ok,如果你有更好的思路,歡迎在本文的評論中給出,非常感謝。#include <iostream> #include <string> #include "BigInt.h" using namespace std; // 素?cái)?shù)數(shù)組 int primeNumber[26] = {2, 3, 5, 7, 11, 13, 17, 19, 23, 29, 31, 37, 41, 43, 47, 53, 59, 61, 67, 71, 73, 79, 83, 89, 97, 101}; int main() { string strOne = "ABCDEFGHLMNOPQRS"; string strTwo = "DCGSRQPOM"; // 這里需要用到大整數(shù) CBigInt product = 1; //大整數(shù)除法的代碼,下頭給出。 // 遍歷長字符串,得到每個字符對應(yīng)素?cái)?shù)的乘積 for (int i = 0; i < strOne.length(); i++) { int index = strOne[i] - 'A'; product = product * primeNumber[index]; } // 遍歷短字符串 for (int j = 0; j < strTwo.length(); j++) { int index = strTwo[j] - 'A'; // 如果余數(shù)不為0,說明不包括短字串中的字符,跳出循環(huán) if (product % primeNumber[index] != 0) break; } // 如果積能整除短字符串中所有字符則輸出"true",否則輸出"false"。 if (strTwo.length() == j) cout << "true" << endl; else cout << "false" << endl; return 0; }???? 上述程序待改進(jìn)的地方:
1.只考慮大些字符,如果考慮小寫字符和數(shù)組的話,素?cái)?shù)數(shù)組需要更多素?cái)?shù)
2.沒有考慮重復(fù)的字符,可以加入判斷重復(fù)字符的輔助數(shù)組。
以下的大整數(shù)除法的代碼,雖然與本題目無多大關(guān)系,但為了保證文章的完整性,我還是決定把它貼出來,
代碼如下(點(diǎn)擊展開):
//copyright@ 2011/03/06 yansha //實(shí)現(xiàn)大整數(shù)類 #include <string> #include <vector> #include <iostream> using namespace std; class CBigInt { public: // input friend istream& operator >> (istream &, CBigInt &); // output friend ostream& operator << (ostream &os, const CBigInt &value) { if (value.bigInt[0] == '-') os << value.bigInt; else { // 正數(shù)不輸出符號 os << value.bigInt.substr(1); } return os; } friend bool operator == (const CBigInt &, const CBigInt &); friend bool operator < (const CBigInt &lValue, const CBigInt &rValue) { if (lValue.bigInt[0] != rValue.bigInt[0]) { // '+'ASCII碼為43,'-'ASCII碼為45 return lValue.bigInt[0] > rValue.bigInt[0]; } else { if (lValue.bigInt[0] == '+') return lValue.smaller(rValue.bigInt); // 正數(shù)的情況 else return lValue.greater(rValue.bigInt); // 負(fù)數(shù)的情況 } } friend bool operator > (const CBigInt &lValue, const CBigInt &rValue) { if (lValue.bigInt[0] != rValue.bigInt[0]) return lValue.bigInt[0] < rValue.bigInt[0]; else { if (lValue.bigInt[0] == '+') return lValue.greater(rValue.bigInt); else return lValue.smaller(rValue.bigInt); } } string bigInt; public: CBigInt(); CBigInt(int); CBigInt(const string &); CBigInt(const CBigInt &); CBigInt(const char *); CBigInt& operator = (const CBigInt &); CBigInt& operator += (const CBigInt &); CBigInt& operator -= (const CBigInt &); CBigInt& operator *= (const CBigInt &); CBigInt& operator /= (const CBigInt &); CBigInt& operator %= (const CBigInt &); // prefix increment CBigInt& operator ++ (); // prefix decrement CBigInt& operator -- (); // postfix increment CBigInt operator ++ (int); // postfix decrement CBigInt operator -- (int); private: // unsigned += void plus(const string &); // unsigned -= void minus(const string &); // unsigned == bool equal(const string &) const; // unsigned < bool smaller(const string &) const; // unsigned > bool greater(const string &) const; }; /************************************************************************/ /* 構(gòu)造函數(shù) /************************************************************************/ // 默認(rèn)構(gòu)造函數(shù) inline CBigInt::CBigInt() : bigInt("+0") {} // 構(gòu)造函數(shù) inline CBigInt::CBigInt(const string &str) : bigInt(str) { if (bigInt.size() > 0) { // 沒有正負(fù)符號 if (bigInt[0] != '+' && bigInt[0] != '-') { string::size_type i = 0; for (; i < bigInt.size() - 1 && bigInt[i] == '0'; i++); if (i > 0) bigInt.replace((string::size_type)0, i, "+"); else bigInt.insert((string::size_type)0, 1, '+'); } else { if (bigInt.size() == 1) bigInt = "+0"; else { string::size_type j = 1; // 去掉多余的0 for (; j < bigInt.size() - 1 && bigInt[j] == '0'; j++); if (j > 1) bigInt.erase((string::size_type)1, j - 1); if (bigInt == "-0") bigInt = "+0"; } } } else bigInt = "+0"; } // 復(fù)制構(gòu)造函數(shù) inline CBigInt::CBigInt(const CBigInt &value) : bigInt(value.bigInt) {} inline CBigInt::CBigInt(int num) { if (num == 0) bigInt = "+0"; else if (num > 0) bigInt = '+'; else { bigInt = '-'; num *= -1; } string temp = ""; while (num != 0) { temp += num % 10 + '0'; num = num / 10; } for (int i = temp.size() - 1; i >= 0; i--) bigInt += temp[i]; } inline CBigInt::CBigInt(const char *str) : bigInt(str) { if (bigInt.size() > 0) { if (bigInt[0] != '+' && bigInt[0] != '-') { string::size_type i = 0; // 去除多余的0 for (; i < bigInt.size() - 1 && bigInt[i] == '0'; i++); if (i > 0) bigInt.replace((string::size_type)0, i, "+"); else bigInt.insert((string::size_type)0, 1, '+'); } else { if (bigInt.size() == 0) bigInt = "+0"; else { string::size_type j = 1; for (; j < bigInt.size() - 1 && bigInt[j] == '0'; j++); if (j > 1) bigInt.erase((string::size_type)1, j - 1); // 處理特殊情況“-0” if (bigInt == "-0") bigInt = "+0"; } } } else bigInt = "+0"; } inline bool operator == (const CBigInt &lValue, const CBigInt &rValue) { return lValue.bigInt == rValue.bigInt; } inline bool operator != (const CBigInt &lValue, const CBigInt &rValue) { return !(lValue.bigInt == rValue.bigInt); } inline bool operator <= (const CBigInt &lValue, const CBigInt &rValue) { return !(lValue > rValue); } inline bool operator >= (const CBigInt &lValue, const CBigInt &rValue) { return !(lValue < rValue); } inline CBigInt& CBigInt::operator = (const CBigInt &value) { bigInt = value.bigInt; return *this; } // unsigned == inline bool CBigInt::equal(const string &value) const { return bigInt.substr(1) == value.substr(1); } // unsigned < inline bool CBigInt::smaller(const string &value) const { if (bigInt.size() == value.size()) return bigInt.substr(1) < value.substr(1); else return bigInt.size() < value.size(); } // unsigned > inline bool CBigInt::greater(const string &value) const { if (bigInt.size() == value.size()) return bigInt.substr(1) > value.substr(1); else return bigInt.size() > value.size(); } /************************************************************************/ /* 實(shí)現(xiàn)+,-,*,/運(yùn)算 /************************************************************************/ void CBigInt::plus(const string &value) { if (bigInt.size() < value.size()) bigInt.insert((string::size_type)1, (value.size() - bigInt.size()), '0'); string::size_type i = bigInt.size() - 1; string::size_type j = value.size() - 1; while (j > 1) { bigInt[i] += value[j] - '0'; if (bigInt[i] > '9') { bigInt[i] -= 10; ++bigInt[i-1]; } i--; j--; } // 最高位進(jìn)位 bigInt[i] += value[1] - '0'; while (i > 1 && bigInt[i] > '9') { bigInt[i] -= 10; i--; ++bigInt[i]; } if (bigInt[1] > '9') { bigInt[1] -= 10; bigInt.insert((string::size_type)1, 1, '1'); } } void CBigInt::minus(const string &vlaue) { string::size_type i = bigInt.size() - 1; string::size_type j = vlaue.size() - 1; while (j >= 1) { bigInt[i] -= vlaue[j] - '0'; if (bigInt[i] < '0') { bigInt[i] += 10; --bigInt[i-1]; } i--; j--; } // 向前借位 while (i > 1 && bigInt[i] < '0') { bigInt[i] += 10; i--; --bigInt[i]; } // 去除多余的0 string::size_type k = 1; for (; k < bigInt.size() - 1 && bigInt[k] == '0'; k++); if (k > 1) bigInt.erase((string::size_type)1, k - 1); } CBigInt& CBigInt::operator += (const CBigInt &value) { if (bigInt[0] == value.bigInt[0]) plus(value.bigInt); else { // 互為相反數(shù)的情況 if (equal(value.bigInt)) bigInt = "+0"; // 絕對值小于的情況 else if (smaller(value.bigInt)) { string temp = bigInt; bigInt = value.bigInt; minus(temp); } else minus(value.bigInt); } return *this; } CBigInt& CBigInt::operator -= (const CBigInt &value) { // 處理過程與+=類似 if (bigInt[0] == value.bigInt[0]) { if (equal(value.bigInt)) bigInt = "+0"; else if (smaller(value.bigInt)) { string temp = bigInt; bigInt = value.bigInt; minus(temp); if (bigInt[0] == '+') bigInt[0] = '-'; else bigInt[0] = '+'; } else minus(value.bigInt); } else plus(value.bigInt); return *this; } CBigInt operator + (const CBigInt &lValue, const CBigInt &rValue) { CBigInt sum(lValue); sum += rValue; return sum; } CBigInt operator - (const CBigInt &lValue, const CBigInt &rValue) { CBigInt diff(lValue); diff -= rValue; return diff; } // prefix increment CBigInt& CBigInt::operator ++ () { string::size_type i = bigInt.size() - 1; if (bigInt[0] == '+') { ++bigInt[i]; while (i > 1 && bigInt[i] > '9') { bigInt[i] -= 10; i--; ++bigInt[i]; } if (bigInt[i] > '9') { bigInt[i] -= 10; bigInt.insert((string::size_type)1, 1, '1'); } } else { --bigInt[i]; while(i > 1 && bigInt[i] < '0') { bigInt[i] += 10; i--; --bigInt[i]; } string::size_type j = 1; for (; j < bigInt.size() - 1 && bigInt[j] == '0'; j++); if (j > 1) bigInt.erase(1, j - 1); if (bigInt[1] == '0') bigInt[0] = '+'; } return *this; } CBigInt& CBigInt::operator -- () { string::size_type i = bigInt.size() - 1; // 對正數(shù)和負(fù)數(shù)分別處理 if (bigInt[0] == '+') { // 對0進(jìn)行處理 if (bigInt[1] == '0') bigInt = "-1"; else { --bigInt[i]; while (i > 1 && bigInt[i] < '0') { bigInt[i] += 10; i--; --bigInt[i]; } string::size_type j = 1; for (; j < bigInt.size() - 1 && bigInt[j] == '0'; j++); if (j > 1) bigInt.erase(1, j - 1); } } else { ++bigInt[i]; while (i > 1 && bigInt[i] > '9') { bigInt[i] -= 10; i--; ++bigInt[i]; } if (bigInt[1] > '9') { bigInt[1] += 10; bigInt.insert((string::size_type)1, 1, '1'); } } return *this; } // postfix increment CBigInt CBigInt::operator ++ (int) { CBigInt temp(*this); ++(*this); return temp; } // postfix decrement CBigInt CBigInt::operator -- (int) { CBigInt temp(*this); --(*this); return temp; } // 模擬筆算過程 CBigInt& CBigInt::operator *= (const CBigInt &value) { // 乘數(shù)或被乘數(shù)有一方為0則返回結(jié)果0 if (bigInt[1] == '0' || value.bigInt[1] == '0') { bigInt = "+0"; return *this; } string::size_type sizeofMultiplicand = bigInt.size(); string::size_type sizeofMultiplier = value.bigInt.size(); vector<unsigned int> product(sizeofMultiplier + sizeofMultiplicand - 1); // 初始化 for (string::size_type i = 1; i < sizeofMultiplicand; ++i) bigInt[i] -= '0'; // 筆算乘法過程 for (string::size_type j = sizeofMultiplier - 1; j > 0; --j) { if (value.bigInt[j] > '0') { for (string::size_type k = sizeofMultiplicand - 1; k > 0; --k) product[k+j] += bigInt[k] * (value.bigInt[j] - '0'); } } // 處理符號 if (bigInt[0] == value.bigInt[0]) product[0] = '+'; else product[0] = '-'; vector<unsigned int>::size_type sizeofProduct = product.size(); bigInt = string(sizeofProduct, '0'); bigInt[0] = product[0]; // 處理進(jìn)位問題 for (vector<unsigned int>::size_type n = sizeofProduct - 1; n > 1; --n) { product[n-1] += product[n] / 10; product[n] %= 10; bigInt[n] += product[n]; } if (product[1] == 0) bigInt.erase(1, 1); else bigInt[1] += product[1]; return *this; } // 重復(fù)做差法求商 CBigInt& CBigInt::operator /= (const CBigInt &value) { // 除數(shù)為0 if (value.bigInt == "+0") { bigInt = "*Error!"; return *this; } // 被除數(shù)大于除數(shù) if (value.smaller(bigInt) == true) { string::size_type sizeofDividend = bigInt.size(); string::size_type sizeofDivisor = value.bigInt.size(); string answer(sizeofDividend, '0'); // 符號處理 if (bigInt[0] == value.bigInt[0]) answer[0] = '+'; else answer[0] = '-'; string::size_type start = 1; string::size_type end = sizeofDivisor - 1; while (end < sizeofDividend) { // 試商過程,從高位到低位 while (value.greater(bigInt.substr(start - 1, end - start + 2)) == false) { string::size_type j = end; // 減法過程 for (string::size_type i = sizeofDivisor - 1; i > 0; i--, j--) { bigInt[j] -= value.bigInt[i] - '0'; if (bigInt[j] < '0') { bigInt[j] += 10; --bigInt[j-1]; } } // 商的相應(yīng)位加1 ++answer[end]; // 以除數(shù)做邊界去掉前綴0 while (start <= end && bigInt[start] == '0') ++start; } // 以被除數(shù)做邊界去掉前綴0 while (start < sizeofDividend && bigInt[start] == '0') ++start; // 如果end-start+1 < sizeofDivisor - 1,則進(jìn)行補(bǔ)位 if (end - start + 2 < sizeofDivisor) end = sizeofDivisor + start - 2; else ++end; } start = 1; for (; start < answer.size() - 1 && answer[start] == '0'; ++start); if (start > 1) answer.erase(1, start - 1); bigInt = answer; } // 絕對值相等的情況 else if (value.equal(bigInt) == true) { string answer = "-1"; if (bigInt[0] == value.bigInt[0]) answer = "+1"; bigInt = answer; } else bigInt = "+0"; return *this; } // 求余,與上面去商過程基本一致 CBigInt& CBigInt::operator %= (const CBigInt &value) { if (value.bigInt == "+0") { bigInt = "*Error!"; return *this; } // 求余,余數(shù)為剩余bigInt值 if (value.smaller(bigInt) == true) { string::size_type sizeofDivident = bigInt.size(); string::size_type sizeofDivisor = value.bigInt.size(); string::size_type start = 1; string::size_type end = sizeofDivisor - 1; while (end < sizeofDivident) { // 除數(shù)的值不大于被除數(shù)的值 while (value.greater(bigInt.substr(start - 1, end - start + 2)) == false) { string::size_type j = end; for (string::size_type i = sizeofDivisor - 1; i > 0; --i, --j) { bigInt[j] -= value.bigInt[i] - '0'; if (bigInt[j] < '0') { bigInt[j] += 10; --bigInt[j-1]; } } while (start <= end && bigInt[start] == '0') ++start; } while (start < sizeofDivident && bigInt[start] == '0') ++start; if (end - start + 2 < sizeofDivisor) end = sizeofDivisor + start - 2; else ++end; } start = 1; for (; start < sizeofDivident - 1 && bigInt[start] == '0'; start++); if (start > 1) bigInt.erase(1, start - 1); if (bigInt == "-0") bigInt[0] = '+'; } else if (value.equal(bigInt)) bigInt = "+0"; return *this; } CBigInt operator * (const CBigInt &lValue, const CBigInt &rValue) { CBigInt product(lValue); product *= rValue; return product; } CBigInt operator / (const CBigInt &lValue, const CBigInt &rValue) { CBigInt quotient(lValue); quotient /= rValue; return quotient; } CBigInt operator % (const CBigInt &lValue, const CBigInt &rValue) { CBigInt mod(lValue); mod %= rValue; return mod; }
??? 說明:此次的判斷字符串是否包含問題,來自一位外國網(wǎng)友提供的gofish、google面試題,這個題目出自此篇文章:http://www.aqee.net/2011/04/11/google-interviewing-story/,文章記錄了整個面試的過程,比較有趣,值得一讀。
??? 擴(kuò)展:正如網(wǎng)友安逸所說:其實(shí)這個問題還可以轉(zhuǎn)換為:a和b兩個字符串,求b串包含a串的最小長度。包含指的就是b的字串包含a中每個字符。
?
第四節(jié)、字符串是否包含問題的繼續(xù)補(bǔ)充
??? updated:本文發(fā)布后,得到很多朋友的建議和意見,其中nossiac,luuillu等倆位網(wǎng)友除了給出具體的思路之外,還給出了代碼,征得同意,下面,我將引用他們的的思路及代碼,繼續(xù)就這個字符串是否包含問題深入闡述。
??? 4.1、在引用nossiac的思路之前,我得先給你介紹下什么是Bit-map?
??? Oliver:所謂的Bit-map就是用一個bit位來標(biāo)記某個元素對應(yīng)的Value, 而Key即是該元素。由于采用了Bit為單位來存儲數(shù)據(jù),因此在存儲空間方面,可以大大節(jié)省。
??? 如果看了以上說的還沒明白什么是Bit-map,那么我們來看一個具體的例子,假設(shè)我們要對0-7內(nèi)的5個元素(4,7,2,5,3)排序(這里假設(shè)這些元素沒有重復(fù))。那么我們就可以采用Bit-map的方法來達(dá)到排序的目的。要表示8個數(shù),我們就只需要8個Bit(1Bytes),首先我們開辟1Byte的空間,將這些空間的所有Bit位都置為0,如下圖:
??? 然后遍歷這5個元素,首先第一個元素是4,那么就把4對應(yīng)的位置為1(可以這樣操作:p+(i/8)|(0x01<<(i%8))當(dāng)然了這里的操作涉及到Big-ending和Little-ending的情況,這里默認(rèn)為Big-ending),因?yàn)槭菑牧汩_始的,所以要把第五位置為一(如下圖):
??? 接著再處理第二個元素7,將第八位置為1,,接著再處理第三個元素,一直到最后處理完所有的元素,將相應(yīng)的位置為1,這時候的內(nèi)存的Bit位的狀態(tài)如下:
??? 最后我們現(xiàn)在遍歷一遍Bit區(qū)域,將該位是一的位的編號輸出(2,3,4,5,7),這樣就達(dá)到了排序的目的。
??? 代碼示例:
//位圖的一個示例 //copyright@ Oliver && July //http://blog.redfox66.com/post/2010/09/26/mass-data-4-bitmap.aspx //July、updated,2011.04.25. #include <memory.h> #include <stdio.h> //定義每個Byte中有8個Bit位 #define BYTESIZE 8 void SetBit(char *p, int posi) { for(int i=0; i < (posi/BYTESIZE); i++) { p++; } *p = *p|(0x01<<(posi%BYTESIZE)); //將該Bit位賦值1 return; } void BitMapSortDemo() { //為了簡單起見,我們不考慮負(fù)數(shù) int num[] = {3,5,2,10,6,12,8,14,9}; //BufferLen這個值是根據(jù)待排序的數(shù)據(jù)中最大值確定的 //待排序中的最大值是14,因此只需要2個Bytes(16個Bit) //就可以了。 const int BufferLen = 2; char *pBuffer = new char[BufferLen]; //要將所有的Bit位置為0,否則結(jié)果不可預(yù)知。 memset(pBuffer,0,BufferLen); for(int i=0;i<9;i++) { //首先將相應(yīng)Bit位上置為1 SetBit(pBuffer,num[i]); } //輸出排序結(jié)果 for(i=0;i<BufferLen;i++) //每次處理一個字節(jié)(Byte) { for(int j=0;j<BYTESIZE;j++) //處理該字節(jié)中的每個Bit位 { //判斷該位上是否是1,進(jìn)行輸出,這里的判斷比較笨。 //首先得到該第j位的掩碼(0x01<<j),將內(nèi)存區(qū)中的 //位和此掩碼作與操作。最后判斷掩碼是否和處理后的 //結(jié)果相同 if((*pBuffer&(0x01<<j)) == (0x01<<j)) { printf("%d ",i*BYTESIZE + j); } } pBuffer++; } printf("\n"); } int main() { BitMapSortDemo(); return 0; }
??? 位圖總結(jié):
????? 1、可進(jìn)行數(shù)據(jù)的快速查找,判重,刪除,一般來說數(shù)據(jù)范圍是int的10倍以下
????? 2、使用bit數(shù)組來表示某些元素是否存在,比如8位電話號碼
????? 3、Bloom filter(日后介紹)可以看做是對bit-map的擴(kuò)展
??? 問題實(shí)例:
??? 1)已知某個文件內(nèi)包含一些電話號碼,每個號碼為8位數(shù)字,統(tǒng)計(jì)不同號碼的個數(shù)。?
???????8位最多99 999 999,大概需要99m個bit,大概10幾m字節(jié)的內(nèi)存即可。 (可以理解為從0-99 999 999的數(shù)字,每個數(shù)字對應(yīng)一個Bit位,所以只需要99M個Bit==12MBytes,這樣,就用了小小的12M左右的內(nèi)存表示了所有的8位數(shù)的電話)
??? 2)2.5億個整數(shù)中找出不重復(fù)的整數(shù)的個數(shù),內(nèi)存空間不足以容納這2.5億個整數(shù)。
??????將bit-map擴(kuò)展一下,用2bit表示一個數(shù)即可,0表示未出現(xiàn),1表示出現(xiàn)一次,2表示出現(xiàn)2次及以上,在遍歷這些數(shù)的時候,如果對應(yīng)位置的值是0,則將其置為1;如果是1,將其置為2;如果是2,則保持不變。或者我們不用2bit來進(jìn)行表示,我們用兩個bit-map即可模擬實(shí)現(xiàn)這個2bit-map,都是一樣的道理。
??? ok,介紹完了什么是bit-map,接下來,咱們回到正題,來看下nossiac關(guān)于此字符串是否包含問題的思路(http://www.shello.name/me/?p=64):
每個字母的ASCII碼值,可以對應(yīng)一個位圖中的位。
先遍歷第一個字符串,生成一個“位圖字典”。
用偽代碼表示就是:
?dictionary = 0
?for x in String1:
??dictionary |= 0x01<<x-'a'
紅色部分就是構(gòu)造位圖字典的過程,剛好能運(yùn)用ASCII碼值完成,取巧,呵呵,比較愜意。
然后,我們遍歷第二個字符串,用查字典的方式較檢,偽代碼為:
?for x in String2:
??if dictionary != dictionary|0x01<<x-'a':
??print("NO")
?else:
??print("YES")
what?還不夠明白,ok,看yansha對此思路的具體闡述吧:
此思路是位操作的典型應(yīng)用:
dictionary = 0
for x in String1:
?? dictionary |= 0x01 << (x - 'a');
分析如下:
dictionary是一個32位的int,初始化為0
| 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 |
dictionary |= 0x01 << (x - 'a')則是把字符映射到dictionary當(dāng)中的某一位;
比方String1 = "abde";
1、當(dāng)x為‘a(chǎn)’時,x-‘a(chǎn)’為0,所以0x01<<0 為0x01。
那么dictionary |= 0x01,也就是將dictionary的第一位置1。
此時dictionary為:
| 1 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 |
2、當(dāng)x為‘b’時,x-‘b’為1,所以0x01<<1 為0x02。
那么dictionary |= 0x02,也就是將dictionary的第二位置1。
此時dictionary為:
| 1 | 1 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 |
3、當(dāng)x為‘d’時,x-‘d’為3,所以0x01<<3 為0x08。
那么dictionary |= 0x08,也就是將dictionary的第四位置1。
此時dictionary為:
| 1 | 1 | 0 | 1 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 |
4、當(dāng)x為‘e’時,x-‘e’為4,所以0x01<<4 為0x10。
那么dictionary |= 0x10,也就是將dictionary的第五位置1。
此時dictionary為:
| 1 | 1 | 0 | 1 | 1 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 |
其他字符依此類推,比較過程也類似。對于128個字符的ASCII碼而言,顯然一個32位的整形是不夠的。
OK,算法完成。時間復(fù)雜度為 O(m+n),空間復(fù)雜度為O(1)。
??? 然后,代碼可以編寫如下:
//copyright@ nossiac //July、updated,2011.04.24。 #include <stdio.h> #include <string.h> #define getbit(x) (1<<(x-'a')) void a_has_b(char * a, char * b) { int i = 0; int dictionary = 0; int alen = strlen(a); int blen = strlen(b); for(i=0;i<alen;i++) dictionary |= getbit(a[i]); for(i=0;i<blen;i++) { if(dictionary != (dictionary|getbit(b[i]))) break; } if(i==blen) printf("YES! A has B!\n"); else printf("NiO! Char at %d is not found in dictionary!\n",i); } int main() { char * str1="abcdefghijklmnopqrstuvwxyz"; char * str2="akjsdfasdfiasdflasdfjklffhasdfasdfjklasdfjkasdf"; char * str3="asdffaxcfsf"; char * str4="asdfai"; a_has_b(str1, str2); a_has_b(str1, str3); a_has_b(str3, str4); return 0; }
???? 4.2、還可以如luuillu所說,判斷字符串是否包含,采用移位的方法(此帖子第745樓:http://topic.csdn.net/u/20101126/10/b4f12a00-6280-492f-b785-cb6835a63dc9_8.html?seed=423056362&r=72955051#r_72955051),他的代碼編寫如下://copyright@ luuillu //July、updated,2011.04.24。 #include <iostream> using namespace std; //判斷 des 是否包含在 src 中 bool compare(char *des,char * src) { unsigned index[26]={1,2,4,8,16,32,64,128,256,512,1024,1<<11, 1<<12,1<<13,1<<14,1<<15,1<<16,1<<17,1<<18,1<<19, 1<<20,1<<21,1<<22,1<<23,1<<24,1<<25}; //2的n次冪 unsigned srcdata=0; unsigned desdata=0; while( *src) srcdata|=index[(*src++)-'A']; while(*des) desdata|=index[(*des++)-'A']; return (srcdata|desdata) == srcdata ; } int main() { char *src="ABCDEFGHLMNOPQRS"; char *des="DCGSRQPOM"; cout<<compare(des,src)<<endl; return 0; }
??? 第四節(jié)總結(jié):正如十一文章在本文評論里所提到的那樣,上面的位圖法,hash,還有bitmap三者之間并沒有本質(zhì)上的區(qū)別,只是形式上不同而已。
?
第五節(jié)、字符串相關(guān)問題擴(kuò)展與闡述
5.1、字符串匹配問題
題目描述:
假設(shè)兩個字符串中所含有的字符和個數(shù)都相同我們就叫這兩個字符串匹配,比如:abcda和adabc,
由于出現(xiàn)的字符個數(shù)都是相同,只是順序不同,所以這兩個字符串是匹配的。
要求高效實(shí)現(xiàn)下面的函數(shù): boolen Is_Match(char *str1,char *str2)。
??? 分析:可以看出,此字符串的匹配問題,是與上述字符串包含的問題相類似的,這個問題可以先排序再比較,也可以利用hash表進(jìn)行判斷。這里給出一種hash表的方法,原理已在上文中闡明了,代碼如下://copyright@ 2011 yansha //July、updated,2011.04.24。 #include <iostream> #include <string> using namespace std; bool Is_Match(const char *strOne,const char *strTwo) { int lenOfOne = strlen(strOne); int lenOfTwo = strlen(strTwo); // 如果長度不相等則返回false if (lenOfOne != lenOfTwo) return false; // 開辟一個輔助數(shù)組并清零 int hash[26] = {0}; // 掃描字符串 for (int i = 0; i < strlen(strOne); i++) { // 將字符轉(zhuǎn)換成對應(yīng)輔助數(shù)組中的索引 int index = strOne[i] - 'A'; // 輔助數(shù)組中該索引對應(yīng)元素加1,表示該字符的個數(shù) hash[index]++; } // 掃描字符串 for (int j = 0; j < strlen(strTwo); j++) { int index = strTwo[j] - 'A'; // 如果輔助數(shù)組中該索引對應(yīng)元素不為0則減1,否則返回false if (hash[index] != 0) hash[index]--; else return false; } return true; } int main() { string strOne = "ABBA"; string strTwo = "BBAA"; bool flag = Is_Match(strOne.c_str(), strTwo.c_str()); // 如果為true則匹配,否則不匹配 if (flag == true) cout << "Match" << endl; else cout << "No Match" << endl; return 0; }?
5.2、在字符串中查找子串
題目描述:
給定一個字符串A,要求在A中查找一個子串B。
如A="ABCDF",要你在A中查找子串B=“CD”。
??? 分析:比較簡單,相當(dāng)于實(shí)現(xiàn)strstr庫函數(shù),主體代碼如下://copyright@ 2011 July && luoqitai //string為模式串,substring為要查找的子串 int strstr(char *string,char *substring) { int len1=strlen(string); int len2=strlen(substring); for (int i=0; i<=len1-len2; i++) //復(fù)雜度為O(m*n) { for (int j=0; j<len2; j++) { if (string[i+j]!=substring[j]) break; } if (j==len2) return i+1; } return 0; }
??? 上述程序已經(jīng)實(shí)現(xiàn)了在字符串中查找第一個子串的功能,時間復(fù)雜度為O(n*m),繼續(xù)的優(yōu)化可以先對兩個字符串進(jìn)行排序,然后再查找,也可以用KMP算法,復(fù)雜度為O(m+n)。具體的,在此不再贅述(多謝hlm_87指正)。?
??? 擴(kuò)展:還有個類似的問題:第17題(字符串):題目:在一個字符串中找到第一個只出現(xiàn)一次的字符。如輸入abaccdeff,則輸出b。代碼,可編寫如下(測試正確):
#include <iostream> using namespace std; //查找第一個只出現(xiàn)一次的字符,第1個程序 //copyright@ Sorehead && July //July、updated,2011.04.24. char find_first_unique_char(char *str) { int data[256]; char *p; if (str == NULL) return '\0'; memset(data, 0, sizeof(data)); //數(shù)組元素先全部初始化為0 p = str; while (*p != '\0') data[(unsigned char)*p++]++; //遍歷字符串,在相應(yīng)位置++,(同時,下標(biāo)強(qiáng)制轉(zhuǎn)換) while (*str != '\0') { if (data[(unsigned char)*str] == 1) //最后,輸出那個第一個只出現(xiàn)次數(shù)為1的字符 return *str; str++; } return '\0'; } int main() { char *str = "afaccde"; cout << find_first_unique_char(str) << endl; return 0; }??當(dāng)然,代碼也可以這么寫(測試正確):?
//查找第一個只出現(xiàn)一次的字符,第2個程序 //copyright@ yansha //July、updated,2011.04.24. char FirstNotRepeatChar(char* pString) { if(!pString) return '\0'; const int tableSize = 256; int hashTable[tableSize] = {0}; //存入數(shù)組,并初始化為0 char* pHashKey = pString; while(*(pHashKey) != '\0') hashTable[*(pHashKey++)]++; while(*pString != '\0') { if(hashTable[*pString] == 1) return *pString; pString++; } return '\0'; //沒有找到滿足條件的字符,退出 }
5.3、字符串轉(zhuǎn)換為整數(shù)
題目:輸入一個表示整數(shù)的字符串,把該字符串轉(zhuǎn)換成整數(shù)并輸出。
例如輸入字符串"345",則輸出整數(shù)345。
??? 分析:此題看起來,比較簡單,每掃描到一個字符,我們把在之前得到的數(shù)字乘以10再加上當(dāng)前字符表示的數(shù)字。這個思路用循環(huán)不難實(shí)現(xiàn)。然其背后卻隱藏著不少陷阱,正如zhedahht 所說,有以下幾點(diǎn)需要你注意:
??? 1、由于整數(shù)可能不僅僅之含有數(shù)字,還有可能以'+'或者'-'開頭,表示整數(shù)的正負(fù)。如果第一個字符是'+'號,則不需要做任何操作;如果第一個字符是'-'號,則表明這個整數(shù)是個負(fù)數(shù),在最后的時候我們要把得到的數(shù)值變成負(fù)數(shù)。
??? 2、如果使用的是指針的話,在使用指針之前,我們要做的第一件是判斷這個指針是不是為空。如果試著去訪問空指針,將不可避免地導(dǎo)致程序崩潰(此第2點(diǎn)在下面的程序不需注意,因?yàn)闆]有用到指針)。
??? 3、輸入的字符串中可能含有不是數(shù)字的字符。
每當(dāng)碰到這些非法的字符,我們就沒有必要再繼續(xù)轉(zhuǎn)換。
??? 4、溢出問題。由于輸入的數(shù)字是以字符串的形式輸入,因此有可能輸入一個很大的數(shù)字轉(zhuǎn)換之后會超過能夠表示的最大的整數(shù)而溢出。
??? 總結(jié)以上四點(diǎn),代碼可以如下編寫:
//字符串轉(zhuǎn)換為整數(shù) //copyright@ yansha #include <iostream> #include <string> using namespace std; int str_2_int(string str) { if (str.size() == 0) exit(0); int pos = 0; int sym = 1; // 處理符號 if (str[pos] == '+') pos++; else if (str[pos] == '-') { pos++; sym = -1; } int num = 0; // 逐位處理 while (pos < str.length()) { // 處理數(shù)字以外的字符 if (str[pos] < '0' || str[pos] > '9') exit(0); num = num * 10 + (str[pos] - '0'); // 處理溢出 if (num < 0) exit(0); pos++; } num *= sym; return num; } int main() { string str = "-3450"; int num = str_2_int(str); cout << num << endl; return 0; }
??? @helloword:這個的實(shí)現(xiàn)非常不好,當(dāng)輸入字符串參數(shù)為非法時,不是拋出異常不是返回error code,而是直接exit了。直接把進(jìn)程給終止了,想必現(xiàn)實(shí)應(yīng)用中的實(shí)現(xiàn)都不會這樣。建議您改改,不然拿到面試官那,會被人噴死的。ok,聽從他的建議,借用zhedahht的代碼了:
//http://zhedahht.blog.163.com/blog/static/25411174200731139971/ enum Status {kValid = 0, kInvalid}; int g_nStatus = kValid; int StrToInt(const char* str) { g_nStatus = kInvalid; long long num = 0; if(str != NULL) { const char* digit = str; // the first char in the string maybe '+' or '-' bool minus = false; if(*digit == '+') digit ++; else if(*digit == '-') { digit ++; minus = true; } // the remaining chars in the string while(*digit != '\0') { if(*digit >= '0' && *digit <= '9') { num = num * 10 + (*digit - '0'); // overflow if(num > std::numeric_limits<int>::max()) { num = 0; break; } digit ++; } // if the char is not a digit, invalid input else { num = 0; break; } } if(*digit == '\0') { g_nStatus = kValid; if(minus) num = 0 - num; } } return static_cast<int>(num); }
updated:
yansha看到了上述helloword的所說的后,修改如下:
#include <iostream> #include <string> #include <assert.h> using namespace std; int str_2_int(string str) { assert(str.size() > 0); int pos = 0; int sym = 1; // 處理符號 if (str[pos] == '+') pos++; else if (str[pos] == '-') { pos++; sym = -1; } int num = 0; // 逐位處理 while (pos < str.length()) { // 處理數(shù)字以外的字符 assert(str[pos] >= '0'); assert(str[pos] <= '9'); num = num * 10 + (str[pos] - '0'); // 處理溢出 assert(num >= 0); pos++; } num *= sym; return num; } int main() { string str = "-1024"; int num = str_2_int(str); cout << num << endl; return 0; }??
5.4、字符串拷貝
題目描述:
??? 要求實(shí)現(xiàn)庫函數(shù)strcpy,
原型聲明:extern char *strcpy(char *dest,char *src);
功能:把src所指由NULL結(jié)束的字符串復(fù)制到dest所指的數(shù)組中。
說明:src和dest所指內(nèi)存區(qū)域不可以重疊且dest必須有足夠的空間來容納src的字符串。
返回指向dest的指針。
??? 分析:如果編寫一個標(biāo)準(zhǔn)strcpy函數(shù)的總分值為10,下面給出幾個不同得分的答案://2分 void strcpy( char *strDest, char *strSrc ) { while( (*strDest++ = * strSrc++) != '\0' ); } //4分 void strcpy( char *strDest, const char *strSrc ) { //將源字符串加const,表明其為輸入?yún)?shù),加2分 while( (*strDest++ = * strSrc++) != '\0' ); } //7分 void strcpy(char *strDest, const char *strSrc) { //對源地址和目的地址加非0斷言,加3分 assert( (strDest != NULL) && (strSrc != NULL) ); while( (*strDest++ = * strSrc++) != '\0' ); } //10分 //為了實(shí)現(xiàn)鏈?zhǔn)讲僮?#xff0c;將目的地址返回,加3分! char * strcpy( char *strDest, const char *strSrc ) { assert( (strDest != NULL) && (strSrc != NULL) ); char *address = strDest; while( (*strDest++ = * strSrc++) != '\0' ); return address; }
聯(lián)系作者:
微博:http://weibo.com/julyweibo @ July,http://weibo.com/yanshazi?@ yansha。
郵箱:zhoulei0907@yahoo.cn @ July,yansha0@hotmail.com @ yansha。
預(yù)告:程序員面試題狂想曲、第三章,4月底之前發(fā)布。
ok,以上,有任何問題,歡迎任何人不吝指正。謝謝。完。
版權(quán)聲明:1、嚴(yán)禁用于任何商業(yè)用途;2、未經(jīng)許可,嚴(yán)禁出版;3、轉(zhuǎn)載,務(wù)必注明出處。
轉(zhuǎn)載于:https://www.cnblogs.com/v-July-v/archive/2011/04/23/2076003.html
總結(jié)
以上是生活随笔為你收集整理的程序员编程艺术:第二章、字符串是否包含问题的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 工行宇宙星座信用卡额度一般是多少?资质决
- 下一篇: 巧用脚本为木马“整容”