1. 程式人生 > >程式設計藝術第十六~第二十章:全排列/跳臺階/奇偶調序,及一致性Hash演算法

程式設計藝術第十六~第二十章:全排列/跳臺階/奇偶調序,及一致性Hash演算法

第十六~第二十章:全排列,跳臺階,奇偶排序,第一個只出現一次等問題

作者:July、2011.10.16。
出處:http://blog.csdn.net/v_JULY_v

引言

    最近這幾天閒職在家,一忙著投簡歷,二為準備面試而蒐集整理各種面試題。故常常關注個人所建的Algorithms1-14群內朋友關於筆試,面試,宣講會,offer,薪資的討論以及在群內釋出的各種筆/面試題,常感言道:咱們這群人之前已經在學校受夠了學校的那種應試教育,如今出來找工作又得東奔西走去參加各種筆試/面試,著實亦不輕鬆。幻想,如果在企業與求職者之間有個中間面試服務平臺就更好了。

    ok,閒話少扯。在上一篇文章中,已經說過,“個人正在針對那100題一題一題的寫文章,多種思路,不斷優化,即成程式設計師程式設計藝術系列。”現本程式設計藝術系列繼續開始創作,你而後自會和我有同樣的感慨:各種面試題千變萬化,層出不窮,但基本型別,解決問題的思路基本一致。

    本文為程式設計師程式設計藝術第十六章~第二十章,包含以下5個問題:

  1. 全排列;
  2. 跳臺階;
  3. 奇偶排序;
  4. 第一個只出現一次的字元;
  5. 一致性雜湊演算法。

    同時,本文會在解答去年微軟面試100題的部分題目時,儘量結合今年最近各大IT公司最新的面試題來講解,兩相對比,彼此對照,相信你會更加贊同我上面的話。且本文也不奢望讀者能從中學到什麼高深技術之類的東西,只求讀者看此文看著舒服便可通順流暢以致一口氣讀完而無任何壓力。ok,有任何問題,歡迎不吝指正。謝謝。

第十六章、全排列問題

53.字串的排列。
題目:輸入一個字串,打印出該字串中字元的所有排列。
例如輸入字串abc,則輸出由字元a、b、c 所能排列出來的所有字串
abc、acb、bac、bca、cab 和cba。

    分析:此題最初整理於去年的微軟面試100題中第53題,第二次整理於微軟、Google等公司非常好的面試題及解答[第61-70題] 第67題。無獨有偶,這個問題今年又出現於今年的2011.10.09百度筆試題中。ok,接下來,咱們先好好分析這個問題。

  • 一、遞迴實現
    從集合中依次選出每一個元素,作為排列的第一個元素,然後對剩餘的元素進行全排列,如此遞迴處理,從而得到所有元素的全排列。以對字串abc進行全排列為例,我們可以這麼做:以abc為例
    固定a,求後面bc的排列:abc,acb,求好後,a和b交換,得到bac
    固定b,求後面ac的排列:bac,bca,求好後,c放到第一位置,得到cba
    固定c,求後面ba的排列:cba,cab。程式碼可如下編寫所示:
  1. template <typename T>  
  2. void CalcAllPermutation_R(T perm[], int first, int num)  
  3. {  
  4.     if (num <= 1) {  
  5.         return;  
  6.     }  
  7.     for (int i = first; i < first + num; ++i) {  
  8.         swap(perm[i], perm[first]);  
  9.         CalcAllPermutation_R(perm, first + 1, num - 1);  
  10.         swap(perm[i], perm[first]);  
  11.     }  
  12. }  
    或者如此編寫,亦可:
void Permutation(char* pStr, char* pBegin);

void Permutation(char* pStr)
{
      Permutation(pStr, pStr);
}

