《王道機試指南》刷題總結

第一章(略)

第二章 暴力求解

  • 習題2.1 注意審題,給的是有關的概念,求的是無關的平方和;
  • 習題2.2 注意審題,強調總的購買數是100只,並不在意是否要儘可能花光給的錢;
  • 習題2.10 路徑打印:多叉樹的插入和遍歷
  • 習題2.11 思路分析:墜落的螞蟻

第三章 排序與查找

  • 例題3.2 成績排序 注意C++與C中定義結構體類型的區別;
  • 習題3.2 sort(arr,arr+n)的第二個參數時數組的結束地址,而不是最後一個元素的起始地址,即:n的值是元素的個數,不是最後一個元素的序號不是元素個數減1;
  • 習題3.7 查找://本題重要思想:爲了避免統計過的字母在後續的循環中重複被統計,把統計過的位置上的字母變爲’*’,從而避開統計;即查找過程中也是可以改動查找對象的,不要拘泥於“不能改動原對象”

第四章 字符串

  • 例題 4.2 讀取一行字符串:getline(cin,str);
  • 習題 4.2 string:swap 交換本字符串和參數的字符串
  • 習題 4.4 浮點數相加
  • 習題 4.5 對vector類型數據 進行排序:
    • vector str;
    • sort(str.begin(),str.end());
  • 例題 4.6 kmp算法;
  • 習題 4.6 cctype庫裏的 isalpha()判斷是否是字母、tolower()得到字母的小寫等函數

第五章 數據結構一

  • 例題 5.6 cctype庫裏的 isdigit()判斷字符是否是數字
  • 習題 5.1 getchar()函數的使用要注意,他能讀取任何字符,包括換行符和空格,注意和scanf、cin的區別;

第六章 數學問題

大整數的各類運算符的重載不能在其他定義的新的結構體中使用!

  • 例題6.2 進制轉化
    • 字符串的取模運算:等同於對最低位取模運算,與正常取模功能等同;
    • 字符串的除法功能:同紙上做除法,從高到低逐位除以除數,不能整除則保留餘數,餘數乘以10位和低位一起進行處理,模擬除法效果;這種做法存在前置多出0的情況,因此計算完成後,需要取首個非0之後的字符串。
  • 例題6.3 二進制轉十進制:十進制轉二進制的逆運算->字符串乘法+字符串加法
    • 十進制轉二進制是取模運算得到餘數(二進制的低位)再除以除數得到商,這是一輪,經過一輪,最後除到商爲0;那麼二進制轉十進制應該把上述運算逆轉過來:商(初始的商爲0,因爲十進制轉二進制時最後商除到了0)乘以原來的除數再加上餘數(二進制的高位)得到上一輪除法運算的商,循環相乘和加餘數,一直到把餘數加完即是原來的十進制數。
    • 進制轉換總結:十進制轉其他進制,用除法和取模;其他進制轉十進制,用乘法和加法。
  • 例題6.5 最大公約數:歐幾里得算法
  • 例題6.6 最小公倍數:兩個數的最小公倍數爲兩數的乘積除以最大公約數
  • 例題6.8 素數篩法:用於統計較大範圍內的素數
    • 命題一:若一個數不是素數,則必然存在一個小於它的素數爲其因數
    • 命題二:若已經獲得了小於一個數的所有素數,則只需確定這個數不能被這些素數所整除,那麼這個數即爲素數。
    • 解決方案:找到一個素數,然後把它在給定範圍內的所有倍數標記爲非素數,當遍歷到某個數時,若它未被任何小於它的素數因爲是其倍數而標記爲非素數時,這個數即可判定爲素數。
    • 小技巧:爲什麼素數篩法如果i是素數,標記非素數要從ii開始,而不從i2開始?這是因爲:ik(k<i)必定已經在求得k的某個素因數時被標記過,即ik同時也是k的素因數的倍數。所以直接從i*i開始標記,避免重複標記從而提高效率。
  • 例題6.9 質因數的個數:對於一個數n(1<n<10^9),求它的質因數個數
    • 小技巧:求素數時只需篩到MAXN=sqrt(10^9)+1,爲什麼?因爲對於數n,它的大於sqrt(n)的質因數顯然最多隻有一個。如果n確實存在大於sqrt(n)的質因數,那麼我們在不斷除以n的其他的質因數後,最後剩下的一定是這個唯一的大於sqrt(n)的質因數,因此不必要求到整個範圍內的素數。
  • 習題6.8 整除問題 通過質因子的冪指數關係來判定兩者能否整除以及能夠循環整除多少次;
  • 習題6.9 快速冪:積的模等於模的積+二分求冪+數學證明

