導語
所有的編程練習都在牛客網OJ提交,鏈接: https://www.nowcoder.com/ta/coding-interviews
九章算法的 lintcode 也有這本書的題目。https://www.lintcode.com/ladder/6/
第二章 面試需要的基礎知識
【面試題3】二維數組中的查找
在一個二維數組中(每個一維數組的長度相同),每一行都按照從左到右遞增的順序排序,每一列都按照從上到下遞增的順序排序。請完成一個函數,輸入這樣的一個二維數組和一個整數,判斷數組中是否含有該整數。
題解:每次刪除一行,或者每次刪除一列。每次選取數組右上角的數字,如果右上角數字等於target,直接返回true。如果右上角數組小於target,說明這一行都比target小,那么刪除這一行。如果右上角數組大於taget,說明這一列都比target大,刪除這一列。

1 class Solution { 2 public: 3 bool Find(int target, vector<vector<int> > array) { 4 int n = array.size(); 5 if (n == 0) {return false;} 6 int m = array[0].size(); 7 if (m == 0) {return false;} 8 int right = m - 1, up = 0; 9 while (up < n && right >= 0) { 10 if (array[up][right] == target) { 11 return true; 12 } else if (array[up][right] < target) { 13 up++; 14 } else if (array[up][right] > target) { 15 right--; 16 } 17 } 18 return false; 19 } 20 };
【面試題4】替換空格
請實現一個函數,將一個字符串中的每個空格替換成“%20”。例如,當字符串為We Are Happy.則經過替換之后的字符串為We%20Are%20Happy。
題解:如果從前往后替換,那么每一段都要往后平移。這樣的復雜度是O(n^2)。我們可以優化下這個算法,首先遍歷字符串計算出空格的個數。然后新字符串的總長度就是 (原長度 + 2 * 空格數)。用兩根指針,一根指向原字符串的末尾,一根指向新字符串的末尾。如果原字符串遇到了空格,新字符串就應該用‘%20’來代替。其他情況一同往前平移。時間復雜度是O(n)

