1、從字串A中找到匹配字串B的第一個子串的位置,以下的程式碼經測試比Plauger的stl(VC7)中的
::std::string::find要快30%左右
// 仿函式版本
template < class Character = char, class Size = int >
struct StringFinder
{
typedef Size size_t;
typedef Character char_t;
size_t operator()( const char_t* lpszSource, const char_t* lpszSearch )
{
// maybe the processing of the following line can delay to the caller
if ( ( NULL == lpszSource ) || ( NULL == lpszSearch ) ) return -1;
const char_t& cSource = *lpszSource;
const char_t& cSearch = *lpszSearch;
for ( ; ; )
{
if ( 0 == *lpszSearch )
{
return ( lpszSource - ( lpszSearch - &cSearch ) - &cSource );
}
if ( 0 == *lpszSource ) return -1;
if ( *lpszSource != *lpszSearch )
{
lpszSource -= lpszSearch - &cSearch - 1;
lpszSearch = &cSearch;
continue;
}
++ lpszSource;
++ lpszSearch;
}
return -1;
}
};
// 函式版本
template < class char_t, class size_t >
size_t StringFind( const char_t* lpszSource, const char_t* lpszSearch )
{
// maybe the processing of the following line can delay to the caller
if ( ( NULL == lpszSource ) || ( NULL == lpszSearch ) ) return -1;
const char_t& cSource = *lpszSource;
const char_t& cSearch = *lpszSearch;
for ( ; ; )
{
if ( 0 == *lpszSearch )
{
return ( lpszSource - ( lpszSearch - &cSearch ) - &cSource );
}
if ( 0 == *lpszSource ) return -1;
if ( *lpszSource != *lpszSearch )
{
lpszSource -= lpszSearch - &cSearch - 1;
lpszSearch = &cSearch;
continue;
}
++ lpszSource;
++ lpszSearch;
}
return -1;
}
2、字串比較,同strcmp的功能,以下為仿函式版本。
struct StringCmp
{
int operator()( const char* lpszStr1, const char* lpszStr2 )
{
if ( NULL == lpszStr1 )
{
if ( NULL == lpszStr2 ) return 0;
return -1;
}
if ( NULL == lpszStr2 ) return 1;
for ( ; ( 0 != ( ( *lpszStr1 ) & ( *lpszStr2 ) ) ); ++ lpszStr1, ++ lpszStr2 )
{
if ( *lpszStr1 < *lpszStr2 ) return -1;
if ( *lpszStr1 > *lpszStr2 ) return 1;
}
if ( 0 != *lpszStr2 ) return -1;
if ( 0 != *lpszStr1 ) return 1;
return 0;
}
};
3、快速排序演算法(做了優化處理的),當元素總數不超過給定的閾值(threshold)時,採用插入排序,如果這個
做筆試題的話,個人認為難度偏大,因為其中程式部分可能技巧性比較強,需要很細心。俺也是花了很長時間上機
除錯,我認為可能冒泡、插入排序更適合筆試。做完此題後,我還參讀了Plauger's stl(VC7)中::std::sort
的原始碼,不同的是,其閾值預設為32,並且在超過閾值時也作了優化,小於或等於則都用了插入排序,呵呵。。。
struct NullType;
template < class T, class Iterator = T*, class Pr = NullType, class Diff = __int64, Diff threshold = 9 >
struct Sorter;
template < class T, class Iterator, class Diff, Diff threshold >
struct Sorter< T, Iterator, NullType, Diff, threshold >// sort by operator <
{
typedef T type;
typedef T& reference;
typedef const T& const_reference;
typedef T* pointer;
typedef Iterator iterator;
void operator()( iterator begin, iterator end )// sort in field [begin, end)
{
QuickSort( begin, end );
}
void InsertionSort( iterator begin, iterator end )// sort in field [begin, end)
{
type swap_temp;
for ( iterator i = begin + 1; i < end; ++ i )
{
for ( iterator j = i; ( j > begin ) && ( *j < *( j - 1 ) ); -- j )
{
swap_temp = *j;
j[0] = j[-1];
j[-1] = swap_temp;
}
}
}
void QuickSort( iterator begin, iterator end )// sort in field [begin, end)
{
Diff diff = end - begin;
// optimize speed with InsertionSort if the number of elements is not more than threshold
if ( threshold >= diff )
{
InsertionSort( begin, end );
return;
}
// find pivot
iterator pivot = begin + ( diff >> 1 );
// swap pivot with the element before end
type swap_temp;
swap_temp = end[-1];
end[-1] = *pivot;
pivot[0] = swap_temp;
// partition
iterator right_first = Partition( begin, end - 2, end[-1] );
// restore pivot to primary position
if ( right_first < ( end - 1 ) )
{
swap_temp = right_first[0];
right_first[0] = end[-1];
end[-1] = swap_temp;
}
if ( ( right_first - begin ) > 2 )
QuickSort( begin, right_first );// sort in field [begin, right_first)
if ( ( end - right_first ) > 3 )
QuickSort( right_first + 1, end );// sort in field [right_first + 1, end )
}
private:
iterator Partition( iterator first, iterator last, const_reference pivot )
{
iterator last_temp = last;
type swap_temp;
for ( ; first < last; )
{
if ( *last < pivot )
{
if ( *first < pivot )
{
++ first;
continue;
}
swap_temp = *first;
first[0] = *last;
last[0] = swap_temp;
++ first;
-- last;
}
else
{
if ( *first < pivot )
{
++ first;
-- last;
continue;
}
-- last;
continue;
}
}
// calculate the first position of right part
for ( ; ( !( pivot < *last ) )&& ( last <= last_temp ); ++ last ) {}
return last;
}
};
template < class T, class Iterator, class Pr, class Diff, Diff threshold >
struct Sorter< T, Iterator, Pr, Diff, threshold >// sort by Pr
{
// Just change it1 < it 2 to Pr( it1, it2 )
};
4、判斷字串是否為迴環串,類似於"abcdcba"(?),這個可能挺簡單,但是好像很多大公司出的
筆試題似乎都很簡單,不知其考察點在於什麼。。。
template < class Character = char >
struct IsCircleString
{
typedef Character char_t;
bool operator()( const char_t* pStr )
{
const char_t* pLast = pStr;
for ( ; 0 != *pLast; ++ pLast ) {}
if ( ( ( pLast - pStr ) & 0x1 ) == 0 ) return false;//偶數即返回false
-- pLast;
for ( ; pStr < pLast; ++ pStr, -- pLast )
{
if ( *pStr != *pLast ) return false;
}
return true;
}
};
我沒有記錯的話是一道MSN的筆試題,網上無意中看到的,拿來做了一下。題目是這樣的,給定一個字串,一個這個字串的子串,將第一個字串反轉,但保留子串的順序不變。例如:
輸入: 第一個字串: "This is zhuxinquan's Chinese site: http://www.zhuxinquan.com/cn"
子串: "zhuxinquan"
輸出: "nc/moc.zhuxinquan.www//:ptth :etis esenihC s'zhuxinquan si sihT"
一般的方法是先掃描一邊第一個字串,然後用stack把它反轉,同時記錄下子串出現的位置。然後再掃描一遍把記錄下來的子串再用stack反轉。我用的方法是用一遍掃描陣列的方法。掃描中如果發現子串,就將子串倒過來壓入堆疊。
最後再將堆疊裡的字元彈出,這樣子串又恢復了原來的順序。原始碼如下:
#include
#include
#include
using namespace std;
//reverse the string 's1' except the substring 'token'.
const char* reverse(const char* s1, const char* token)
{
assert(s1 && token);
stack stack1;
const char* ptoken = token, *head = s1, *rear = s1;
while (*head != '\0')
{
while(*head!= '\0' && *ptoken == *head)
{
ptoken++;
head++;
}
if(*ptoken == '\0')//contain the token
{
const char* p;
for(p=head-1;p>=rear;p--)
stack1.push(*p);
ptoken = token;
rear = head;
}
else
{
stack1.push(*rear);
head=++rear;
ptoken = token;
}
}
char * return_v = new char[strlen(s1)+1];
int i=0;
while(!stack1.empty())
{
return_v[i++] = stack1.top();
stack1.pop();
}
return_v[i]='\0';
return return_v;
}
int main(int argc, char* argv[])
{
cout<
雅虎筆試題(字串操作) 給定字串A和B,輸出A和B中的共有最大子串。
比如A="aocdfe" B="pmcdfa" 則輸出"cdf"
#include
#include
#include
char *commanstring(char shortstring[], char longstring[])
{
int i, j;
char *substring=malloc(256);
if(strstr(longstring, shortstring)!=NULL) //如果……,那麼返回shortstring
return shortstring;
for(i=strlen(shortstring)-1;i>0; i--) //否則,開始迴圈計算
{
for(j=0; j<=strlen(shortstring)-i; j++){
memcpy(substring, &shortstring[j], i);
substring[i]='\0';
if(strstr(longstring, substring)!=NULL)
return substring;
}
}
return NULL;
}
main()
{
char *str1=malloc(256);
char *str2=malloc(256);
char *comman=NULL;
gets(str1);
gets(str2);
if(strlen(str1)>strlen(str2)) //將短的字串放前面
comman=commanstring(str2, str1);
else
comman=commanstring(str1, str2);
printf("the longest comman string is: %s\n", comman);
}
早一點的ms筆試:字串倒序輸出
#include
#include
using namespace std;
void convert(char *str,int len)
{
assert(str!=NULL);
char *p1=str;
char *p2=str+len-1;
while(p2>p1){
*p2^=*p1;
*p1^=*p2;
*p2^=*p1;
p1++;
--p2;
}
}
int main(){
char str[]="The quick brown fox jumps over a lazy dog";
cout << str<convert(str,strlen(str));
cout << str<char *p=str;
char *q=p;
while(*p!='\0'){
++p;
if(*p==' ' || *p=='\0'){
convert(q,p-q);
cout << str< q=p+1;
}
}
cout << str<
system("pause");
}
程式設計師面試題精選(07)-翻轉句子中單詞的順序
題目:輸入一個英文句子,翻轉句子中單詞的順序,但單詞內字元的順序不變。句子中單詞以空格符隔開。為簡單起見,標點符號和普通字母一樣處理。
例如輸入“I am a student.”,則輸出“student. a am I”。
分析:由於編寫字串相關程式碼能夠反映程式設計師的程式設計能力和程式設計習慣,與字串相關的問題一直是程式設計師筆試、面試題的熱門題目。本題也曾多次受到包括微軟在內的大量公司的青睞。
由於本題需要翻轉句子,我們先顛倒句子中的所有字元。這時,不但翻轉了句子中單詞的順序,而且單詞內字元也被翻轉了。我們再顛倒每個單詞內的字元。由於單詞內的字元被翻轉兩次,因此順序仍然和輸入時的順序保持一致。
還是以上面的輸入為例子。翻轉“I am a student.”中所有字元得到“.tneduts a ma I”,再翻轉每個單詞中字元的順序得到“students. a am I”,正是符合要求的輸出。
參考程式碼:
///////////////////////////////////////////////////////////////////////
// Reverse a string between two pointers
// Input: pBegin - the begin pointer in a string
// pEnd - the end pointer in a string
///////////////////////////////////////////////////////////////////////
void Reverse(char *pBegin, char *pEnd)
{
if(pBegin == NULL || pEnd == NULL)
return;
while(pBegin < pEnd)
{
char temp = *pBegin;
*pBegin = *pEnd;
*pEnd = temp;
pBegin ++, pEnd --;
}
}
///////////////////////////////////////////////////////////////////////
// Reverse the word order in a sentence, but maintain the character
// order inside a word
// Input: pData - the sentence to be reversed
///////////////////////////////////////////////////////////////////////
char* ReverseSentence(char *pData)
{
if(pData == NULL)
return NULL;
char *pBegin = pData;
char *pEnd = pData;
while(*pEnd != '\0')
pEnd ++;
pEnd--;
// Reverse the whole sentence
Reverse(pBegin, pEnd);
// Reverse every word in the sentence
pBegin = pEnd = pData;
while(*pBegin != '\0')
{
if(*pBegin == ' ')
{
pBegin ++;
pEnd ++;
continue;
}
// A word is between with pBegin and pEnd, reverse it
else if(*pEnd == ' ' || *pEnd == '\0')
{
Reverse(pBegin, --pEnd);
pBegin = ++pEnd;
}
else
{
pEnd ++;
}
}
return pData;
}
程式設計師面試題精選(13)-第一個只出現一次的字元
題目:在一個字串中找到第一個只出現一次的字元。如輸入abaccdeff,則輸出b。 分析:這道題是2006年google的一道筆試題。
看到這道題時,最直觀的想法是從頭開始掃描這個字串中的每個字元。當訪問到某字元時拿這個字元和後面的每個字元相比較,如果在後面沒有發現重複的字元,則該字元就是隻出現一次的字元。如果字串有n個字元,每個字元可能與後面的O(n)個字元相比較,因此這種思路時間複雜度是O(n2)。我們試著去找一個更快的方法。
由於題目與字元出現的次數相關,我們是不是可以統計每個字元在該字串中出現的次數?要達到這個目的,我們需要一個資料容器來存放每個字元的出現次數。在這個資料容器中可以根據字元來查詢它出現的次數,也就是說這個容器的作用是把一個字元對映成一個數字。在常用的資料容器中,雜湊表正是這個用途。
雜湊表是一種比較複雜的資料結構。由於比較複雜,STL中沒有實現雜湊表,因此需要我們自己實現一個。但由於本題的特殊性,我們只需要一個非常簡單的雜湊表就能滿足要求。由於字元(char)是一個長度為8的資料型別,因此總共有可能256 種可能。於是我們建立一個長度為256的陣列,每個字母根據其ASCII碼值作為陣列的下標對應陣列的對應項,而陣列中儲存的是每個字元對應的次數。這樣我們就建立了一個大小為256,以字元ASCII碼為鍵值的雜湊表。
我們第一遍掃描這個陣列時,每碰到一個字元,在雜湊表中找到對應的項並把出現的次數增加一次。這樣在進行第二次掃描時,就能直接從雜湊表中得到每個字元出現的次數了。
參考程式碼如下:
///////////////////////////////////////////////////////////////////////
// Find the first char which appears only once in a string
// Input: pString - the string
// Output: the first not repeating char if the string has, otherwise 0
///////////////////////////////////////////////////////////////////////
char FirstNotRepeatingChar(char* pString)
{
// invalid input
if(!pString)
return 0;
// get a hash table, and initialize it
const int tableSize = 256;
unsigned int hashTable[tableSize];
for(unsigned int i = 0; i < tableSize; ++ i)
hashTable = 0;
// get the how many times each char appears in the string
char* pHashKey = pString;
while(*(pHashKey) != '\0')
hashTable[*(pHashKey++)] ++;
// find the first char which appears only once in a string
pHashKey = pString;
while(*pHashKey != '\0')
{
if(hashTable[*pHashKey] == 1)
return *pHashKey;
pHashKey++;
}
// if the string is empty
// or every char in the string appears at least twice
return 0;
}
程式設計師面試題精選(17)-把字串轉換成整數
題目:輸入一個表示整數的字串,把該字串轉換成整數並輸出。例如輸入字串"345",則輸出整數345。 分析:這道題儘管不是很難,學過C/C++語言一般都能實現基本功能,但不同程式設計師就這道題寫出的程式碼有很大區別,可以說這道題能夠很好地反應出程式設計師的思維和程式設計習慣,因此已經被包括微軟在內的多家公司用作面試題。建議讀者在往下看之前自己先編寫程式碼,再比較自己寫的程式碼和下面的參考程式碼有哪些不同。
首先我們分析如何完成基本功能,即如何把表示整數的字串正確地轉換成整數。還是以"345"作為例子。當我們掃描到字串的第一個字元'3'時,我們不知道後面還有多少位,僅僅知道這是第一位,因此此時得到的數字是3。當掃描到第二個數字'4'時,此時我們已經知道前面已經一個3了,再在後面加上一個數字4,那前面的3相當於30,因此得到的數字是3*10+4=34。接著我們又掃描到字元'5',我們已經知道了'5'的前面已經有了34,由於後面要加上一個5,前面的34就相當於340了,因此得到的數字就是34*10+5=345。
分析到這裡,我們不能得出一個轉換的思路:每掃描到一個字元,我們把在之前得到的數字乘以10再加上當前字元表示的數字。這個思路用迴圈不難實現。
由於整數可能不僅僅之含有數字,還有可能以'+'或者'-'開頭,表示整數的正負。因此我們需要把這個字串的第一個字元做特殊處理。如果第一個字元是'+'號,則不需要做任何操作;如果第一個字元是'-'號,則表明這個整數是個負數,在最後的時候我們要把得到的數值變成負數。
接著我們試著處理非法輸入。由於輸入的是指標,在使用指標之前,我們要做的第一件是判斷這個指標是不是為空。如果試著去訪問空指標,將不可避免地導致程式崩潰。另外,輸入的字串中可能含有不是數字的字元。每當碰到這些非法的字元,我們就沒有必要再繼續轉換。最後一個需要考慮的問題是溢位問題。由於輸入的數字是以字串的形式輸入,因此有可能輸入一個很大的數字轉換之後會超過能夠表示的最大的整數而溢位。
現在已經分析的差不多了,開始考慮編寫程式碼。首先我們考慮如何宣告這個函式。由於是把字串轉換成整數,很自然我們想到:
int StrToInt(const char* str);
這樣宣告看起來沒有問題。但當輸入的字串是一個空指標或者含有非法的字元時,應該返回什麼值呢?0怎麼樣?那怎麼區分非法輸入和字串本身就是”0”這兩種情況呢?
接下來我們考慮另外一種思路。我們可以返回一個布林值來指示輸入是否有效,而把轉換後的整數放到引數列表中以引用或者指標的形式傳入。於是我們就可以宣告如下:
bool StrToInt(const char *str, int& num);
這種思路解決了前面的問題。但是這個函式的使用者使用這個函式的時候會覺得不是很方便,因為他不能直接把得到的整數賦值給其他整形變臉,顯得不夠直觀。
前面的第一種宣告就很直觀。如何在保證直觀的前提下當碰到非法輸入的時候通知使用者呢?一種解決方案就是定義一個全域性變數,每當碰到非法輸入的時候,就標記該全域性變數。使用者在呼叫這個函式之後,就可以檢驗該全域性變數來判斷轉換是不是成功。
下面我們寫出完整的實現程式碼。參考程式碼:
enum Status {kValid = 0, kInvalid};
int g_nStatus = kValid;
///////////////////////////////////////////////////////////////////////
// Convert a string into an integer
///////////////////////////////////////////////////////////////////////
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);
}
討論:在參考程式碼中,我選用的是第一種宣告方式。不過在面試時,我們可以選用任意一種宣告方式進行實現。但當面試官問我們選擇的理由時,我們要對兩者的優缺點進行評價。第一種宣告方式對使用者而言非常直觀,但使用了全域性變數,不夠優雅;而第二種思路是用返回值來表明輸入是否合法,在很多API中都用這種方法,但該方法宣告的函式使用起來不夠直觀。
最後值得一提的是,在C語言提供的庫函式中,函式atoi能夠把字串轉換整數。它的宣告是int atoi(const char *str)。該函式就是用一個全域性變數來標誌輸入是否合法的。
程式設計師面試題精選(20)-最長公共子串
題目:如果字串一的所有字元按其在字串中的順序出現在另外一個字串二中,則字串一稱之為字串二的子串。注意,並不要求子串(字串一)的字元必須連續出現在字串二中。請編寫一個函式,輸入兩個字串,求它們的最長公共子串,並列印出最長公共子串。 例如:輸入兩個字串BDCABA和ABCBDAB,字串BCBA和BDAB都是是它們的最長公共子串,則輸出它們的長度4,並列印任意一個子串。
分析:求最長公共子串(Longest Common Subsequence, LCS)是一道非常經典的動態規劃題,因此一些重視演算法的公司像MicroStrategy都把它當作面試題。
完整介紹動態規劃將需要很長的篇幅,因此我不打算在此全面討論動態規劃相關的概念,只集中對LCS直接相關內容作討論。如果對動態規劃不是很熟悉,請參考相關演算法書比如演算法討論。
先介紹LCS問題的性質:記Xm={x0, x1,…xm-1}和Yn={y0,y1,…,yn-1}為兩個字串,而Zk={z0,z1,…zk-1}是它們的LCS,則:
1. 如果xm-1=yn-1,那麼zk-1=xm-1=yn-1,並且Zk-1是Xm-1和Yn-1的LCS;
2. 如果xm-1≠yn-1,那麼當zk-1≠xm-1時Z是Xm-1和Y的LCS;
3. 如果xm-1≠yn-1,那麼當zk-1≠yn-1時Z是Yn-1和X的LCS;
下面簡單證明一下這些性質:
1. 如果zk-1≠xm-1,那麼我們可以把xm-1(yn-1)加到Z中得到Z’,這樣就得到X和Y的一個長度為k+1的公共子串Z’。這就與長度為k的Z是X和Y的LCS相矛盾了。因此一定有zk-1=xm-1=yn-1。
既然zk-1=xm-1=yn-1,那如果我們刪除zk-1(xm-1、yn-1)得到的Zk-1,Xm-1和Yn-1,顯然Zk-1是Xm-1和Yn-1的一個公共子串,現在我們證明Zk-1是Xm-1和Yn-1的LCS。用反證法不難證明。假設有Xm-1和Yn-1有一個長度超過k-1的公共子串W,那麼我們把加到W中得到W’,那W’就是X和Y的公共子串,並且長度超過k,這就和已知條件相矛盾了。
2. 還是用反證法證明。假設Z不是Xm-1和Y的LCS,則存在一個長度超過k的W是Xm-1和Y的LCS,那W肯定也X和Y的公共子串,而已知條件中X和Y的公共子串的最大長度為k。矛盾。
3. 證明同2。
有了上面的性質,我們可以得出如下的思路:求兩字串Xm={x0, x1,…xm-1}和Yn={y0,y1,…,yn-1}的LCS,如果xm-1=yn-1,那麼只需求得Xm-1和Yn-1的LCS,並在其後新增xm-1(yn-1)即可;如果xm-1≠yn-1,我們分別求得Xm-1和Y的LCS和Yn-1和X的LCS,並且這兩個LCS中較長的一個為X和Y的LCS。
如果我們記字串Xi和Yj的LCS的長度為c[i,j],我們可以遞迴地求c[i,j]:
/ 0 if i<0 or j<0
c[i,j]= c[i-1,j-1]+1 if i,j>=0 and xi=xj
\ max(c[i,j-1],c[i-1,j] if i,j>=0 and xi≠xj
上面的公式用遞迴函式不難求得。但從前面求Fibonacci第n項(本面試題系列第16題)的分析中我們知道直接遞迴會有很多重複計算,我們用從底向上迴圈求解的思路效率更高。
為了能夠採用迴圈求解的思路,我們用一個矩陣(參考程式碼中的LCS_length)儲存下來當前已經計算好了的c[i,j],當後面的計算需要這些資料時就可以直接從矩陣讀取。另外,求取c[i,j]可以從c[i-1,j-1] 、c[i,j-1]或者c[i-1,j]三個方向計算得到,相當於在矩陣LCS_length中是從c[i-1,j-1],c[i,j-1]或者c[i-1,j]的某一個各自移動到c[i,j],因此在矩陣中有三種不同的移動方向:向左、向上和向左上方,其中只有向左上方移動時才表明找到LCS中的一個字元。於是我們需要用另外一個矩陣(參考程式碼中的LCS_direction)儲存移動的方向。
參考程式碼如下:
#include "string.h"
// directions of LCS generation
enum decreaseDir {kInit = 0, kLeft, kUp, kLeftUp};
/////////////////////////////////////////////////////////////////////////////
// Get the length of two strings' LCSs, and print one of the LCSs
// Input: pStr1 - the first string
// pStr2 - the second string
// Output: the length of two strings' LCSs
/////////////////////////////////////////////////////////////////////////////
int LCS(char* pStr1, char* pStr2)
{
if(!pStr1 || !pStr2)
return 0;
size_t length1 = strlen(pStr1);
size_t length2 = strlen(pStr2);
if(!length1 || !length2)
return 0;
size_t i, j;
// initiate the length matrix
int **LCS_length;
LCS_length = (int**)(new int[length1]);
for(i = 0; i < length1; ++ i)
LCS_length = (int*)new int[length2];
for(i = 0; i < length1; ++ i)
for(j = 0; j < length2; ++ j)
LCS_length[j] = 0;
// initiate the direction matrix
int **LCS_direction;
LCS_direction = (int**)(new int[length1]);
for( i = 0; i < length1; ++ i)
LCS_direction = (int*)new int[length2];
for(i = 0; i < length1; ++ i)
for(j = 0; j < length2; ++ j)
LCS_direction[j] = kInit;
for(i = 0; i < length1; ++ i)
{
for(j = 0; j < length2; ++ j)
{
if(i == 0 || j == 0)
{
if(pStr1 == pStr2[j])
{
LCS_length[j] = 1;
LCS_direction[j] = kLeftUp;
}
else
LCS_length[j] = 0;
}
// a char of LCS is found,
// it comes from the left up entry in the direction matrix
else if(pStr1 == pStr2[j])
{
LCS_length[j] = LCS_length[i - 1][j - 1] + 1;
LCS_direction[j] = kLeftUp;
}
// it comes from the up entry in the direction matrix
else if(LCS_length[i - 1][j] > LCS_length[j - 1])
{
LCS_length[j] = LCS_length[i - 1][j];
LCS_direction[j] = kUp;
}
// it comes from the left entry in the direction matrix
else
{
LCS_length[j] = LCS_length[j - 1];
LCS_direction[j] = kLeft;
}
}
}
LCS_Print(LCS_direction, pStr1, pStr2, length1 - 1, length2 - 1);
return LCS_length[length1 - 1][length2 - 1];
}
void LCS_Print(int **LCS_direction,
char* pStr1, char* pStr2,
size_t row, size_t col)
{
if(pStr1 == NULL || pStr2 == NULL)
return;
size_t length1 = strlen(pStr1);
size_t length2 = strlen(pStr2);
if(length1 == 0 || length2 == 0 || !(row < length1 && col < length2))
return;
// kLeftUp implies a char in the LCS is found
if(LCS_direction[row][col] == kLeftUp)
{
if(row > 0 && col > 0)
LCS_Print(LCS_direction, pStr1, pStr2, row - 1, col - 1);
// print the char
printf("%c", pStr1[row]);
}
else if(LCS_direction[row][col] == kLeft)
{
// move to the left entry in the direction matrix
if(col > 0)
LCS_Print(LCS_direction, pStr1, pStr2, row, col - 1);
}
else if(LCS_direction[row][col] == kUp)
{
// move to the up entry in the direction matrix
if(row > 0)
LCS_Print(LCS_direction, pStr1, pStr2, row - 1, col);
}
}
擴充套件:如果題目改成求兩個字串的最長公共子字串,應該怎麼求?子字串的定義和子串的定義類似,但要求是連續分佈在其他字串中。比如輸入兩個字串BDCABA和ABCBDAB的最長公共字串有BD和AB,它們的長度都是2。
程式設計師面試題精選(21)-左旋轉字串
題目:定義字串的左旋轉操作:把字串前面的若干個字元移動到字串的尾部。如把字串abcdef左旋轉2位得到字串cdefab。請實現字串左旋轉的函式。要求時間對長度為n的字串操作的複雜度為O(n),輔助記憶體為O(1)。
分析:如果不考慮時間和空間複雜度的限制,最簡單的方法莫過於把這道題看成是把字串分成前後兩部分,通過旋轉操作把這兩個部分交換位置。於是我們可以新開闢一塊長度為n+1的輔助空間,把原字串後半部分拷貝到新空間的前半部分,在把原字串的前半部分拷貝到新空間的後半部分。不難看出,這種思路的時間複雜度是O(n),需要的輔助空間也是O(n)。
接下來的一種思路可能要稍微麻煩一點。我們假設把字串左旋轉m位。於是我們先把第0個字元儲存起來,把第m個字元放到第0個的位置,在把第2m個字元放到第m個的位置…依次類推,一直移動到最後一個可以移動字元,最後在把原來的第0個字元放到剛才移動的位置上。接著把第1個字元儲存起來,把第m+1個元素移動到第1個位置…重複前面處理第0個字元的步驟,直到處理完前面的m個字元。
該思路還是比較容易理解,但當字串的長度n不是m的整數倍的時候,寫程式會有些麻煩,感興趣的朋友可以自己試一下。由於下面還要介紹更好的方法,這種思路的程式碼我就不提供了。
我們還是把字串看成有兩段組成的,記位XY。左旋轉相當於要把字串XY變成YX。我們先在字串上定義一種翻轉的操作,就是翻轉字串中字元的先後順序。把X翻轉後記為XT。顯然有(XT)T=X。
我們首先對X和Y兩段分別進行翻轉操作,這樣就能得到XTYT。接著再對XTYT進行翻轉操作,得到(XTYT)T=(YT)T(XT)T=YX。正好是我們期待的結果。
分析到這裡我們再回到原來的題目。我們要做的僅僅是把字串分成兩段,第一段為前面m個字元,其餘的字元分到第二段。再定義一個翻轉字串的函式,按照前面的步驟翻轉三次就行了。時間複雜度和空間複雜度都合乎要求。
參考程式碼如下:
#include "string.h"
///////////////////////////////////////////////////////////////////////
// Move the first n chars in a string to its end
///////////////////////////////////////////////////////////////////////
char* LeftRotateString(char* pStr, unsigned int n)
{
if(pStr != NULL)
{
int nLength = static_cast<int>(strlen(pStr));
if(nLength > 0 || n == 0 || n > nLength)
{
char* pFirstStart = pStr;
char* pFirstEnd = pStr + n - 1; //pStr+nLength-1;
char* pSecondStart = pStr + n;// pStr+nLength;
char* pSecondEnd = pStr + nLength - 1;// pStr + n - 1
// reverse the first part of the string
ReverseString(pFirstStart, pFirstEnd);
// reverse the second part of the strint
ReverseString(pSecondStart, pSecondEnd);
// reverse the whole string
ReverseString(pFirstStart, pSecondEnd);
}
}
return pStr;
}
///////////////////////////////////////////////////////////////////////
// Reverse the string between pStart and pEnd
///////////////////////////////////////////////////////////////////////
void ReverseString(char* pStart, char* pEnd)
{
if(pStart == NULL || pEnd == NULL)
{
while(pStart <= pEnd)
{
char temp = *pStart;
*pStart = *pEnd;
*pEnd = temp;
pStart ++;
pEnd --;
}
}
}
程式設計師面試題精選100題(28)-字串的排列 [摺疊]
題目:輸入一個字串,列印出該字串中字元的所有排列。例如輸入字串abc,則輸出由字元a、b、c所能排列出來的所有字串abc、acb、bac、bca、cab和cba。
分析:這是一道很好的考查對遞迴理解的程式設計題,因此在過去一年中頻繁出現在各大公司的面試、筆試題中。
我們以三個字元abc為例來分析一下求字串排列的過程。首先我們固定第一個字元a,求後面兩個字元bc的排列。當兩個字元bc的排列求好之後,我們把第一個字元a和後面的b交換,得到bac,接著我們固定第一個字元b,求後面兩個字元ac的排列。現在是把c放到第一位置的時候了。記住前面我們已經把原先的第一個字元a和後面的b做了交換,為了保證這次c仍然是和原先處在第一位置的a交換,我們在拿c和第一個字元交換之前,先要把b和a交換回來。在交換b和a之後,再拿c和處在第一位置的a進行交換,得到cba。我們再次固定第一個字元c,求後面兩個字元b、a的排列。
既然我們已經知道怎麼求三個字元的排列,那麼固定第一個字元之後求後面兩個字元的排列,就是典型的遞迴思路了。
基於前面的分析,我們可以得到如下的參考程式碼:
void Permutation(char* pStr, char* pBegin);
/////////////////////////////////////////////////////////////////////////
// Get the permutation of a string,
// for example, input string abc, its permutation is
// abc acb bac bca cba cab
/////////////////////////////////////////////////////////////////////////
void Permutation(char* pStr)
{
Permutation(pStr, pStr);
}
/////////////////////////////////////////////////////////////////////////
// Print the permutation of a string,
// Input: pStr - input string
// pBegin - points to the begin char of string
// which we want to permutate in this recursion
/////////////////////////////////////////////////////////////////////////
void Permutation(char* pStr, char* pBegin)
{
if(!pStr || !pBegin)
return;
// if pBegin points to the end of string,
// this round of permutation is finished,
// print the permuted string
if(*pBegin == '\0')
{
printf("%s\n", pStr);
}
// otherwise, permute string
else
{
for(char* pCh = pBegin; *pCh != '\0'; ++ pCh)
{
// swap pCh and pBegin
char temp = *pCh;
*pCh = *pBegin;
*pBegin = temp;
Permutation(pStr, pBegin + 1);
// restore pCh and pBegin
temp = *pCh;
*pCh = *pBegin;
*pBegin = temp;
}
}
}
擴充套件1:如果不是求字元的所有排列,而是求字元的所有組合,應該怎麼辦呢?當輸入的字串中含有相同的字串時,相同的字元交換位置是不同的排列,但是同一個組合。舉個例子,如果輸入aaa,那麼它的排列是6個aaa,但對應的組合只有一個。
擴充套件2:輸入一個含有8個數字的陣列,判斷有沒有可能把這8個數字分別放到正方體的8個頂點上,使得正方體上三組相對的面上的4個頂點的和相等。
正在載入評論...
程式設計師面試題精選100題(29)-調整陣列順序使奇數位於偶數前面
[摺疊]
題目:輸入一個整數陣列,調整陣列中數字的順序,使得所有奇數位於陣列的前半部分,所有偶數位於陣列的後半部分。要求時間複雜度為O(n)。
分析:如果不考慮時間複雜度,最簡單的思路應該是從頭掃描這個陣列,每碰到一個偶數時,拿出這個數字,並把位於這個數字後面的所有數字往前挪動一位。挪完之後在陣列的末尾有一個空位,這時把該偶數放入這個空位。由於碰到一個偶數,需要移動O(n)個數字,因此總的時間複雜度是O(n2)。
要求的是把奇數放在陣列的前半部分,偶數放在陣列的後半部分,因此所有的奇數應該位於偶數的前面。也就是說我們在掃描這個陣列的時候,如果發現有偶數出現在奇數的前面,我們可以交換他們的順序,交換之後就符合要求了。
因此我們可以維護兩個指標,第一個指標初始化為陣列的第一個數字,它只向後移動;第二個指標初始化為陣列的最後一個數字,它只向前移動。在兩個指標相遇之前,第一個指標總是位於第二個指標的前面。如果第一個指標指向的數字是偶數而第二個指標指向的數字是奇數,我們就交換這兩個數字。
基於這個思路,我們可以寫出如下的程式碼:
void Reorder(int *pData, unsigned int length, bool (*func)(int));
bool isEven(int n);
/////////////////////////////////////////////////////////////////////////
// Devide an array of integers into two parts, odd in the first part,
// and even in the second part
// Input: pData - an array of integers
// length - the length of array
/////////////////////////////////////////////////////////////////////////
void ReorderOddEven(int *pData, unsigned int length)
{
if(pData == NULL || length == 0)
return;
Reorder(pData, length, isEven);
}
/////////////////////////////////////////////////////////////////////////
// Devide an array of integers into two parts, the intergers which
// satisfy func in the first part, otherwise in the second part
// Input: pData - an array of integers
// length - the length of array
// func - a function
/////////////////////////////////////////////////////////////////////////
void Reorder(int *pData, unsigned int length, bool (*func)(int))
{
if(pData == NULL || length == 0)
return;
int *pBegin = pData;
int *pEnd = pData + length - 1;
while(pBegin < pEnd)
{
// if *pBegin does not satisfy func, move forward
if(!func(*pBegin))
{
pBegin ++;
continue;
}
// if *pEnd does not satisfy func, move backward
if(func(*pEnd))
{
pEnd --;
continue;
}
// if *pBegin satisfy func while *pEnd does not,
// swap these integers
int temp = *pBegin;
*pBegin = *pEnd;
*pEnd = temp;
}
}
/////////////////////////////////////////////////////////////////////////
// Determine whether an integer is even or not
// Input: an integer
// otherwise return false
/////////////////////////////////////////////////////////////////////////
bool isEven(int n)
{
return (n & 1) == 0;
}
討論:
上面的程式碼有三點值得提出來和大家討論:
1.函式isEven判斷一個數字是不是偶數並沒有用%運算子而是用&。理由是通常情況下位運算子比%要快一些;
2.這道題有很多變種。這裡要求是把奇數放在偶數的前面,如果把要求改成:把負數放在非負數的前面等,思路都是都一樣的。
3.在函式Reorder中,用函式指標func指向的函式來判斷一個數字是不是符合給定的條件,而不是用在程式碼直接判斷(hard code)。這樣的好處是把調整順序的演算法和調整的標準分開了(即解耦,decouple)。當調整的標準改變時,Reorder的程式碼不需要修改,只需要提供一個新的確定調整標準的函式即可,提高了程式碼的可維護性。例如要求把負數放在非負數的前面,我們不需要修改Reorder的程式碼,只需新增一個函式來判斷整數是不是非負數。這樣的思路在很多庫中都有廣泛的應用,比如在STL的很多演算法函式中都有一個仿函式(functor)的引數(當然仿函式不是函式指標,但其思想是一樣的)。如果在面試中能夠想到這一層,無疑能給面試官留下很好的印象。
程式設計師面試題精選(49):最長遞增子序列
題目描述:設L=1,a2,…,an>是n個不同的實數的序列,L的遞增子序列是這樣一個子序列Lin=K1,ak2,…,akm>,其中k12m且aK1k2km。求最大的m值
程式碼實現如下:
#include "stdio.h"
#include
template < class T >
int GetLISLen(T * arr, int n)
{
if (n return 0 ;
int iCurrMaxLen = 0 ;
int left, right, mid;
int * last = new int [n]();
last[ 0 ] = arr[ 0 ];
for ( int i = 1 ; i {
if (arr[i] >= last[iCurrMaxLen])
last[ ++ iCurrMaxLen] = arr[i];
else if (arr[i] last[ 0 ] = arr[i];
else
{
left = 0 ;
right = iCurrMaxLen;
while (left != right - 1 )
{
mid = (left + right) / 2 ;
(last[mid] <= arr[i]) ? (left = mid) : (right = mid);
}
last[right] = arr[i];
} // if
} // for
for ( i = 0 ; i {
printf( " %d " , last[i]);
if (i != iCurrMaxLen)
printf( " \x20 " );
else
printf( " \n " );
}
if (last)
{
delete [] last;
last = 0 ;
}
return iCurrMaxLen + 1 ;
}
int main()
{
int arr[11]={1,2,3,1,2,4,3,7,9,8,10};
GetLISLen(arr,11);
getchar();
return 0;
}
上述求解的最長遞增序列不要求連續,下面給出求最長連續遞增序列
template < class T >
int FindLongestConIncSubseq( const T * arr, int n, int * pos)
{
int start = 0 , end = 1 ;
int iMaxLen = 1 , iCurrLen = 1 ;
for (end = 1 ; end {
if (arr[end] >= arr[end - 1 ])
{
iCurrLen ++ ;
}
else
{
if (iCurrLen > iMaxLen)
{
iMaxLen = iCurrLen;
start = end - iMaxLen;
}
iCurrLen = 1 ;
}
} // for
if (iCurrLen > iMaxLen)
{
iMaxLen = iCurrLen;
start = end - iMaxLen;
}
* pos = start;
return iMaxLen;
}
程式設計師面試題精選(50):字串原地壓縮
題目描述:“eeeeeaaaff" 壓縮為 "e5a3f2"
程式碼實現:
#include "stdio.h"
#include "stdlib.h"
void CompressStrInPlace(char* str)
{
char *dest=str;
if ((0 == str) || ('\0' == *str))
{
return ;
}
char* p = str + 1;
char temp[11];
int rep = 0;
char* sNum = 0;
while (*p != '\0')
{
if (*str != *p)
{
*(++str) = *p++;
}
else
{
while (*p == *str)
{
++rep;
++p;
}
if (rep < 10)
{
*(++str) = rep + '0'; // 30h
}
else
{
char* sNum = itoa(rep, temp, 10);
while ((*(++str) = *sNum++) != '\0')
/**//*null*/;
--str;
}
rep = 0;
}//if-else
}//while
*(++str) = '\0';
str=dest;
}
int main()
{
char str[100]="ssssssssssssssssssssssssssssllllllllluyutuuuuuuuu";
CompressStrInPlace(str);
printf("%s",str);
getchar();
return 0;
}
不過有個問題,就是隻能對不含數字的字串進行上述壓縮
程式員面試題精選(51):按單詞翻轉句子
void ReverseString(char *s, int start, int end)
2 {
3 while (start 4 {
5 if (s[start] != s[end])
6 {
7 s[start] ^= s[end];
8 s[end] ^= s[start];
9 s[start] ^= s[end];
10 }
11
12 start++;
13 end--;
14 }
15 }
16
17 void ReverseByWords(char *s, int len, char seperator)
18 {
19 int start = 0, end = 0;
20
21 ReverseString(s, start, len - 1);
22
23 while (end 24 {
25 if (s[end] != seperator)
26 {
27 start = end;
28
29 while (end 30 end++;
31 end--;
32
33 ReverseString(s, start, end);
34 }
35
36 end++;
37
38 }//while
39 }
程式設計師面試題精選(52):字串匹配實現(回溯與不回溯演算法)
回溯:
const char * MyStrStr( const char * text, const char * pattern)
2 {
3 int i = 0 , j = 0 ;
4
5 while (pattern[i] && text[j])
6 {
7 if (pattern[i] == text[j])
8 {
9 ++ i;
10 ++ j;
11 }
12 else
13 {
14 i = 0 ;
15 j = j - i + 1 ;
16 }
17 } // while
18
19 if ( ! pattern[i])
20 {
21 return text + j - i;
22 }
23 else
24 {
25 return 0 ;
26 }
27 }
不回溯:
#include < cstring >
2 int BM( const char * text, const char * pattern)
3 {
4 int arrShiftTable[ 256 ];
5 int tLen = strlen(text);
6 int pLen = strlen(pattern);
7 int currPos, tReadPos, pReadPos;
8
9 // exclude NUL
10 for ( int i = 1 ; i < sizeof arrShiftTable / sizeof ( int ); i ++ )
11 {
12 arrShiftTable[i] = pLen;
13 }
14
15 // exclude the last char in pattern
16 for ( int i = 0 ; i < pLen - 1 ; i ++ )
17 {
18 arrShiftTable[pattern[i]] = pLen - 1 - i;
19 }
20
21 for (currPos = pLen - 1 ; currPos < tLen; /**/ /* null */ )
22 {
23 for (pReadPos = pLen - 1 , tReadPos = currPos;
24 pReadPos >= 0 && pattern[pReadPos] == text[tReadPos];
25 pReadPos -- , tReadPos -- )
26 /**/ /* null */ ;
27
28 if (pReadPos < 0 )
29 {
30 return tReadPos + 1 ;
31 }
32 else
33 {
34 currPos += arrShiftTable[text[currPos]];
35 }
36
37 } // outer for
38
39 return - 1 ;
40 }
已知strcpy函式的原型是:
char * strcpy(char * strDest,const char * strSrc);
1.不呼叫庫函式,實現strcpy函式。
2.解釋為什麼要返回char *。
解說:
1.strcpy的實現程式碼
char * strcpy(char * strDest,const char * strSrc)
{
if ((strDest==NULL)||(strSrc==NULL)) //[1]
throw "Invalid argument(s)"; //[2]
char * strDestCopy=strDest; //[3]
while ((*strDest++=*strSrc++)!='\0'); //[4]
return strDestCopy;
}
錯誤的做法:
[1]
(A)不檢查指標的有效性,說明答題者不注重程式碼的健壯性。
(B)檢查指標的有效性時使用((!strDest)||(!strSrc))或(!(strDest&&strSrc)),說明答題者對C語言中型別的隱式轉換沒有深刻認識。在本例中char *轉換為bool即是型別隱式轉換,這種功能雖然靈活,但更多的是導致出錯概率增大和維護成本升高。所以C++專門增加了bool、true、false三個關鍵字以提供更安全的條件表示式。
(C)檢查指標的有效性時使用((strDest==0)||(strSrc==0)),說明答題者不知道使用常量的好處。直接使用字面常量(如本例中的0)會減少程式的可維護性。0雖然簡單,但程式中可能出現很多處對指標的檢查,萬一出現筆誤,編譯器不能發現,生成的程式內含邏輯錯誤,很難排除。而使用NULL代替0,如果出現拼寫錯誤,編譯器就會檢查出來。
[2]
(A)return new string("Invalid argument(s)");,說明答題者根本不知道返回值的用途,並且他對記憶體洩漏也沒有警惕心。從函式中返回函式體內分配的記憶體是十分危險的做法,他把釋放記憶體的義務拋給不知情的呼叫者,絕大多數情況下,呼叫者不會釋放記憶體,這導致記憶體洩漏。
(B)return 0;,說明答題者沒有掌握異常機制。呼叫者有可能忘記檢查返回值,呼叫者還可能無法檢查返回值(見後面的鏈式表示式)。妄想讓返回值肩負返回正確值和異常值的雙重功能,其結果往往是兩種功能都失效。應該以丟擲異常來代替返回值,這樣可以減輕呼叫者的負擔、使錯誤不會被忽略、增強程式的可維護性。
[3]
(A)忘記儲存原始的strDest值,說明答題者邏輯思維不嚴密。
[4]
(A)迴圈寫成while (*strDest++=*strSrc++);,同[1](B)。
(B)迴圈寫成while (*strSrc!='\0') *strDest++=*strSrc++;,說明答題者對邊界條件的檢查不力。迴圈體結束後,strDest字串的末尾沒有正確地加上'\0'。
2.返回strDest的原始值使函式能夠支援鏈式表示式,增加了函式的“附加值”。同樣功能的函式,如果能合理地提高的可用性,自然就更加理想。
鏈式表示式的形式如:
int iLength=strlen(strcpy(strA,strB));
又如:
char * strA=strcpy(new char[10],strB);
返回strSrc的原始值是錯誤的。其一,源字串肯定是已知的,返回它沒有意義。其二,不能支援形如第二例的表示式。其三,為了保護源字串,形參用const限定strSrc所指的內容,把const char *作為char *返回,型別不符,編譯報錯。
來自 “ ITPUB部落格 ” ,連結:http://blog.itpub.net/10697500/viewspace-545332/,如需轉載,請註明出處,否則將追究法律責任。