第七章 貪心策略

  • 貪心策略的應用範圍:

    • 遇到求最大、最小、最多等最值問題時,應優先考慮是否能夠用貪心策略求解。
    • 若問題滿足最優子結構性質,即該問題具備無後效性,則全局的最優解可由求子問題的最優解得到。
    • 無法通過貪心策略求解的最優化問題,通常要用到動態規劃。
  • 習題7.1 此題使用貪心策略的合理性證明見程序註釋。

  • 例題7.4 區間貪心:從衆多區間選取最多的兩兩互不相交的區間

  • 例題7.5 難題!!!

  • 習題7.2 今天琢磨一下午加晚上了

  • 啓發:貪心問題 首先把問題規模縮小,找尋局部最優解,之後擴大數據規模。

第八章 遞歸與分冶

  • 全排列 next_permutation(str.begin(), str.end()),每循環使用一次循環該函數,就會返回下一個排列,直到指空。習題
  • 習題8.2 此題不使用全排列的話,辦法較複雜
  • 例題8.4 利用分治法
  • 習題8.3 獲取某個數的某個二進制位,不需要使用除K取餘法存入數組這麼麻煩,只需要用右移運算符加上按位與運算符即可獲得:n>>i&&1;由此即可獲得第i位(從高到低)的二進制位是否爲1;
  • 習題9.1 瑪雅人的密碼:
    • C++中find函數的運用可直接運用於字符串的匹配,不需要自己寫kmp函數;
    • 關於map<string,int> 的使用,可以簡化很多步驟

第九章 BFS和DFS(略)

第十章 數據結構二

一、二叉樹遍歷和二叉樹搜索

  • 如何判斷兩個序列同屬一個二叉樹:通過前序或後序遍歷結果是否相同判定,原因如下:
    a. 對於任意二叉樹而言,前序遍歷和中序遍歷唯一確定一課二叉樹;
    b. 而對於二叉排序樹而言,只要元素都相同,那麼由這些元素構成的任意二叉排序樹的中序遍歷都一定是一樣的,結果都是元素按大小有序的排列;
    c. 因此要區分相同元素不同序列構成的二叉排序樹是否是同一棵,中序遍歷並不能區分,因爲一定都是一樣的。但如果不屬於同一棵二叉排序樹,他們的前序或者後序遍歷結果一定不一樣,因爲如果一樣,那麼根據前序和中序唯一確定一棵二叉樹的理論,他們將屬於同一棵二叉樹,與前設矛盾。

二、優先隊列的使用和哈弗曼樹的構造

三、map的使用
1. map的最後一個元素的迭代器:map.rbegin();

第十一章 圖論

一、鄰接矩陣、鄰接表的概念

二、並查集:常用來判斷圖是否爲連通圖,或用來求圖的聯通分量。

  • 初始化(Initial):把每個結點的父親賦成自己
  • 查找(Find):用於判斷兩個元素是否屬於同一集合
  • 合併(Union):總是將高度較低的樹作爲高度較高的樹的子樹進行合併
  • 路徑壓縮:優化樹結構,爲後續查找工作節約了大量時間
    • 習題11.1:
      i. 建立二叉樹不一定用鏈表,也可以用數組,這樣訪問中間的元素可以直接訪問,不需要遍歷整棵二叉樹,可以解決一些特殊的問題。此題既需要運用到並查集的方法,但是又因爲一個結點對應兩個父節點,因此線性結構已不能滿足,必須要用二叉樹來表現這種結構,又因爲需要用到並查集,因此選擇用數組存儲的二叉樹來解決問題!!!
      ii. 以上用遍歷的方法解,事實上此題還是用並查集解法最簡單,此處稍微需要靈活一點,不要記錄誰是誰的父母,雖然題目給的這樣,但在我們處理數據時,我們應該倒過來,記錄誰是誰的兒子,這樣才能用並查集。題目給出的是一棵倒着的樹,我們要把他變成正着的樹。

三、最小生成樹(Minumum Spanning Tree,MST)

  • 定義:包含原圖中所有頂點和部分邊,,且這個子圖不存在迴路,所有生成樹中邊權和最小的即爲最小生成樹。
  • 兩類算法的區別:
    • 普里姆算法:是不斷從剩下的頂點之中選取一個加入MST的頂點集合,要求選取的頂點和當前集合的頂點之間的邊權值最小。
    • 克魯斯卡爾算法:不斷從剩下的邊的集合中選取邊權值最短的邊,要求這個邊的兩個頂點分屬不同的集合。

四、單源最短路徑——Dijkstra算法

  • 關於圖的構造:鄰接表:頂點+邊
  • 設置一個數爲無窮大:const int INF = INT_MAX;
  • fill(first,last,val):把目標單元賦成指定的值;
  • 使用此算法時,圖不能是負權圖。

