面試題:翻轉單詞順序VS左旋轉字符串
題目一:輸入一個英文句子,翻轉句子中單詞的順序,但單詞內字符的順序不變。爲簡單起見,標點符號和普通字母一樣處理。例如輸入字符串 "I am a student",則輸出 "student.a am I " 。
題目二:字符串的左旋轉操作是把字符串前面的若干個字符轉移到字符串的尾部。請定義一個函數實現字符串左旋轉操作的功能。比如輸入字符串 "abcdefg" 和數字2,該函數將返回左旋轉2位得到的結果 "cdefgab"。
這個題目流傳甚廣,很多公司都多次拿來作面試題,很多應聘者也多次在各種博客或者書籍上看到過通過兩次翻轉字符串的解法,於是很快就可以跟面試官解釋清楚解題思路:第一步翻轉句子中所有的字符。比如翻轉”I am a student.”中所有的字符得到”.tneduts a ma I”,此時不但翻轉了句子中單詞的順序,連單詞內的字符順序也被翻轉了。第二步再翻轉每個單詞中字符的順序,就得到了”student a am I”。這正是符合題目要求的輸出。
這種思路的關鍵在於實現一個函數以翻轉字符串中的一段。下面的函數reverse可以完成這一功能:
void Reverse (char*pBegin, char *pEnd)
{
if ( pBegin == NULL || pEnd == NULL )
return;
while ( pBegin < pEnd )
{
char temp = *pBegin;
*pBegin = *pEnd;
*pEnd = temp;
pBegin++;
pEnd--;
}
}
_____________________________________________________________________
接着我們可以用這個函數先翻轉整個句子,再翻轉句子中的每個單詞。這種思路的參考代碼如下:
char*ReverseSentence(char *pData)
{
if (pData == NULL)
return NULL;
char *pBegin=pData;
char*pEnd=pData;
while(*pEnd!=' \0')
pEnd++;
pEnd--;
// 翻轉整個句子
Reverse (pBegin, pEnd);
//翻轉句子中的每個單詞
pBegin = pEnd = pData;
while(*pBegin!='\0')
{
if ( *pBegin == ' ' )
{
pBegin++;
pEnd++;
}
else if(*pEnd == ' ' | | *pEnd == '\0')
{
Reverse (pBegin, --pEnd);
pBegin = ++pEnd;
}
else
{
pEnd++;
}
return pData;
}
在英語句子中,單詞被空格符號分隔,因此我們可以通過掃描空格來確定每個單詞的起始和終止位置。在上述代碼的翻轉每個單詞階段,指針pBegin指向單詞的第一個字符,而pEnd指向單詞的最後一個字符。
有經驗的面試官看到一個應聘者幾乎不假思索就能想出一種比較巧妙的算法,就會覺得他之前可能見過這個題目。這個時候很多面試官都會再問一個問題,以考查他是不是真的理解了這種算法。面試官一個常見的考查辦法就是問一個類似的但更加難一點的問題。以這道題爲例,如果面試官覺得應聘者之前看過這個思路,那他可能再問第二個問題:
題目二:字符串的左旋轉操作是把字符串前面的若干個字符轉移到字符串的尾部。請定義一個函數實現字符串左旋轉操作的功能。比如輸入字符串 "abcdefg "和數字2,該函數將返回左旋轉2位得到的結果 " cdefgab "。
要找到字符串旋轉時每個字符移動的規律,不是一件輕易的事情。那我們是不是可以從解決第一個問題的思路中找到啓發?在第一個問題中,如果輸入的字符串之中只有兩個單詞,比如”hello world”,那麼翻轉這個句子中的單詞順序就得到了”world hello”。比較這兩個字符串,我們是不是可以把”world hello”看成是把原始字符串”hello world”的前面若干個字符轉移到後面?也就是說這兩個問題是非常相似的,我們同樣可以通過翻轉字符串的辦法來解決第二個問題。
以”abcdefg”爲例,我們可以把它分爲兩部分。由於想把它的前兩個字符移到後面,我們就把前兩個字符分到第一部分,把後面的所有字符都分到第二部分。我們先分別翻轉這兩部分,於是就得到”bagfedc”。接下來我們再翻轉整個字符串,得到的”cdefgab”剛好就是把原始字符串左旋轉2位的結果。
通過前面的分析,我們發現只需要調用3次前面的Reverse函數就可以實現字符串的左旋轉功能。參考代碼如下:
char* leftRotateString (char* pStr, 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-l;
char *pSecondStart = pStr+n;
char *pSecondEnd = pStr+nLength - 1 ;
// 翻轉字符串的前面n個字符
Reverse (pFirstStart, pFirstEnd);
// 翻轉字符串的後面部分
Reverse(pSecondStart, pSecondEnd);
// 翻轉整個字符串
Reverse (pFirstStart, pSecondEnd);
}
}
return p Str;
}
_____________________________________________________________________
想清楚思路之後再寫代碼是一件很容易的事情,但我們也不能掉以輕心。面試官在檢查與字符串相關的代碼時經常會發現兩種問題:一是輸入空指針NULL時程序會崩潰;二是內存訪問越界的問題,也就是試圖訪問不屬於字符串的內存。例如如果輸入的n小於0,指針pStr+n指向的內存就不屬於字符串。如果我們不排除這種情況,試圖訪問不屬於字符串的內存就會留下嚴重的內存越界的安全隱患。