void Permutation(char* pStr, char* pBegin)
{
	if(!pStr || !pBegin)
		return;
	
	if(*pBegin == '\0')
	{
		printf("%s\n", pStr);
	}
	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;
		}
	}
}
  • 二、字典序排列
    把升序的排列(當然,也可以實現為降序)作為當前排列開始,然後依次計算當前排列的下一個字典序排列。
    對當前排列從後向前掃描,找到一對為升序的相鄰元素,記為i和j(i < j)。如果不存在這樣一對為升序的相鄰元素,則所有排列均已找到,演算法結束;否則,重新對當前排列從後向前掃描,找到第一個大於i的元素k,交換i和k,然後對從j開始到結束的子序列反轉,則此時得到的新排列就為下一個字典序排列。這種方式實現得到的所有排列是按字典序有序的,這也是C++ STL演算法next_permutation的思想。演算法實現如下:
  1. template <typename T>  
  2. void CalcAllPermutation(T perm[], int num)  
  3. {  
  4.     if (num < 1)  
  5.         return;  
  6.     while (true) {  
  7.         int i;  
  8.         for (i = num - 2; i >= 0; --i) {  
  9.             if (perm[i] < perm[i + 1])  
  10.                 break;  
  11.         }  
  12.         if (i < 0)  
  13.             break;  // 已經找到所有排列
  14.         int k;  
  15.         for (k = num - 1; k > i; --k) {  
  16.             if (perm[k] > perm[i])  
  17.                 break;  
  18.         }  
  19.         swap(perm[i], perm[k]);  
  20.         reverse(perm + i + 1, perm + num);  
  21.     }  
  22. }  
  擴充套件:如果不是求字元的所有排列,而是求字元的所有組合,應該怎麼辦呢?當輸入的字串中含有相同的字串時,相同的字元交換位置是不同的排列,但是同一個組合。舉個例子,如果輸入abc,它的組合有a、b、c、ab、ac、bc、abc。

第十七章、跳臺階問題

27.跳臺階問題
題目:一個臺階總共有n 級,如果一次可以跳1 級,也可以跳2 級。
求總共有多少總跳法,並分析演算法的時間複雜度。

    分析:在九月騰訊,創新工場,淘寶等公司最新面試十三題中第23題又出現了這個問題,題目描述如下:23、人人筆試1:一個人上臺階可以一次上1個,2個,或者3個,問這個人上n層的臺階,總共有幾種走法?咱們先撇開這個人人筆試的問題(其實差別就在於人人筆試題中多了一次可以跳三級的情況而已),先來看這個第27題。

    首先考慮最簡單的情況。如果只有1級臺階,那顯然只有一種跳法。如果有2級臺階,那就有兩種跳的方法了:一種是分兩次跳,每次跳1級;另外一種就是一次跳2級。

    現在我們再來討論一般情況。我們把n級臺階時的跳法看成是n的函式,記為f(n)。當n>2時,第一次跳的時候就有兩種不同的選擇:一是第一次只跳1級,此時跳法數目等於後面剩下的n-1級臺階的跳法數目,即為f(n-1);另外一種選擇是第一次跳2級,此時跳法數目等於後面剩下的n-2級臺階的跳法數目,即為f(n-2)。因此n級臺階時的不同跳法的總數f(n)=f(n-1)+(f-2)。

    我們把上面的分析用一個公式總結如下:
        /     1                              n=1
f(n)=      2                              n=2
        \  f(n-1) + f(n-2)            n>2

    原來上述問題就是我們平常所熟知的Fibonacci數列問題。可編寫程式碼,如下:

long long Fibonacci_Solution1(unsigned int n)
{
	int result[2] = {0, 1};
	if(n < 2)
		return result[n];
	
	return Fibonacci_Solution1(n - 1) + Fibonacci_Solution1(n - 2);
}

     那麼,如果是人人筆試那道題呢?一個人上臺階可以一次上1個,2個,或者3個,豈不是可以輕而易舉的寫下如下公式:

        /      1                                      n=1
f(n)=      2                                      n=2

              4                                      n=3       //111, 12, 21, 3
        \  f(n-1)+(f-2)+f(n-3)            n>3

    行文至此,你可能會認為問題已經解決了,但事實上沒有:

  1. 用遞迴方法計算的時間複雜度是以n的指數的方式遞增的,我們可以嘗試用遞推方法解決。具體如何操作,讀者自行思考。
  2. 有一種方法,能在O(logn)的時間複雜度內求解Fibonacci數列問題,你能想到麼?
  3. 同時,有朋友指出對於這個臺階問題只需求冪就可以了(求複數冪C++庫裡有),不用任何迴圈且複雜度為O(1),如下圖所示,是否真如此?:

第十八章、奇偶調序

54.調整陣列順序使奇數位於偶數前面。
題目:輸入一個整數陣列,調整陣列中數字的順序,使得所有奇數位於陣列的前半部分,
所有偶數位於陣列的後半部分。要求時間複雜度為O(n)。