五、拓撲排序

六、關鍵路徑

  • 事件的最早開始時間:所有先序活動中最晚完成的活動的時間;
  • 事件的最晚開始時間:所有後續活動中最早開始的時間減去本活動的時間;

補充:關於王道機試指南習題11.6 上海交通大學複試上機題解:最短路徑

第十二章 動態規劃

一、遞推求解

二、最大連續子序列和

  • 一維
  • 二維

三、最長遞增子序列(Longest Increasing Subsequence,LIS):求給定序列的所有遞增子序列中最長的那個子序列長度

  • 令dp[i]表示以A[i]作爲末尾的最長遞增子序列的長度;
    • 情形一:A[i]之前的元素都比A[i]大,即最長遞增子序列只有A[i]本身,即dp[i]=1;
    • 情形二:A[i]之前存在比A[i]小的元素A[j],那麼dp[i]=max(dp[j]+1|j<i&&A[j]<A[i])
      • 此處注意,dp[i]並不一定等於最近的位置j(滿足A[j]<A[i])的元素的dp值加1,而是等於前序所有滿足A[j]<A[i]的具有最大的dp值的dp[j]+1。
      • 舉例:1 2 4 7 8 3 9.對於dp[6]來說,離它最近滿足條件的是A[5]=3,dp[5]=3,如果按最近的處理,那麼dp[6]=4,序列爲1 2 3 9,這顯然是錯誤的。正確的dp[6]=dp[4]+1=6,序列爲1 2 4 7 8 9
  • 如此得到狀態轉移方程:dp[i]=max{1,dp[j]+1||j<i&&A[j]<A[i]},時間複雜度爲O(n^2)

四、最長公共子序列(Longest Common Subsequence,LCS)

五、揹包問題(狀態轉移方程詳解見機試指南P237,P240,P242)

  1. 0-1揹包:每種物品至多隻能選擇一件,在揹包中該物品的數量只有0和1兩種情況;
    • 內循環從大到小至j>=weight[i]
    • 爲什麼貪心策略不能解決0-1揹包問題?
    • 答:貪婪準則:價值vi,質量wi,每一項計算ri=vi/wi,即價值和質量之比,再按比值的降序來排序,從第一項開始裝揹包,然後是第二項,依次類推,儘可能的多放,直到裝滿揹包。這種策略並不能保證得到最優解。利用此策略試解n= 3 ,w=[40,35,35], v=[40,30,20], c=70 時的最優解。直覺上它可能是對的,得到的結果是選擇w[0]=40,剩餘30的空間容量不能裝下其他物品,總價值量爲40。很顯然,這是錯誤的,真正可以獲得的最大價值是30+20=50。事實上這一方法只是解決普通揹包問題的最優解,因爲按此方法在選擇物品i裝入揹包時,我們是可以選擇物品i的一部分的,不一定要全部裝入揹包。但是實際上0-1揹包問題的特點就是每個個體是不可拆分的,那麼此時再按貪心策略就不能證明結果是正確的,相反我們可以舉出反例證明它是錯誤的。這兩者的關係其實有點類似於概率論中古典概型和幾何概型問題的區分。對於一個方法是否正確,有時我們雖然不能直接通過理論證明它是正確的或是不正確,但如果是錯誤的,一定可以通過反例證明。理論不能證明正確的方法一般就不要使用,因爲有可能存在反例使它出現錯誤的結果。而對於動態規劃的解法,它的本質是枚舉,這個方法無疑是證明正確的。
    1. 0-1揹包變體求最小值,初始值一般設爲INF=INT_MAX/10;
    2. 完全揹包,揹包中每類物品數量無線,循環時從小到大開始,從j=weight[i]到m爲止。
    3. 多重揹包:即每種物品的數量既不是有限的,也不是隻能取一次,而是有限個k次。
      a. 解決辦法:轉化爲0-1揹包問題,每種物品均視爲k種重量和價值都相同的不同物品,如此時間複雜度爲O(m*∑24_(i=0)^n▒k_i ),但如此複雜度較高。
      b. 進一步優化,將一類物品拆分爲若干組,將每組物品視爲一件物品,價值和重量爲該組所有物品的價值重量總和。每組物品包含的原物品的個數分別爲20,21,……,2(c−1),k-2c+1,其中c是使得k-2c+1≥0的最大整數。這種類似於二進制的拆分,不僅將物品數量大大降低,同時通過由若干原物品得到新物品的不同組合,可以得到0到k之間的任意件物品的價值重量和,所以對所有這些新物品做0-1揹包,即可得到多重揹包的解。

六、其他問題

附:動態規劃:最長遞增子序列問題的時間複雜度由O(n^2)降低爲O(nlogn)的改良算法的自我理解

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章