又是很長時間纔回來發一篇博客,前一個月確實由於雜七雜八的事情影響了不少,如今仍是到了大火燃眉毛的時候了,也應該開始繼續整理一下算法的思路了。Linked List你們應該是特別熟悉不過的了,由於這個算是數據結構了裏面基本上最開始講的結構吧。這塊內容也沒有太多須要琢磨的技巧,能夠考量的東西也很少,因此考的就是一些小的trick來完成,面試中鏈表考得特別多,算是面試官對面試者的基礎的考查,因此我建議你們在Linked List這一章,必定要實現Bug Free。這個也是我練的比較多的,有些想法能夠和你們分享。node
1. Dummy Node in Linked List面試
有不少時候,咱們須要對整個鏈表進行操做,這樣會致使鏈表的結構發生變化,或者當須要返回的鏈表頭是不肯定的時候,咱們就須要用一個Dummy Node來存那個開始的「頭」,最後返回的也是這個「頭」。這樣就不須要單獨對head進行操做了,第一個題目以下:算法
Remove Duplicates from Sorted List IIchrome
給定一個排序鏈表,刪除全部重複的元素只留下原鏈表中沒有重複的元素。數據結構
樣例ide
給出 1->2->3->3->4->4->5->null,返回 1->2->5->nullpost
給出 1->1->1->2->3->null,返回 2->3->nullspa
這個題算是比較簡單的題目,就是考慮鏈表的刪除操做。可是若是不用dummy的話,可能還須要單獨考慮head是否爲重複元素,部分代碼以下:翻譯
int val = head->val; while (head->next && head->next->val == val) { head->next = head->next->next; } head = head->next;
這樣就很麻煩,代碼不夠簡潔,並且可能在某些地方出現問題。因此這裏就引入dummy的方法(這塊內容必定要朗讀並背誦全文)(Bug Free):3d
ListNode * deleteDuplicates(ListNode *head) { // write your code here if (!head || !head->next) { return head; } ListNode dummy = ListNode(0); dummy.next = head; head = &dummy; while (head->next && head->next->next) { if (head->next->val == head->next->next->val) { int val = head->next->val; while (head->next && head->next->val == val) { head->next = head->next->next; } } else { head = head->next; } } return dummy.next; }
這裏的思想比較簡單,就是用一個dummy存入一開始head的地址,而後再把head指向dummy,這樣就至關於整個鏈表從head->next開始了,而後進行相應的判斷就好。最後返回dummy.next即爲鏈表當前的head。其實就是一個小技巧,你們就能夠把head當成一個p節點,直接使用就好。
接下來一個題:
Reverse Linked List II
翻轉鏈表中第m個節點到第n個節點的部分
樣例
給出鏈表1->2->3->4->5->null, m = 2 和n = 4,返回1->4->3->2->5->null
相信你們在面試的時候被問到鏈表反轉的時候必定是最高興的,由於這時候你能夠5分鐘把bug free的代碼寫出來。這道題稍微增長了一點點難度,就是反轉從m到n的節點,其餘的不變。其實也不難,就是保留一下關鍵的點,而後其餘地方同樣進行反轉就能夠。這個題也是爲了講解一下dummy,因此放出來,代碼以下(Bug Free):
ListNode *reverseBetween(ListNode *head, int m, int n) { // write your code here if (!head || !head->next) { return head; } ListNode dummy = ListNode(0); dummy.next = head; head = &dummy; for (int i = 1; i < m; ++i) { head = head->next; } // 保存m前一個節點 ListNode *pre = head; // 保存第m個節點 ListNode *mNode = head->next; // 保存第n個節點 ListNode *nNode = mNode; // 保存n的後一個節點 ListNode *post = mNode->next; for (int i = m; i < n; ++i) { ListNode *tmp = post->next; post->next = nNode; nNode = post; post = tmp; } mNode->next = post; pre->next = nNode; return dummy.next; }
這裏就很少解釋了。
這個小節主要就是讓你們熟悉掌握dummy的用法,這樣可以大量介紹代碼量,而且給面試加分很多。
仍是記住兩個點:
這兩種狀況下是須要使用dummy的。
這裏最後來一個題:
Partition List
給定一個單鏈表和數值x,劃分鏈表使得全部小於x的節點排在大於等於x的節點以前。
你應該保留兩部份內鏈表節點原有的相對順序。
樣例
給定鏈表 1->4->3->2->5->2->null,而且 x=3
返回 1->2->2->4->3->5->null
其實這道題是最能體現dummy的做用的。咱們就遍歷整個鏈表,比x小的放入左邊的鏈表,不然放入右邊的鏈表,最後再把兩個鏈表合在一塊兒。這時候使用dummy就不須要再去找各自的頭指針了。看似簡單,可是最後我仍是沒有實現bug free,關鍵的地方就是:當遍歷到最後一個節點的時候,若是這個數比x小,那麼須要把它向前移動,此時就須要把右邊鏈表的尾節點指向空,不然就會出現LTE,這個是很是關鍵的問題,須要你們重視。代碼以下:
ListNode *partition(ListNode *head, int x) { // write your code here if (!head || !head->next) { return head; } ListNode dummy1 = ListNode(0); ListNode dummy2 = ListNode(0); ListNode *pre = &dummy1; ListNode *post = &dummy2; while (head) { if (head->val < x) { pre->next = head; pre = head; } else { post->next = head; post = head; } head = head->next; } // 最後的節點指向空,不然出現死循環 post->next = NULL; pre->next = dummy2.next; return dummy1.next; }
2. Basic Skills in Linked List
說到鏈表的基本技巧,插入、刪除、翻轉這三個你們應該都很熟悉了,而後還有兩個比較麻煩的操做就是合併兩個鏈表或是中分兩個鏈表(這裏用這樣的翻譯求不吐槽,原文:middle of a linked list),能夠說這兩個技巧要是充分掌握了的話,基本上鍊表的題目你也不須要擔憂了。
直接來一個題吧:
Sort List
在 O(n log n) 時間複雜度和常數級的空間複雜度下給鏈表排序。
樣例
給出 1->3->2->null,給它排序變成 1->2->3->null.
這個思路就不須要我講了,接下來我將用歸併排序來作,這裏不適用快速排序是由於大量的操做對於鏈表來講耗時太大了,因此就不考慮了。
歸併排序(Bug Free):
ListNode *merge(ListNode *left, ListNode *right) { ListNode dummy = ListNode(0); ListNode *res = &dummy; while (left && right) { if (left->val < right->val) { res->next = left; left = left->next; } else { res->next = right; right = right->next; } res = res->next; } if (left) { res->next = left; } else { res->next = right; } return dummy.next; } ListNode *sortList(ListNode *head) { // write your code here if (!head || !head->next) { return head; } ListNode *fast = head->next; ListNode *slow = head; while(fast && fast->next) { fast = fast->next->next; slow = slow->next; } ListNode * right = sortList(slow->next); // 這裏很是關鍵,必定要把左邊結尾指向空 slow->next = NULL; ListNode * left = sortList(head); return merge(left,right); }
接下來的一個題在面試中見到過,算是比較簡單,可是容易出錯的題目:
Reorder List
給定一個單鏈表L: L0→L1→…→Ln-1→Ln,
從新排列後爲:L0→Ln→L1→Ln-1→L2→Ln-2→…
必須在不改變節點值的狀況下進行原地操做。
樣例
給出鏈表 1->2->3->4->null,從新排列後爲1->4->2->3->null。
這樣的題其實看上去比較複雜,可是你能夠結合一下我前面講過的幾個題,就是一種很是簡單的作法就能夠完成了。咱們首先找到鏈表的中點,而後把右半段鏈表翻轉,以後再進行合併就能夠,代碼以下(Bug Free):
void merge(ListNode *left, ListNode *right) { while (left && right) { ListNode *node1 = left->next; ListNode *node2 = right->next; left->next = right; right->next = node1; left = node1; right = node2; } } void reorderList(ListNode *head) { // write your code here if (!head || !head->next) { return; } ListNode *fast = head->next; ListNode *slow = head; while (fast && fast->next) { fast = fast->next->next; slow = slow->next; } ListNode *right = slow->next; slow->next = NULL; ListNode *ntr = NULL; while (right && right->next) { ListNode *tmp = right->next; right->next = ntr; ntr = right; right = tmp; } right->next = ntr; merge(head,right); }
這個題關鍵仍是要細心,應該不會出什麼問題的。
3.fast-slow pointers
寫了半天,忽然chrome就崩潰了,真是好心塞。
這個由於以前也講過,因此直接來作一個比較重要的題目吧:
Merge K Sorted Lists
合併k個排序鏈表,而且返回合併後的排序鏈表。嘗試分析和描述其複雜度。
這個題目能夠用優先隊列來作,也能夠兩兩合併而後合在一塊兒,這裏我才用的是分治法來說解:把lists分解爲k/2的規模,而後繼續分解,直至兩兩合併,思路比較簡單,代碼以下
ListNode *mergeTwoLists(ListNode *left, ListNode *right) { ListNode dummy = ListNode(0); ListNode *res = &dummy; while(left && right) { if (left->val < right->val) { res->next = left; left = left->next; } else { res->next = right; right = right->next; } res = res->next; } if (left) { res->next = left; } else { res->next = right; } return dummy.next; } ListNode *mergeHelper(vector<ListNode *> &lists, int start, int end) { if (start == end) { return lists[start]; } int mid = start + (end - start) / 2; ListNode *left = mergeHelper(lists, start, mid); ListNode *right = mergeHelper(lists, mid + 1, end); return mergeTwoLists(left,right); } ListNode *mergeKLists(vector<ListNode *> &lists) { // write your code here if (!lists.size()) { return NULL; } return mergeHelper(lists, 0, lists.size() - 1); }
由於鏈表算是比較基礎的內容了,因此也不須要太多的東西。你們就把幾個重要的trick掌握好就能夠了,仍是須要多多練習。
知識點以下:
1.dummy的使用
2.fast-slow pointer的使用