分析:

  1. 你當然可以從頭掃描這個陣列,每碰到一個偶數時,拿出這個數字,並把位於這個數字後面的所有數字往前挪動一位。挪完之後在陣列的末尾有一個空位,這時把該偶數放入這個空位。由於碰到一個偶數,需要移動O(n)個數字,只是這種方法總的時間複雜度是O(n),不符合要求,pass
  2. 很簡單,維護兩個指標,一個指標指向陣列的第一個數字,向後移動;一個個指標指向最後一個數字,向前移動。如果第一個指標指向的數字是偶數而第二個指標指向的數字是奇數,我們就交換這兩個數字。
    思路有了,接下來,寫程式碼實現:
//思路,很簡答,倆指標,一首一尾
//如果第一個指標指向的數字是偶數而第二個指標指向的數字是奇數,
//我們就交換這兩個數字

// 2 1 3 4 6 5 7 
// 7 1 3 4 6 5 2
// 7 1 3 5 6 4 2

//如果限制空間複雜度為O(1),時間為O(N),且奇偶數之間相對順序不變,就相當於正負數間順序調整的那道題了。

//[email protected] zhedahht。
void Reorder(int *pData, unsigned int length, bool (*func)(int));
bool isEven(int n);
void ReorderOddEven(int *pData, unsigned int length)
{
	if(pData == NULL || length == 0)
		return;
	
	Reorder(pData, length, isEven);
}
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;
	}
}
bool isEven(int n)
{
	return (n & 1) == 0;
}
    細心的讀者想必注意到了上述程式註釋中所說的“如果限制空間複雜度為O(1),時間為O(N)就相當於正負數間順序調整的那道題了”,沒錯,它與個人之前整理的一文中的第5題極其類似:5、一個未排序整數陣列,有正負數,重新排列使負數排在正數前面,並且要求不改變原來的正負數之間相對順序 比如: input: 1,7,-5,9,-12,15 ans: -5,-12,1,7,9,15 要求時間複雜度O(N),空間O(1) 。此題一直沒看到令我滿意的答案,一般達不到題目所要求的:時間複雜度O(N),空間O(1),且保證原來正負數之間的相對位置不變    如果你想到了絕妙的解決辦法,不妨在本文評論下告知於我,或者來信指導([email protected]),謝謝。

第十九章、第一個只出現一次的字元

第17 題:題目:在一個字串中找到第一個只出現一次的字元。如輸入abaccdeff,則輸出b。    分析:這道題是2006 年google 的一道筆試題。它在今年又出現了,不過換了一種形式。即最近的搜狐筆試大題:陣列非常長,如何找到第一個只出現一次的數字,說明演算法複雜度。此問題已經在程式設計師程式設計藝術系列第二章中有所闡述,在此不再作過多講解。