1 class Solution { 2 public: 3 void replaceSpace(char *str,int length) { 4 if (length == 0) {return;} 5 //==1.計算空格個數 6 int cnt = 0; 7 for (int i = 0; i < length; ++i) { 8 if (str[i] == ' ') { cnt++; } 9 } 10 int newLen = length + cnt * 2; 11 //==2.兩根指針從右往左 12 int p1 = length - 1, p2 = newLen - 1; 13 while (p1 >= 0 && p2 >= 0) { 14 if (str[p1] == ' ') { 15 str[p2--] = '0'; 16 str[p2--] = '2'; 17 str[p2--] = '%'; 18 } else { 19 str[p2--] = str[p1]; 20 } 21 p1--; 22 } 23 return ; 24 } 25 };
【面試題5】 從尾到頭打印鏈表
輸入一個鏈表,按鏈表值從尾到頭的順序返回一個ArrayList。
題解:我原本以為這個題目用了什么高端技巧,其實不是。我們從頭到尾打印鏈表的值,然后用個vector reverse一下就可以。也可以往數據結構棧上貼,先用個棧保存中間結果,然后在用先進后出的特性存入vector。

1 /** 2 * struct ListNode { 3 * int val; 4 * struct ListNode *next; 5 * ListNode(int x) : 6 * val(x), next(NULL) { 7 * } 8 * }; 9 */ 10 class Solution { 11 public: 12 vector<int> printListFromTailToHead(ListNode* head) { 13 vector<int> ans; 14 if (head == 0) {return ans;} 15 ListNode* tail = head; 16 for (; tail; tail = tail->next) { 17 ans.push_back(tail->val); 18 } 19 reverse(ans.begin(), ans.end()); 20 return ans; 21 } 22 };
【面試題6】重建二叉樹
輸入某二叉樹的前序遍歷和中序遍歷的結果,請重建出該二叉樹。假設輸入的前序遍歷和中序遍歷的結果中都不含重復的數字。例如輸入前序遍歷序列{1,2,4,7,3,5,6,8}和中序遍歷序列{4,7,2,1,5,3,8,6},則重建二叉樹並返回。
題解:直接遞歸重建。

1 /** 2 * Definition for binary tree 3 * struct TreeNode { 4 * int val; 5 * TreeNode *left; 6 * TreeNode *right; 7 * TreeNode(int x) : val(x), left(NULL), right(NULL) {} 8 * }; 9 */ 10 class Solution { 11 public: 12 TreeNode* reConstructBinaryTree(vector<int> pre,vector<int> vin) { 13 int r = pre[0]; 14 TreeNode* root = new TreeNode(r); 15 auto iter = find(vin.begin(), vin.end(), r); 16 if (iter == vin.end()) {cout << "illegal"; return root; } 17 int k = distance(vin.begin(), iter); 18 int leftSize = k, rightSize = pre.size() - 1 - k; 19 if (leftSize) { 20 vector<int> leftPre(pre.begin()+1, pre.begin()+k+1); 21 vector<int> leftVin(vin.begin(), vin.begin()+k); 22 root->left = reConstructBinaryTree(leftPre, leftVin); 23 } 24 if (rightSize) { 25 vector<int> rightPre(pre.begin()+k+1, pre.end()); 26 vector<int> rightVin(iter+1, vin.end()); 27 root->right = reConstructBinaryTree(rightPre, rightVin); 28 } 29 return root; 30 } 31 };
【面試題7】用兩個棧實現隊列
用兩個棧來實現一個隊列,完成隊列的Push和Pop操作。 隊列中的元素為int類型。
題解:不管是兩個棧實現一個隊列還是兩個隊列實現一個棧,看到這題就要知道一定可以做就行了。記憶,push元素的時候我們從第一個棧push,pop元素的時候我們從第二個棧pop。

1 class Solution 2 { 3 public: 4 void push(int node) { 5 stack1.push(node); 6 } 7 8 int pop() { 9 if (stack2.empty()) { 10 while (!stack1.empty()) { 11 int node = stack1.top(); 12 stack1.pop(); 13 stack2.push(node); 14 } 15 } 16 if (stack2.empty()) { 17 cout << "err"; 18 return -1; 19 } 20 int node = stack2.top(); 21 stack2.pop(); 22 return node; 23 } 24 25 private: 26 stack<int> stack1; 27 stack<int> stack2; 28 };
【面試題8】旋轉數組的最小數字
把一個數組最開始的若干個元素搬到數組的末尾,我們稱之為數組的旋轉。 輸入一個非減排序的數組的一個旋轉,輸出旋轉數組的最小元素。 例如數組{3,4,5,1,2}為{1,2,3,4,5}的一個旋轉,該數組的最小值為1。 NOTE:給出的所有元素都大於0,若數組大小為0,請返回0。
題解: 我知道是二分,但是沒法一次AC,有很多邊界條件沒有考慮。這個題目需要復習review。

1 class Solution { 2 public: 3 int minNumberInRotateArray(vector<int> rotateArray) { 4 const int n = rotateArray.size(); 5 if (n == 0) {return 0;} 6 //沒有旋轉的情況 7 if (rotateArray[0] < rotateArray[n-1]) { 8 return rotateArray[0]; 9 } 10 int mid, low = 0, high = n - 1; 11 int ans = INT_MAX; 12 while (rotateArray[low] >= rotateArray[high]) { 13 mid = (low + high) / 2; 14 if ( high - low == 1) { 15 mid = high; 16 break; 17 } 18 if (rotateArray[mid] == rotateArray[low] && rotateArray[mid] == rotateArray[high]) { 19 for (int k = low; k <= high; ++k) { 20 ans = min(ans, rotateArray[k]); 21 return ans; 22 } 23 } 24 if (rotateArray[mid] >= rotateArray[high]) { 25 low = mid ; 26 } else if (rotateArray[mid] <= rotateArray[low]) { 27 high = mid ; 28 } 29 } 30 return rotateArray[mid]; 31 } 32 };
【面試題9】斐波那契數列
大家都知道斐波那契數列,現在要求輸入一個整數n,請你輸出斐波那契數列的第n項(從0開始,第0項為0)。n<=39。
題解:正常寫就ok,可以用滾動數組優化。

1 class Solution { 2 public: 3 int Fibonacci(int n) { 4 if (n == 0) return 0; 5 vector<int> f(n + 1, 0); 6 f[0] = 0, f[1] = 1; 7 for (int i = 2; i < n + 1; ++i) { 8 f[i] = f[i-2] + f[i-1]; 9 } 10 return f[n]; 11 } 12 };

1 class Solution { 2 public: 3 int Fibonacci(int n) { 4 if (n == 0 || n == 1) return n; 5 int pre = 0, cur = 1; 6 int ans = 0; 7 for (int i = 2; i <= n; ++i) { 8 ans = pre + cur; 9 pre = cur; 10 cur = ans; 11 } 12 return ans; 13 } 14 };
【面試題9的拓展題】
【一】跳台階
一只青蛙一次可以跳上1級台階,也可以跳上2級。求該青蛙跳上一個n級的台階總共有多少種跳法(先后次序不同算不同的結果)。
題解:和面試題9一模一樣。

1 class Solution { 2 public: 3 int jumpFloor(int number) { 4 vector<int> f(number+1, 0); 5 f[0] = 0, f[1] = 1, f[2] = 2; 6 for (int k = 3; k < number+1; ++k) { 7 f[k] = f[k-1] + f[k-2]; 8 } 9 return f[number]; 10 } 11 };

1 class Solution { 2 public: 3 int jumpFloor(int number) { 4 if (number == 0 || number == 1) {return number;} 5 int ans = 0, pre = 1, cur = 1; 6 for (int k = 2; k <= number; ++k) { 7 ans = pre + cur; 8 pre = cur; 9 cur = ans; 10 } 11 return ans; 12 } 13 };
【二】變態跳台階
一只青蛙一次可以跳上1級台階,也可以跳上2級……它也可以跳上n級。求該青蛙跳上一個n級的台階總共有多少種跳法。
題解:f[n] = f[1] + f[2] + f[3] +...+f[n-1] + 1, f[1] = 1, f[2] = 2, f[3] = 4, f[4] = 8, 我們可以從數學歸納法得到 f[n] = 2 ^ (n-1)

1 class Solution { 2 public: 3 int jumpFloorII(int number) { 4 if (number == 0) return 0; 5 int ans = 1; 6 for (int k = 1; k < number; ++k) { 7 ans *= 2; 8 } 9 return ans; 10 } 11 };
【三】矩陣覆蓋
我們可以用2*1的小矩形橫着或者豎着去覆蓋更大的矩形。請問用n個2*1的小矩形無重疊地覆蓋一個2*n的大矩形,總共有多少種方法?
題解:f[n-1] --> f[n], f[n-1] = f[n], f[n] = f[n-2] + 2; f[0] = 0, f[1] = 1, f[2] = 2 (這個想法是錯的。我對題目的理解有點問題,題目的大矩形已經設定好了必須是2行N列,我搞成了隨意一個大矩形)《劍指offer》 P77
我們把 2 * 8 的覆蓋方法記為 f(8),當用第一個 1 * 2 的小矩形去覆蓋最左邊的時候有兩個選擇,橫着放或者豎着放。當豎着放的時候,右邊還剩下 2 * 7的區域,這種情況下的覆蓋方法數我們記為 f(7)。接下來考慮橫着放的情況,當橫着放的時候,下面也必須橫着放一個小矩形,所以剩下區域的覆蓋方法我們記為 f(6)。所以 f(8) = f(7) + f(6)。這個和斐波那契數列一樣的。

1 class Solution { 2 public: 3 int rectCover(int number) { 4 if (number == 0 || number == 1) {return number;} 5 int ans, pre = 1, cur = 1; 6 for (int i = 2; i <= number; ++i) { 7 ans = pre + cur; 8 pre = cur; 9 cur = ans; 10 } 11 return ans; 12 } 13 };
【面試題10】二進制中1的個數
輸入一個整數n,輸出該數二進制表示中1的個數。其中負數用補碼表示。
題解:書上提出了一種錯誤的做法是每次把n右移一位,然后和1相&,這種做法是錯的,當n是負數的時候,比如 0x80000000,把它右移一位的時候,並不是變成0x40000000,而是0xc00000000。因為移位前是個負數,移位后也依然是個負數,所以移位后最高位會設置成1。如果一直右移,那么最終這個數會變成0xFFFFFFFF。
為了避免右移n,我們可以左移flag。依次判斷n的最低位是不是1,次低位是不是1...(這個是常規做法)。

1 class Solution { 2 public: 3 int NumberOf1(int n) { 4 unsigned int flag = 1; 5 int cnt = 0; 6 while (flag) { 7 if (flag & n) { 8 cnt++; 9 } 10 flag <<= 1; 11 } 12 return cnt; 13 } 14 };
還有一種位運算的做法。我們先來分析把一個數減去1的情況。如果一個數不等於0, 那么它的二進制表示中起碼有一個1。假設這個數的最右邊一位是1,那么減去1的時候,最后一位變成0而其他所有位都保持不變。也就是最后移位相當於做了取反操作,由1變成了0。再來談談如果最右一位不是1而是0的話,假如這個數的二進制表示最右邊的第一個1位於第m位,那么減去1的時候,第m位從1變成了0,m位之后的所有的0都變成了1,而m位之前的所有位保持不變。eg, 1100,它的第二位是從最右邊數的第一個1,減去1之后,第二位變成0,的后面的兩位變成1,而前面的1保持不變,得到的結果是1011。綜合以上兩種情況,我們發現把一個數減去1,都是相當於把最右邊的1變成了0。如果它右邊還有0的話,所有的0都變成了1,而它左邊所有位都保持不變。接下來我們把這個整數和它減去1之后的數做與運算,相當於把最右邊的1變成了0。eg, n=1100, n-1=1011,n&(n-1) = 1000。我們把上面的分析總結起來就是,把一個整數減去1,再和原整數做與運算,會把該整數最右邊一個1變成0。那么一個整數的二進制表示中有多少個1,就可以進行多少次這樣的操作。

1 class Solution { 2 public: 3 int NumberOf1(int n) { 4 int cnt = 0; 5 while (n) { 6 cnt++; 7 n = n & (n - 1); 8 } 9 return cnt; 10 } 11 };
【面試題10的拓展題】 p82
第三章 高質量的代碼
【面試題11】數值的整數次方
給定一個double類型的浮點數base和int類型的整數exponent。求base的exponent次方。
題解:這個題目的難點在於很多情況,需要分類討論。如果base為0,或者exponent為正數,負數,或者零的情況。具體的明天再補充,今天累了先休息。

1 class Solution { 2 public: 3 double Power(double base, int exponent) { 4 if (exponent == 0) {return 1;} 5 if (equal(base, 0.0)) { 6 return base; 7 } 8 int absExp = exponent; 9 if (exponent < 0) { absExp = -absExp; } 10 double ans = PowerWithAbsExp(base, absExp); 11 if (exponent < 0) { ans = 1/ans; } 12 return ans; 13 } 14 bool equal(double num1, double num2) { 15 if (num1 - num2 < 0.000000001 && num1 - num2 > -0.000000001) { 16 return true; 17 } 18 return false; 19 } 20 double PowerWithAbsExp(double base, int absExp) { 21 if (absExp == 0) {return 1;} 22 if (absExp == 1) {return base;} 23 double res = PowerWithAbsExp(base, absExp >> 1); 24 res = res * res; 25 if (absExp & 0x1 == 1) { 26 res *= base; 27 } 28 return res; 29 } 30 };
【面試題12】打印 1 到最大的 n 位數
【面試題13】在 O(1) 時間刪除鏈表節點
【面試題14】調整數組順序使奇數位於偶數前面 (leetcode 905這題沒有要求相對位置不變)
輸入一個整數數組,實現一個函數來調整該數組中數字的順序,使得所有的奇數位於數組的前半部分,所有的偶數位於數組的后半部分,並保證奇數和奇數,偶數和偶數之間的相對位置不變。
書上也沒有要求交換后的數組相對位置不變。所以這題提供兩種思路。
(1) 按照牛客網的題目,要求交換前后相對位置不變的情況下,有點類似與冒泡排序。每一趟都把不符合「前奇后偶」性質的相鄰元素交換位置,做n-1趟就可以了。時間復雜度是 O(n^2)

1 class Solution { 2 public: 3 void reOrderArray(vector<int> &array) { 4 const int n = array.size(); 5 for (int i = 1; i <= n-1; ++i) { 6 for (int j = 0; j < n - 1; ++j) { 7 if (array[j] % 2 == 0 && array[j+1] % 2 == 1) { 8 swap(array[j], array[j+1]); 9 } 10 } 11 } 12 return; 13 } 14 };
(2) 按照劍指offer書上和leetcode的題意,沒有要求奇數和奇數,偶數和偶數相對位置不變的情況下,我們可以用two pointers掃描一遍就可以解決問題。時間復雜度是 O(n)。

1 class Solution { 2 public: 3 vector<int> sortArrayByParity(vector<int>& A) { 4 const int n = A.size(); 5 int begin = 0, end = n - 1; 6 while (begin < end) { 7 if (begin < n && end >= 0 && A[begin] % 2 == 1 && A[end] % 2 == 0) { 8 swap(A[begin], A[end]); 9 begin++, end--; 10 } 11 while (begin < n && A[begin] % 2 == 0) { begin++; } 12 while (end >= 0 && A[end] % 2 == 1) { end--; } 13 //printf("begin = %d, end = %d \n", begin, end); 14 } 15 return A; 16 } 17 };
(3) 除此之外,劍指offer提出了一種工程性拓展的方式,把模式通用化,聽說可以秒殺offer。 細節等待補充, P104.
【面試題15】鏈表中倒數第k個結點
輸入一個鏈表,輸出該鏈表中倒數第k個結點。
題解:這個題目其實一開始大家的想法都是先遍歷一次鏈表,數出一共有n個節點,然后倒數第k個節點其實就是從前往后數第 n-(k-1) 個節點(一共遍歷兩次)。但是,面試官會說我想要一種方法只需要遍歷一次鏈表。blablabla...
我們可以用 two pointers 的解法,第一根指針先走 k-1 步,從第k步開始,兩根指針一起往前走,他們的距離保持在 k-1步,當第一根指針指向最后一個節點的時候,第二根指針正好是倒數第k個節點。
注意判頭(頭節點是空),判尾(第一根指針是否超過了最后一個節點,踩內存),判空(鏈表為空,或者k大於鏈表長度n)。

1 /* 2 struct ListNode { 3 int val; 4 struct ListNode *next; 5 ListNode(int x) : 6 val(x), next(NULL) { 7 } 8 };*/ 9 class Solution { 10 public: 11 ListNode* FindKthToTail(ListNode* pListHead, unsigned int k) { 12 if (!pListHead || k == 0) {return NULL;} 13 ListNode* p1 = pListHead, *p2 = pListHead; 14 for (int i = 0; i < k - 1; ++i) { 15 if (p1->next) { 16 p1 = p1->next; 17 } else { 18 return NULL; 19 } 20 } 21 while (p1->next) { 22 p1 = p1->next; 23 p2 = p2->next; 24 } 25 return p2; 26 } 27 };
PS:書后練習題目有兩個相關題目,這兩個還沒看 P111
【面試題16】反轉鏈表
輸入一個鏈表,反轉鏈表后,輸出新鏈表的表頭。
題解:掃描一遍反轉鏈表。3根指針。(這個題目太久沒寫了,WA了兩次,都是在多節點的情況下,eg = {1, 2, 3, 4 ,5})

1 /* 2 struct ListNode { 3 int val; 4 struct ListNode *next; 5 ListNode(int x) : 6 val(x), next(NULL) { 7 } 8 };*/ 9 class Solution { 10 public: 11 ListNode* ReverseList(ListNode* pHead) { 12 if (!pHead) {return pHead;} 13 ListNode *pre = NULL, *cur = NULL, *ne = pHead; 14 while (ne) { 15 pre = cur; 16 cur = ne; 17 ne = cur->next; 18 cur->next = pre; 19 } 20 return cur; 21 } 22 };
【面試題17】合並兩個排序的鏈表
輸入兩個單調遞增的鏈表,輸出兩個鏈表合成后的鏈表,當然我們需要合成后的鏈表滿足單調不減規則。
題解:鏈表版本的歸並排序。(一次過了)

1 /* 2 struct ListNode { 3 int val; 4 struct ListNode *next; 5 ListNode(int x) : 6 val(x), next(NULL) { 7 } 8 };*/ 9 class Solution { 10 public: 11 ListNode* Merge(ListNode* pHead1, ListNode* pHead2) 12 { 13 if (!pHead1) {return pHead2;} 14 if (!pHead2) {return pHead1;} 15 ListNode *p1 = pHead1, *p2 = pHead2, *head = NULL, *tail = NULL; 16 while (p1 && p2) { 17 if (p1->val <= p2->val) { 18 if (!head) { 19 tail = head = p1; 20 } else { 21 tail = tail->next = p1; 22 } 23 p1 = p1->next; 24 } else { 25 if (!head) { 26 tail = head = p2; 27 } else { 28 tail = tail->next = p2; 29 } 30 p2 = p2->next; 31 } 32 } 33 while (p1) { 34 tail = tail->next = p1; 35 p1 = p1->next; 36 } 37 while (p2) { 38 tail = tail->next = p2; 39 p2 = p2->next; 40 } 41 return head; 42 } 43 };
【面試題18】樹的子結構
輸入兩棵二叉樹A,B,判斷B是不是A的子結構。(ps:我們約定空樹不是任意一個樹的子結構)
題解:遞歸查找判斷,先在A中找一個和B的根節點值相同的節點,然后遞歸判斷。不行的話換下一個A的節點。
我第一次寫理解上有點問題,我認為 B 是 A 的子結構的時候,當B的葉子節點沒有子節點的時候,A也必須沒有子節點。其實不是這樣的。如果在A中找到了B的葉子節點,那么A的這個節點下面可以有別的節點。可以參考P118的例子。

1 /* 2 struct TreeNode { 3 int val; 4 struct TreeNode *left; 5 struct TreeNode *right; 6 TreeNode(int x) : 7 val(x), left(NULL), right(NULL) { 8 } 9 };*/ 10 class Solution { 11 public: 12 bool HasSubtree(TreeNode* pRoot1, TreeNode* pRoot2) 13 { 14 if (!pRoot1 || !pRoot2) {return false;} 15 bool res = false; 16 if (pRoot1->val == pRoot2->val) { 17 res = JudgeSame(pRoot1, pRoot2); 18 } 19 if (!res) { 20 res = HasSubtree(pRoot1->left, pRoot2); 21 } 22 if (!res) { 23 res = HasSubtree(pRoot1->right, pRoot2); 24 } 25 return res; 26 } 27 bool JudgeSame(TreeNode* pRoot1, TreeNode* pRoot2) { 28 if (pRoot2 == NULL) {return true;} 29 if (pRoot1 == NULL) {return false;} 30 if (pRoot1->val != pRoot2->val) {return false;} 31 return JudgeSame(pRoot1->left, pRoot2->left) && JudgeSame(pRoot1->right, pRoot2->right); 32 } 33 };
第四章 解決面試題的思路
【面試題19】二叉樹的鏡像
二叉樹的鏡像定義:源二叉樹 8 / \ 6 10 / \ / \ 5 7 9 11 鏡像二叉樹 8 / \ 10 6 / \ / \ 11 9 7 5

1 /* 2 struct TreeNode { 3 int val; 4 struct TreeNode *left; 5 struct TreeNode *right; 6 TreeNode(int x) : 7 val(x), left(NULL), right(NULL) { 8 } 9 };*/ 10 class Solution { 11 public: 12 void Mirror(TreeNode *pRoot) { 13 if (!pRoot) { return; } 14 mirror(&(pRoot->left), &(pRoot->right)); 15 return; 16 } 17 void mirror(TreeNode **l, TreeNode **r) { 18 if (!*l && !*r) {return;} 19 //swap(l, r); 20 TreeNode *temp = *l; 21 *l = *r; 22 *r = temp; 23 if (*l) { 24 mirror(&((*l)->left), &((*l)->right)); 25 } 26 if (*r) { 27 mirror(&((*r)->left), &((*r)->right)); 28 } 29 return; 30 } 31 };

1 class Solution { 2 public: 3 vector<int> printMatrix(vector<vector<int> > matrix) { 4 vector<int> ans; 5 int x = 0, y = 0; 6 const int n = matrix.size(); 7 if (n == 0) {return ans;} 8 const int m = matrix[0].size(); 9 if (m == 0) {return ans;} 10 int tot = n * m, cnt = 0; 11 int beginx = 0, endx = n - 1, beginy = 0, endy = m - 1; 12 while (cnt < tot) { 13 y = beginy; 14 while (cnt < tot&& y <= endy) { 15 ans.push_back(matrix[beginx][y]); 16 cnt++, y++; 17 } 18 ++beginx; 19 x = beginx; 20 while (cnt < tot && x <= endx) { 21 ans.push_back(matrix[x][endy]); 22 cnt++, x++; 23 } 24 --endy; 25 y = endy; 26 while (cnt < tot && y >= beginy) { 27 ans.push_back(matrix[endx][y]); 28 cnt++, y--; 29 } 30 --endx; 31 x = endx; 32 while (cnt < tot && x >= beginx) { 33 ans.push_back(matrix[x][beginy]); 34 cnt++, --x; 35 } 36 ++beginy; 37 } 38 return ans; 39 } 40 };

1 class Solution { 2 public: 3 void push(int value) { 4 s1.push(value); 5 if (!s2.empty()) { 6 int t = ::min(value, s2.top()); 7 s2.push(t); 8 } else { 9 s2.push(value); 10 } 11 } 12 void pop() { 13 //if (s1.empty()) {cerr << "empty"}; 14 s1.pop(); 15 s2.pop(); 16 } 17 int top() { 18 return s1.top(); 19 } 20 int min() { 21 return s2.top(); 22 } 23 stack<int> s1, s2; 24 };

1 class Solution { 2 public: 3 bool IsPopOrder(vector<int> pushV,vector<int> popV) { 4 if (pushV.empty() || popV.empty()) {return false;} 5 stack<int> s; 6 int idx = 0; //popV; 7 for (int i = 0; i < pushV.size(); ++i) { 8 s.push(pushV[i]); 9 while (!s.empty() && s.top() == popV[idx]) { 10 ++idx; 11 s.pop(); 12 } 13 } 14 return s.empty(); 15 } 16 };

1 /* 2 struct TreeNode { 3 int val; 4 struct TreeNode *left; 5 struct TreeNode *right; 6 TreeNode(int x) : 7 val(x), left(NULL), right(NULL) { 8 } 9 };*/ 10 class Solution { 11 public: 12 vector<int> PrintFromTopToBottom(TreeNode* root) { 13 vector<int> ans; 14 if (!root) { return ans; } 15 queue<TreeNode*> que; 16 que.push(root); 17 while (!que.empty()) { 18 TreeNode* cur = que.front(); 19 que.pop(); 20 ans.push_back(cur->val); 21 if (cur->left) { que.push(cur->left); } 22 if (cur->right) { que.push(cur->right);} 23 } 24 return ans; 25 } 26 };
【面試題24】二叉搜索樹的后序遍歷序列
輸入一個整數數組,判斷該數組是不是某二叉搜索樹的后序遍歷的結果。如果是則輸出Yes,否則輸出No。假設輸入的數組的任意兩個數字都互不相同。
題解:先找到根節點,然后區分左子樹和右子樹,遞歸判斷。對於左右子樹數組中的每個元素應當判斷和根節點的大小關系。

1 class Solution { 2 public: 3 bool VerifySquenceOfBST(vector<int> sequence) { 4 const int n = sequence.size(); 5 if (n == 0) {return false;} 6 if (n == 1) {return true;} 7 int root = sequence.back(); 8 int idx = 0; 9 while (sequence[idx] < root) { 10 ++idx; 11 } 12 // idx = {0, n-1, middle} 13 vector<int> left(sequence.begin(), sequence.begin()+idx), right(sequence.begin()+idx, sequence.end()-1); 14 bool ansLeft = true, ansRight = true; 15 16 if (!left.empty()) { 17 ansLeft = VerifySquenceOfBST(left); 18 } 19 if (!right.empty()) { 20 //這里必須要判斷,不然可能判斷成右子樹的數組里面有元素比根節點小。 21 for (int i = 0; i < right.size(); ++i) { 22 if (right[i] < root) { 23 return false; 24 } 25 } 26 ansRight = VerifySquenceOfBST(right); 27 } 28 return ansLeft && ansRight; 29 } 30 };
【面試題25】二叉樹中和為某一值的路徑
輸入一顆二叉樹的跟節點和一個整數,打印出二叉樹中結點值的和為輸入整數的所有路徑。路徑定義為從樹的根結點開始往下一直到葉結點所經過的結點形成一條路徑。(注意: 在返回值的list中,數組長度大的數組靠前)
題解:直接dfs。注意寫法,如果在dfs中開始判斷root是否為空再往ans里面加當前路徑的時候,當前路徑會被加兩次,因為我們會調用 dfs(root->left), dfs(root->right) (葉子節點的root->left 和 root->right 都為空) 。所以我答案里面的寫法是直接在當前層判斷是否滿足條件而且是否為葉子節點(是否應該被加在答案里面)。

1 /* 2 struct TreeNode { 3 int val; 4 struct TreeNode *left; 5 struct TreeNode *right; 6 TreeNode(int x) : 7 val(x), left(NULL), right(NULL) { 8 } 9 };*/ 10 class Solution { 11 public: 12 vector<vector<int> > FindPath(TreeNode* root,int expectNumber) { 13 vector<vector<int>> ans; 14 if (!root) {return ans;} 15 vector<int> temp; 16 dfs(root, expectNumber, ans, temp); 17 return ans; 18 } 19 void dfs(TreeNode* root, int leftNumber, vector<vector<int>>& ans, vector<int>& temp) { 20 temp.push_back(root->val); 21 if (!root->left && !root->right && leftNumber - root->val == 0) { 22 ans.push_back(temp); 23 } else { 24 if (root->left) { 25 dfs(root->left, leftNumber - root->val, ans, temp); 26 } 27 if (root->right) { 28 dfs(root->right, leftNumber - root->val, ans, temp); 29 } 30 } 31 temp.pop_back(); 32 return; 33 } 34 };
【面試題26】復雜鏈表的復制
請實現函數 ComplexListNode* Clone(ComplexListNode* pHead) ,復制一個復雜鏈表。在復雜鏈表中,每個結點除了有一個 next 指針指向下一個結點之外,還有一個 sibling 指針指向鏈表的任意結點或者NULL。
牛客網的結點定義如下:
1 struct RandomListNode { 2 int label; 3 struct RandomListNode *next, *random; 4 RandomListNode(int x) : 5 label(x), next(NULL), random(NULL) { 6 } 7 };
題解:這道題有兩種解法都還可以。
第一種解法是我們首先復制一個正常鏈表,先連接next指針,然后每次對於結點N,new出來一個N' 的時候用一個哈希保存 <N, N'>,然后再考慮兄弟結點,直接用map查找對應的結點。 這樣時間復雜度是O(N), 空間復雜度也是O(N). (leetcode clone graph一樣的思路)

1 /* 2 struct RandomListNode { 3 int label; 4 struct RandomListNode *next, *random; 5 RandomListNode(int x) : 6 label(x), next(NULL), random(NULL) { 7 } 8 }; 9 */ 10 class Solution { 11 public: 12 RandomListNode* Clone(RandomListNode* pHead) 13 { 14 if (!pHead) {return pHead;} 15 unordered_map<RandomListNode*, RandomListNode*> mp; 16 RandomListNode *head = 0, *tail = 0, *cur = pHead; 17 //1.先復制鏈表 18 while (cur) { 19 if(!head) { 20 head = tail = new RandomListNode(cur->label); 21 mp[cur] = tail; 22 } else { 23 tail = tail->next = new RandomListNode(cur->label); 24 mp[cur] = tail; 25 } 26 cur = cur->next; 27 } 28 //2. 然后關心一下random指針 29 cur = pHead; tail = head; 30 while (cur) { 31 if (cur->random) { 32 tail->random = mp[cur->random]; 33 } 34 cur = cur->next; 35 tail = tail->next; 36 } 37 return head; 38 39 } 40 };
第二種解法是比較驚奇的解法,以前沒接觸過這種玩法。第一步依舊是根據原始鏈表的每個結點N創建對應的N',這一次我們把 N' 鏈接在 N 后面。
第二步復制 sibling (如果原始鏈表中的結點N的sibling指向S,那么它的復制結點N' 的sibling指向S的下一個結點S')。
第三步拆分奇偶鏈表。
奇怪了,死活過不了。不明覺厲
【面試題27】二叉搜索樹與雙向鏈表
輸入一棵二叉搜索樹,將該二叉搜索樹轉換成一個排序的雙向鏈表。要求不能創建任何新的結點,只能調整樹中結點指針的指向。
題解:看到這題完全不會做。抄了答案,一定要復習。
我們在把BST轉換成排好序的雙向鏈表的時候,原來指向左兒子的指針調整為鏈表中指向前一個結點的指針,原來指向右兒子的指針調整成鏈表中指向后一個結點指針。

1 /* 2 struct TreeNode { 3 int val; 4 struct TreeNode *left; 5 struct TreeNode *right; 6 TreeNode(int x) : 7 val(x), left(NULL), right(NULL) { 8 } 9 };*/ 10 class Solution { 11 public: 12 TreeNode* Convert(TreeNode* pRootOfTree) 13 { 14 if (!pRootOfTree) { return pRootOfTree; } 15 TreeNode* lastNodeInList = 0; 16 ConvertNode(pRootOfTree, &lastNodeInList); 17 TreeNode* head = lastNodeInList; 18 while (head && head->left) { 19 head = head->left; 20 } 21 return head; 22 } 23 void ConvertNode(TreeNode* pNode, TreeNode** lastNodeInList) { 24 if (!pNode) { 25 return; 26 } 27 if (pNode->left) { 28 ConvertNode(pNode->left, lastNodeInList); 29 } 30 pNode->left = *lastNodeInList; 31 if (*lastNodeInList) { 32 (*lastNodeInList)->right = pNode; 33 } 34 *lastNodeInList = pNode; 35 if (pNode->right) { 36 ConvertNode(pNode->right, lastNodeInList); 37 } 38 } 39 };
【面試題28】 字符串的排列
輸入一個字符串,按字典序打印出該字符串中字符的所有排列。例如輸入字符串abc,則打印出由字符a,b,c所能排列出來的所有字符串abc,acb,bac,bca,cab和cba。
題解:(1)直接用stl的next_permutaion; (2)回溯法(需要復習重新思考為什么這么寫就能對。)

1 class Solution { 2 public: 3 vector<string> Permutation(string str) { 4 const int n = str.size(); 5 vector<string> ans; 6 if (n == 0) {return ans;} 7 do { 8 ans.push_back(str); 9 } while (next_permutation(str.begin(), str.end())); 10 return ans; 11 } 12 };

1 class Solution { 2 public: 3 vector<string> Permutation(string str) { 4 n = str.size(); 5 set<string> ans; 6 vector<string> ret; 7 if (n == 0) {return ret;} 8 backtracking(str, 0, ans, str); 9 for (auto iter = ans.begin(); iter != ans.end(); ++iter) { 10 ret.push_back(*iter); 11 } 12 return ret; 13 } 14 void backtracking(string& str, int cur, set<string>& ans, string& temp) { 15 if (cur == n) { 16 ans.insert(temp); 17 } 18 for (int i = cur; i < n; ++i) { 19 swap(str[cur], temp[i]); 20 backtracking(str, cur+1, ans, temp); 21 swap(str[cur], temp[i]); 22 } 23 } 24 int n; 25 };
【面試題28的拓展題】 P157
【28.1】輸入一個含有8個數字的數組,判斷有沒有可能把這8個數字分別放到正方體的八個頂點上, 使得正方體上三組相對的面上的4個頂點的和都相等。
【28.2】八皇后
第五章 優化時間和空間效率
【面試題29】數組中出現次數超過一半的數字
數組中有一個數字出現的次數超過數組長度的一半,請找出這個數字。例如輸入一個長度為9的數組{1,2,3,2,2,2,5,4,2}。由於數字2在數組中出現了5次,超過數組長度的一半,因此輸出2。如果不存在則輸出0。
題解:本題有兩種解法,第一種是基於partition函數的O(n)解法,第二種是「Boyer-Moore Voting」?算法。
第一種解釋:基於快速排序的partition. (需要再次理解)
第二種解釋:數組中有一個數字出現的次數超過數組的一半,也就是說明它出現的次數比其他所有數字出現的次數之和還多。那么我們遍歷數組的時候考慮保存兩個值,一個數組中的數字,另外一個是次數。當我們遍歷到下一個數字的時候,如果下一個數字和我們之前保存的數字相同,則次數加1;如果下一個數字和我們之前保存的數字不同,則次數減1。如果次數為零,我們需要保存下一個數字,並且把次數設置為1。我們要找的數字就是最后把次數設置為1的那個數。 (但是需要在最后判斷這個數字是否真的次數出現超過一半,有可能數組中根本沒有次數超過一半的數字。)時間復雜度O(N),空間復雜度O(1)。

1 class Solution { 2 public: 3 int MoreThanHalfNum_Solution(vector<int> numbers) { 4 int n = numbers.size(); 5 if (n == 0) {return 0;} 6 //用一個變量和一個計數器統計 7 int num = numbers[0], cnt = 1; 8 for (int i = 1; i < n; ++i) { 9 if (cnt == 0) {num = numbers[i]; cnt++;} 10 else if (numbers[i] == num) { 11 cnt++; 12 } else { 13 cnt--; 14 } 15 } 16 //判斷最后的num是否真的超過了數組元素的一半。 17 int times = 0; 18 for (int i = 0; i < n; ++i) { 19 if (numbers[i] == num) { 20 times++; 21 } 22 } 23 return times > n / 2 ? num : 0; 24 } 25 };
【面試題30】 最小的K個數
輸入n個整數,找出其中最小的K個數。例如輸入4,5,1,6,2,7,3,8這8個數字,則最小的4個數字是1,2,3,4。
題解:本題有一個O(N)的解法,只有當我們可以修改輸入數組的時候可以用(mark下,有空研究下)
第一種解法:O(N)的算法,只有當我們可以修改輸入的數組的時候可以用(P167)
第二種解法:O(nlogk)的解法,特別適合處理海量數據。(適合n比較大,k比較小的實際問題)。我們用一個大根堆維護k個元素,當堆里面的元素個數小於k個的時候,就用數組里面的元素往里加,當堆里面的元素等於k個的時候,取堆里面最大的那個元素,判斷它和新元素的大小關系,如果它大於新元素,就把它彈出,把新元素放進去。(假設題目是要求從海量數據里面尋找最小的k個數字,由於內存的大小是有限的,有可能不能把這些海量數據一次性全部載入內存。這個時候我們可以借助從硬盤中每次讀取一個數字,然后判斷是否需要放進堆里面即可。)

1 class Solution { 2 public: 3 vector<int> GetLeastNumbers_Solution(vector<int> input, int k) { 4 priority_queue<int> pq; 5 const int n = input.size(); 6 //考慮邊界問題(k和n的大小比較) 7 if (n < k || k < 1) {return vector<int>();} 8 if (n == k) {return input;} 9 10 for (int i = 0; i < n; ++i) { 11 if (pq.size() < k) { 12 pq.push(input[i]); 13 } else if (pq.size() == k) { 14 int t = pq.top(); 15 if (t > input[i]) { 16 pq.pop(); 17 pq.push(input[i]); 18 } 19 } 20 } 21 vector<int> ans; 22 while (!pq.empty()) { 23 ans.push_back(pq.top()); 24 pq.pop(); 25 } 26 return ans; 27 } 28 };
【面試題31】連續子數組的最大和(最大子段和)
輸入一個整型數組,數組里面有正數也有負數。數組中一個或者連續的多個整數組成一個子數組。求所有子數組的和的最大值。要求時間復雜度為O(N)。
題解:求當前的累加和,如果累加和是負數的話,直接賦值為0。

1 class Solution { 2 public: 3 int FindGreatestSumOfSubArray(vector<int> array) { 4 int ans = INT_MIN; 5 const int n = array.size(); 6 if (n == 0) {return ans;} 7 int temp = 0; 8 for (int i = 0; i < n; ++i) { 9 temp = temp + array[i]; 10 ans = max(temp, ans); 11 if (temp < 0) { 12 temp = 0; 13 } 14 } 15 return ans; 16 } 17 };
【面試題32】從 1 到 n 整數中 1 出現的次數
輸入一個整數n,求從 1 到 n 這 n 個整數的十進制表示中 1 出現的次數。 例如輸入 12, 從 1 到 12 這些整數中包含 1 的數字有 1, 10, 11 和 12, 1 一共出現了5次。
題解: 編程之美里面也有這道題。但是多了一個小問題。(這題先留着,還沒研究好。。。)
【面試題33】把數組排成最小的數
輸入一個正整數數組,把數組里所有數字拼接起來排成一個數,打印能拼接出的所有數字中最小的一個。例如輸入數組{3,32,321},則打印出這三個數字能排成的最小數字為321323。
題解:給任意兩個數字 m 和 n, 給出一個排序規則,使得 mn < nm。根據題目的要求,兩個數字 m 和 n 能拼接成數字 mn 和 nm。如果 mn < nm,我們應該打印 mn,也就是 m 排在 n 前面,我們這時定義 m 小於 n。反之,如果 nm < mn 我們定義 n < m。如果 mn = nm,那么 m = n。(注意這題可能用 int 類型的數值計算會導致溢出,所以我們可以改成字符串類型進行大小比較和判斷。) 這題可能會要求證明,看看就好,劍指offer P179。

1 class Solution { 2 public: 3 string PrintMinNumber(vector<int> numbers) { 4 const int n = numbers.size(); 5 if (n == 0) {return "";} 6 vector<string> strNumbers(n); 7 for (int i = 0; i < n; ++i) { 8 strNumbers[i] = to_string(numbers[i]); 9 } 10 sort(strNumbers.begin(), strNumbers.end(), cmp); 11 string ans = ""; 12 for (int i = 0; i < n; ++i) { 13 ans += strNumbers[i]; 14 } 15 return ans; 16 } 17 static bool cmp(const string &s1, const string &s2) { 18 string str12 = s1 + s2, str21 = s2 + s1; 19 return str12 < str21; 20 } 21 };
【面試題34】丑數 (leetcode 264)
把只包含質因子2、3和5的數稱作丑數(Ugly Number)。例如6、8都是丑數,但14不是,因為它包含質因子7。 習慣上我們把1當做是第一個丑數。求按從小到大的順序的第N個丑數。
題解:根據丑數的定義,丑數應該是另外一個丑數乘以 2, 3,或者 5的結果(除了1)。那么我們可以創建一個數組,里面的數字是排序好的丑數,每一個丑數都是前面的丑數乘以 2, 3,5得到的。思路的關鍵在於如何確保數組里面的丑數的排好序的。對於原來要乘以2的丑數而言,肯定有那么一個位置 T2, 排在它之前的每一個丑數乘以 2 得到的結果都會小於已有最大的丑數,排在它之后的每個丑數乘以 2 得到的結果都會比較大。我們只需要記錄下這個T2 的位置就可以了,然后每次生成新的丑數的時候去更新這個 T2。對於乘以 3 和乘以 5 而言,也同樣有這個 T3, 和 T5。 (注意牛客網的 OJ 里面有個 n = 0的邊界)

1 class Solution { 2 public: 3 int GetUglyNumber_Solution(int index) { 4 if (index == 0) {return 0;} //在leetcode上沒有這個用例,寫代碼的時候因為0沒有考慮,一直RE。 5 vector<int> f(index, 0); 6 f[0] = 1; 7 int ptr2 = 0, ptr3 = 0, ptr5 = 0; 8 int minn = 0; 9 for (int i = 1; i < index; ++i) { 10 int num2 = f[ptr2] * 2, num3 = f[ptr3] * 3, num5 = f[ptr5] * 5; 11 minn = min(num2, min(num3, num5)); //有可能 num2 和 num3 或者這三個數中的兩個數值相等,都是minn,比如6, 這個時候兩個指針都要往前移動。 12 if (num2 == minn) { 13 ptr2++; 14 } 15 if (num3 == minn) { 16 ptr3++; 17 } 18 if (num5 == minn) { 19 ptr5++; 20 } 21 f[i] = minn; 22 } 23 return f[index-1]; 24 } 25 };
【面試題35】第一個只出現一次的字符
在一個字符串(0<=字符串長度<=10000,全部由字母組成)中找到第一個只出現一次的字符,並返回它的位置, 如果沒有則返回 -1(需要區分大小寫).
題解:直接用個map求。這個章節的重點是用空間優化時間效率。

1 class Solution { 2 public: 3 int FirstNotRepeatingChar(string str) { 4 const int n = str.size(); 5 if (n == 0) {return -1;} 6 map<char, vector<int>> mp; 7 for (int i = 0; i < n; ++i) { 8 char c = str[i]; 9 mp[c].push_back(i); 10 } 11 int minPos = n + 1; 12 for (auto iter = mp.begin(); iter != mp.end(); ++iter) { 13 if (iter->second.size() == 1) { 14 minPos = min(minPos, iter->second[0]); 15 } 16 } 17 return minPos == n + 1 ? -1 : minPos; 18 } 19 };
【面試題36】數組中的逆序對 (重點題,歸並排序找逆序對)
題目保證輸入的數組中沒有的相同的數字
數據范圍:
對於%50的數據,size<=10^4
對於%75的數據,size<=10^5
對於%100的數據,size<=2*10^5
題解:
【面試題37】兩個鏈表的第一個公共結點
輸入兩個鏈表,找出它們的第一個公共結點。
題解:本題有兩種解法,第一種用兩個輔助棧,第二種用 2 pointers。
第一種方法:如果兩個鏈表有公共結點,那么公共結點出現在鏈表的尾部,如果我們從鏈表的尾部開始比較,最后一個相同的結點就是我們要找的結點。可是單向鏈表中我們只能從頭結點開始遍歷。我們想要從尾部開始依次向前比較,那么這種后進先出的操作可以借助棧來完成。分別把兩個鏈表的結點都放進兩個棧里,這樣兩個鏈表的尾結點就位於棧頂,接下來比較兩個棧頂的結點是否相同。如果相同,就 pop 出去,直到找到最后一個相同的結點。時間復雜度 O(m+n),空間復雜度 O(m+n)。

1 /* 2 struct ListNode { 3 int val; 4 struct ListNode *next; 5 ListNode(int x) : 6 val(x), next(NULL) { 7 } 8 };*/ 9 class Solution { 10 public: 11 //兩個輔助棧 12 ListNode* FindFirstCommonNode(ListNode* pHead1, ListNode* pHead2) { 13 if (!pHead1 || !pHead2) {return NULL;} 14 ListNode *p1 = pHead1, *p2 = pHead2; 15 stack<ListNode*> stk1, stk2; 16 while (p1) { 17 stk1.push(p1); 18 p1 = p1->next; 19 } 20 while (p2) { 21 stk2.push(p2); 22 p2 = p2->next; 23 } 24 ListNode* ret = NULL; 25 while (!stk1.empty() && !stk2.empty() && stk1.top() == stk2.top()) { 26 ret = stk1.top(); 27 stk1.pop(), stk2.pop(); 28 } 29 30 return ret; 31 } 32 33 };
第二種方法:先分別求出兩個鏈表的長度,然后讓長的那個鏈表先走到和短的鏈表一樣長的位置,然后兩個鏈表一起走,一起走的時候第一個相等的結點就是第一個公共結點。

1 /* 2 struct ListNode { 3 int val; 4 struct ListNode *next; 5 ListNode(int x) : 6 val(x), next(NULL) { 7 } 8 };*/ 9 class Solution { 10 public: 11 //思路是分別求出 L1 和 L2 的長度, 然后讓比較長的那個先走到和短的那個一樣長度,然后一起走,一起走相同的第一個結點就是第一個公共結點。 12 ListNode* FindFirstCommonNode( ListNode* pHead1, ListNode* pHead2) { 13 if (!pHead1 || !pHead2) {return NULL;} 14 int len1 = getLenOfList(pHead1), len2 = getLenOfList(pHead2); 15 int diff = max(len1, len2) - min(len1, len2); 16 ListNode *p1 = pHead1, *p2 = pHead2; 17 if (len1 > len2) { 18 while (diff-- && p1) { 19 p1 = p1->next; 20 } 21 } else if (len1 < len2) { 22 while (diff-- && p2) { 23 p2 = p2->next; 24 } 25 } 26 if (!p1 || !p2) {return NULL;} 27 while (p1 && p2) { 28 if (p1 == p2) { 29 return p1; 30 } else { 31 p1 = p1->next; 32 p2 = p2->next; 33 } 34 } 35 return NULL; 36 } 37 int getLenOfList(ListNode* head) { 38 if (!head) {return 0;} 39 ListNode* cur = head; 40 int len = 0; 41 while (cur) { 42 len++; 43 cur = cur->next; 44 } 45 return len; 46 } 47 };
第六章 面試中的各項能力
【面試題38】數字在排序數組中出現的次數
統計一個數字在排序數組中出現的次數。
題解:用二分查找做,時間復雜度是 O(logN),我是用 lower_bound(), upper_bound() 做的。也可以自己寫二分。

1 class Solution { 2 public: 3 int GetNumberOfK(vector<int> data ,int k) { 4 const int n = data.size(); 5 if (n == 0) {return 0;} 6 auto iter1 = lower_bound(data.begin(), data.end(), k); 7 if (iter1 == data.end() || *iter1 != k) { 8 return 0; 9 } 10 auto iter2 = upper_bound(data.begin(), data.end(), k); 11 int dis = distance(iter1, iter2); 12 return dis; 13 } 14 };
【面試題39】二叉樹的深度
輸入一棵二叉樹的根節點,求該樹的深度。從根結點到葉結點依次經過的結點(含根、葉結點)形成的樹的一條路徑,最長路徑的長度為樹的深度。
題解: 基礎題,直接dfs

1 /* 2 struct TreeNode { 3 int val; 4 struct TreeNode *left; 5 struct TreeNode *right; 6 TreeNode(int x) : 7 val(x), left(NULL), right(NULL) { 8 } 9 };*/ 10 class Solution { 11 public: 12 int TreeDepth(TreeNode* pRoot) 13 { 14 if (!pRoot) {return 0;} 15 int l = TreeDepth(pRoot->left), r = TreeDepth(pRoot->right); 16 return max(l, r) + 1; 17 } 18 };
【面試題39拓展題】平衡二叉樹
輸入一棵二叉樹,判斷該二叉樹是否是平衡二叉樹。如果某二叉樹中任意結點的左右子樹的深度相差不超過 1 ,那么它就是一棵平衡二叉樹。
題解:遞歸判斷。

1 class Solution { 2 public: 3 bool IsBalanced_Solution(TreeNode* pRoot) { 4 if (!pRoot) {return true;} 5 int height = 0; 6 return isBalanced(pRoot, height); 7 } 8 bool isBalanced(TreeNode* pRoot, int& height) { 9 if (!pRoot) {height = 0; return true;} 10 int leftHeight = 0, rightHeight = 0; 11 if (isBalanced(pRoot->left, leftHeight) && isBalanced(pRoot->right, rightHeight)) { 12 int diff = max(leftHeight, rightHeight) - min(leftHeight, rightHeight); 13 if (diff <= 1) { 14 height = max(leftHeight, rightHeight) + 1; 15 return true; 16 } 17 } 18 height = max(leftHeight, rightHeight) + 1; 19 return false; 20 } 21 };
【面試題40】數組中只出現一次的數字
一個整型數組里除了兩個數字之外,其他的數字都出現了偶數次。請寫程序找出這兩個只出現一次的數字。
題解:
【面試題41】和為S的兩個數字 VS 和為S的連續正數序列
【面試題41.1】輸入一個遞增的數組和一個數字 S,在數組中查找兩個數,使得它們的和正好是 S。如果有多對數字的和為 S, 輸出任意一對即可。(牛客網要求如果多對結果,返回乘積最小的那對)
題解:2 pointers。時間復雜度 O(N),空間復雜度 O(1).

1 class Solution { 2 public: 3 vector<int> FindNumbersWithSum(vector<int> array,int sum) { 4 const int n = array.size(); 5 vector<int> ans; 6 if (n < 2) {return ans;} 7 int p1 = 0, p2 = n - 1; 8 long long mul = LLONG_MAX; 9 while (p1 < p2) { 10 int tempSum = array[p1] + array[p2]; 11 if (tempSum == sum) { 12 long long t = array[p1] * array[p2]; 13 if (t < mul) { 14 mul = t; 15 ans = vector<int>{array[p1], array[p2]}; 16 } 17 p1++, p2--; 18 } 19 if (tempSum < sum) { p1++; } 20 if (tempSum > sum) { p2--; } 21 } 22 return ans; 23 } 24 };
【面試題41.2】輸入一個正數 S,打印出所有和為 S 的連續正數序列(至少含有兩個數)。例如輸入 15, 由於 (1 + 2 + 3 + 4 + 5) =( 4 + 5 + 6) = (7 + 8)= 15 ,所以結果打印出 3 個連續序列 1 ~ 5, 4 ~ 6, 7 ~ 8。
題解:
【面試題42】翻轉單詞順序 VS 左旋轉字符串
【面試題42.1】翻轉單詞順序
輸入一個英文句子,翻轉句子中的單詞順序,但單詞內字符的順序不變。為簡單起見,標點符號和普通字母一樣處理。例如輸入字符串 "I am a student.",則輸出 "student. a am I"。
題解:先翻轉整個句子,再依次翻轉每個單詞。(有個注意點,句子的分隔符可能不僅僅是一個空格,可能是多個空格,所以不能用getline,要手動用兩根指針。)

1 class Solution { 2 public: 3 string ReverseSentence(string str) { 4 if (str.empty()) { return str; } 5 const int n = str.size(); 6 reverse(str.begin(), str.end()); 7 stringstream ss; 8 ss << str; 9 int p1 = 0, p2= 0; 10 while (p1 < n && p2 < n) { 11 while (p1 < n && str[p1] == ' ') { 12 ++p1; 13 } 14 p2 = p1; 15 while (p2 < n && str[p2] != ' ') { 16 ++p2; 17 } 18 reverse(str.begin() + p1, str.begin() + p2); 19 p1 = p2 + 1; 20 } 21 return str; 22 } 23 };
【面試題42.2】左旋轉字符串
字符串的左旋轉操作是把字符串前面的若干個字符轉移到字符串的尾部。請定義一個函數實現字符串左旋轉操作的功能。比如輸入字符串 "abcdefg" 和數字 2, 該函數將返回左旋轉兩位得到的結果 "cdefgab"。
題解:先把前 n 位倒序,然后把剩下的字符串倒序,最后整體倒序。

1 class Solution { 2 public: 3 string LeftRotateString(string str, int n) { 4 const int len = str.size(); 5 if (len < n) { return str; } 6 reverse(str.begin(), str.begin()+n); 7 reverse(str.begin()+n, str.end()); 8 reverse(str.begin(), str.end()); 9 return str; 10 } 11 };
【面試題43】n個骰子的點數
【面試題44】撲克牌的順子
【面試題45】圓圈中最后剩下的數字(約瑟夫環)
【面試題46】求 1 + 2 + 3 + 4 + .... + n
【面試題47】不用加減乘除做加法
【面試題48】不能被繼承的類
第七章 兩個面試案例
【面試題49】把字符串轉換成整數 (atoi)
【面試題50】樹中兩個結點的最低公共祖先
第八章 英文版新增面試題
8.1 數組
【面試題51】數組中重復的數字
【面試題52】構建乘積數組
8.2 字符串
【面試題53】正則表達式匹配
【面試題54】表示數值的字符串
【面試題55】字符流中第一個不重復的字符
8.3 鏈表
【面試題56】鏈表中環入口的結點
【面試題57】刪除鏈表中重復的結點
8.4 樹
【面試題58】二叉樹的下一個結點
給定一個二叉樹和其中的一個結點,請找出中序遍歷順序的下一個結點並且返回。注意,樹中的結點不僅包含左右子結點,同時包含指向父結點的指針。
1 struct TreeLinkNode { 2 int val; 3 struct TreeLinkNode *left; 4 struct TreeLinkNode *right; 5 struct TreeLinkNode *next; 6 TreeLinkNode(int x) :val(x), left(NULL), right(NULL), next(NULL) { 7 } 8 };
【面試題59】對稱二叉樹
請實現一個函數,用來判斷一顆二叉樹是不是對稱的。注意,如果一個二叉樹同此二叉樹的鏡像是同樣的,定義其為對稱的。
題解:左子樹的左兒子和右子樹的右兒子比較,左子樹的右兒子和右子樹的左兒子比較。

1 /* 2 struct TreeNode { 3 int val; 4 struct TreeNode *left; 5 struct TreeNode *right; 6 TreeNode(int x) : 7 val(x), left(NULL), right(NULL) { 8 } 9 }; 10 */ 11 class Solution { 12 public: 13 bool isSymmetrical(TreeNode* pRoot) 14 { 15 if (!pRoot) { return true; } 16 return isSymmetric(pRoot->left, pRoot->right); 17 } 18 bool isSymmetric(TreeNode* l, TreeNode* r) { 19 if (!l && !r) {return true;} 20 if (!l || !r) {return false;} 21 if (l->val != r->val) {return false;} 22 return isSymmetric(l->left, r->right) && isSymmetric(l->right, r->left); 23 } 24 25 };
【面試題60】把二叉樹打印成多行
從上到下按層打印二叉樹,同一層結點從左至右輸出。每一層輸出一行。
題解:BFS

1 /* 2 struct TreeNode { 3 int val; 4 struct TreeNode *left; 5 struct TreeNode *right; 6 TreeNode(int x) : 7 val(x), left(NULL), right(NULL) { 8 } 9 }; 10 */ 11 class Solution { 12 public: 13 vector<vector<int> > Print(TreeNode* pRoot) { 14 vector<vector<int>> ret; 15 if (!pRoot) {return ret;} 16 queue<TreeNode*> que, que2; 17 que.push(pRoot); 18 vector<int> temp; 19 while (!que.empty()) { 20 int size = que.size(); 21 while (size--) { 22 TreeNode* cur = que.front(); 23 que.pop(); 24 temp.push_back(cur->val); 25 if (cur->left) { que2.push(cur->left); } 26 if (cur->right) { que2.push(cur->right); } 27 } 28 ret.push_back(temp); 29 temp.clear(); 30 swap(que, que2); 31 } 32 return ret; 33 } 34 };
【面試題61】按照之字形打印二叉樹
請實現一個函數按照之字形打印二叉樹,即第一行按照從左到右的順序打印,第二層按照從右至左的順序打印,第三行按照從左到右的順序打印,其他行以此類推。
題解:用棧的BFS

1 /* 2 struct TreeNode { 3 int val; 4 struct TreeNode *left; 5 struct TreeNode *right; 6 TreeNode(int x) : 7 val(x), left(NULL), right(NULL) { 8 } 9 }; 10 */ 11 class Solution { 12 public: 13 vector<vector<int> > Print(TreeNode* pRoot) { 14 vector<vector<int>> ans; 15 if (!pRoot) {return ans;} 16 stack<TreeNode*> stk, stk2; 17 stk.push(pRoot); 18 int level = 1; 19 vector<int> temp; 20 while (!stk.empty()) { 21 while (!stk.empty()) { 22 TreeNode * cur = stk.top(); 23 stk.pop(); 24 temp.push_back(cur->val); 25 if (level & 1) { 26 if (cur->left) {stk2.push(cur->left);} 27 if (cur->right) {stk2.push(cur->right);} 28 } else { 29 if (cur->right) {stk2.push(cur->right);} 30 if (cur->left) {stk2.push(cur->left);} 31 } 32 } 33 ans.push_back(temp); 34 temp.clear(); 35 swap(stk, stk2); 36 level++; 37 } 38 return ans; 39 } 40 };
【面試題62】序列化二叉樹
【面試題63】二叉搜索樹的第 k 個結點
【面試題64】數據流中的中位數
8.5 棧和隊列
【面試題65】滑動窗口的最大值
8.6 回溯
【面試題66】矩陣中的路徑
【面試題67】機器人的運動范圍