程式碼,可編寫如下:

  1. #include <iostream>
  2. usingnamespace std;  
  3. //查詢第一個只出現一次的字元,第1個程式
  4. //[email protected] Sorehead && July
  5. //July、updated,2011.04.24.
  6. char find_first_unique_char(char *str)  
  7. {  
  8.     int data[256];  
  9.     char *p;  
  10.     if (str == NULL)  
  11.         return'\0';  
  12.     memset(data, 0, sizeof(data));    //陣列元素先全部初始化為0
  13.     p = str;  
  14.     while (*p != '\0')  
  15.         data[(unsigned char)*p++]++;  //遍歷字串,在相應位置++,(同時,下標強制轉換)
  16.     while (*str != '\0')  
  17.     {  
  18.         if (data[(unsigned char)*str] == 1)  //最後,輸出那個第一個只出現次數為1的字元
  19.             return *str;  
  20.         str++;  
  21.     }  
  22.     return'\0';  
  23. }  
  24. int main()  
  25. {  
  26.     char *str = "afaccde";  
  27.     cout << find_first_unique_char(str) << endl;  
  28.     return 0;  
  29. }  
  當然,程式碼也可以這麼寫(測試正確): 
  1. //查詢第一個只出現一次的字元,第2個程式
  2. //[email protected] yansha
  3. //July、updated,2011.04.24.
  4. char FirstNotRepeatChar(char* pString)  
  5. {  
  6.     if(!pString)  
  7.         return'\0';  
  8.     constint tableSize = 256;  
  9.     int hashTable[tableSize] = {0}; //存入陣列,並初始化為0
  10.     char* pHashKey = pString;  
  11.     while(*(pHashKey) != '\0')  
  12.         hashTable[*(pHashKey++)]++;  
  13.     while(*pString != '\0')  
  14.     {  
  15.         if(hashTable[*pString] == 1)  
  16.             return *pString;  
  17.         pString++;  
  18.     }  
  19.     return'\0';  //沒有找到滿足條件的字元,退出
  20. 相關推薦

    程式設計藝術~第二排列/臺階/調一致性Hash演算法

    第十六~第二十章:全排列,跳臺階,奇偶排序,第一個只出現一次等問題作者:July、2011.10.16。出處:http://blog.csdn.net/v_JULY_v。引言    最近這幾天閒職在家,一忙著投簡歷,二為準備面試而蒐集整理各種面試題。故常常關注個人所建的Alg

    activiti RuntimeService設定獲和取流程變數與taskService的區別開始和完成任務時設定流程變數

    上一章我們講了taskService獲取流程變數的過程,這裡我們講講RuntimeService是怎麼設定和獲取的,其實過程跟taskService是差不多的。RuntimeService 與流程例項及執行物件相關,對於的表是:act_ru_executionTaskServ

    程式設計師程式設計藝術-----五 ~ 二-----排列臺階、第一個出現一次字元、一致性hash

    第十六~第二十章:全排列,跳臺階,奇偶排序,第一個只出現一次等問題作者:July、2011.10.16。出處:http://blog.csdn.net/v_JULY_v。引言    最近這幾天閒職在家,一忙著投簡歷,二為準備面試而蒐集整理各種面試題。故常常關注個人所建的Algorithms1-14群內朋友關於

    程式設計師程式設計藝術----------最長公共子序列(LCS)問題

      程式設計師程式設計藝術第十一章:最長公共子序列(LCS)問題0、前言    程式設計師程式設計藝術系列重新開始創作了(前十章,請參考程式設計師程式設計藝術第一~十章集錦與總結)。回顧之前的前十章,有些程式碼是值得商榷的,因當時的程式碼只顧闡述演算法的原理或思想,所以,很多的與程式碼規範相關的問題都未能做到

    程式設計師程式設計藝術-----一 ~ -----海量整數處理、蓄水池抽樣、迴文

           程式設計師程式設計藝術第十二~十五章:中籤概率,IP訪問次數,迴文等問題(初稿)作者:上善若水.qinyu,BigPotato,luuillu,well,July。程式設計藝術室出品。前言    本文的全部稿件是由我們程式設計藝術室的部分成員:上善若水.qinyu,BigPotato,luuil

    程式設計師程式設計藝術~三字串轉換成整數萬用字元字串匹配

    第三十~三十一章:字串轉換成整數,帶萬用字元的字串匹配前言    之前本一直想寫寫神經網路演算法和EM演算法,但寫這兩個演算法實在需要大段大段的時間,而平時上班,週末則跑去北大教室自習看書(順便以時間為序,說下過去半年看過的自覺還不錯的數學史方面的書:《數理統計學簡史》《微積

    資料庫視訊-七-第二

    【使用.NET訪問SQL Server 2008】 1.ADO.NET基礎知識 ADO.NET提供對Microsoft SQL Server等資料來源以及通過OLE DB和XML公開的資料來源的一致訪問。資料共享使用者應用程式可以使用ADO.NET來連線到這些資料來源,並檢索、操作和更新資料

    程式設計師程式設計藝術----------求解500萬以內的親和數(素數、完數)

    前奏    本章陸續開始,除了繼續保持原有的字串、陣列等面試題之外,會有意識的間斷性節選一些有關數字趣味小而巧的面試題目,重在突出思路的“巧”,和“妙”。本章親和數問題之關鍵字,“500萬”,“線性複雜度”。第一節、親和數問題題目描述:求500萬以內的所有親和數如果兩個數a和b,a的所有真因數之和等於b,b的

    周微職位Memcached,haproxy,varnish

    第十六周微職位:memcached varnish haproxy 1、為LNMP架構添加memcached支持,並完成對緩存效果的測試報告;一、Memcached的簡介: Memcached是一個自由開源的,高性能,分布式內存對象緩存系統。它是一種基於內存的key-value存儲,用來存儲

    程式設計師程式設計藝術----------尋找滿足和為定值的兩個或多個數

                        程式設計師程式設計藝術:第五章、尋找和為定值的兩個或多個數 前奏    希望此程式設計藝術系列能給各位帶來的是一種方法,一種創造力,一種舉一反三的能力。本章依然同第四章一樣,選取比較簡單的面試題,恭祝各位旅途愉快。同樣,有任何問題,歡迎不吝指正。謝謝。第一節、尋找和為定

    程式設計師程式設計藝術----------求連續子陣列的最大和

     程式設計師程式設計藝術:第七章、求連續子陣列的最大和 前奏希望更多的人能和我一樣,把本狂想曲系列中的任何一道面試題當做一道簡單的程式設計題或一個實質性的問題來看待,在閱讀本狂想曲系列的過程中,希望你能儘量暫時放下所有有關面試的一切包袱,潛心攻克每一道“程式設計題”,在解決程式設計題的過程中,好好享受程式設計

    程式設計師程式設計藝術----------現場編寫類似strstr/strcpy/strpbrk的函式

                   第四章、現場編寫類似strstr/strcpy/strpbrk的函式   作者:July。    說明: 如果在部落格中程式碼使用了\n,csdn blog系統將會自動回給我變成/n。據後續驗證,可能是原來舊blog版本的bug,新版已不存在此問題。至於,本文程式碼,日後統一修正

    程式設計師程式設計藝術-----續-----Top K演算法問題的實現

                         程式設計師程式設計藝術:第三章續、Top K演算法問題的實現前奏    在上一篇文章,程式設計師面試題狂想曲:第三章、尋找最小的k個數中,後來為了論證類似快速排序中partition的方法在最壞情況下,能在O(N)的時間複雜度內找到最小的k個數,而前前後後update

    程式設計師程式設計藝術----------閒話連結串列追趕問題

    前奏    有這樣一個問題:在一條左右水平放置的直線軌道上任選兩個點,放置兩個機器人,請用如下指令系統為機器人設計控制程式,使這兩個機器人能夠在直線軌道上相遇。(注意兩個機器人用你寫的同一個程式來控制)。    指令系統:只包含4條指令,向左、向右、條件判定、無條件跳轉。其中向左(右)指令每次能控制機器人向左

    程式設計師程式設計藝術----------尋找最小的k個數

                        程式設計師程式設計藝術:第三章、尋找最小的k個數作者:July。時間:二零一一年四月二十八日。致謝:litaoye, strugglever,yansha,luuillu,Sorehead,及狂想曲創作組。微博:http://weibo.com/julyweibo。出處

    程式設計師程式設計藝術----------從頭至尾漫談虛擬函式

    前奏    有關虛擬函式的問題層出不窮,有關虛擬函式的文章千篇一律,那為何還要寫這一篇有關虛擬函式的文章呢?看完本文後,相信能懂其意義之所在。同時,原狂想曲系列已經更名為程式設計師程式設計藝術系列,因為不再只專注於“面試”,而在“程式設計”之上了。ok,如果有不正之處,望不吝賜教。謝謝。第一節、一道簡單的虛擬

    VS2013/MFC程式設計入門之(對話方塊檔案對話方塊)

    上一講介紹的是訊息對話方塊,本節講解檔案對話方塊。        檔案對話方塊的分類 檔案對話方塊分為開啟檔案對話方塊和儲存檔案對話方塊,相信大家在Windows系統中經常見到這兩種檔案對話方塊。例如,很多編輯軟體像記事本等都有“開啟”選項,選擇“開啟”後會彈出一個對

    JS DOM 程式設計藝術(2版)讀書筆記 12 綜合示例

    /** * addLoadEvent */ function addLoadEvent(func) { var oldonload = window.onload; if(typeof oldonload != "function"){

    、模塊time模塊(時間)、random模塊(隨機數)

    urn 選項 blog 三種 python .com %x 方式 sleep 導入模塊:import 模塊 模塊實際就是以個.py文件 調用模塊下內容: 模塊.方法名 模塊分類:   內置模塊、第三方模塊、自定義模塊 模塊查找順序:   自定義模塊--第三方模塊--內

    把一個無符號整數對應的進位制的前八位和末八位交換中間的位也同理互換

    例如0xfabcde12,變換後為:12debcfa 用位運算實現,再控制輸出; 程式碼如下: #include <stdio.h> using namespace std; in