牛客TOP101刷題記錄
一、排序總結
排序演算法大致可以分為兩類:內排序和外排序。在排序過程中,全部記錄存放在記憶體,則稱為內排序;如果需要使用外存,則稱為外排序。
內排序有以下幾類:
- 插入排序:直接插入、二分法插入、希爾
- 選擇排序:直接選擇、堆排序
- 交換排序:冒泡、快速
- 歸併排序
- 基數排序
排序方法 |
時間複雜度(平均) |
時間複雜度(最壞) |
時間複雜度(最好) |
空間複雜度 |
穩定性 |
複雜性 |
直接插入排序 |
O(n2) |
O(n2) |
O(n) |
O(1) |
穩定 |
簡單 |
希爾排序 |
O(nlog2n) |
O(n2) |
O(n1.3) |
O(1) |
不穩定 |
較複雜 |
直接選擇排序 |
O(n2) |
O(n2) |
O(n2) |
O(1) |
不穩定 |
簡單 |
堆排序 |
O(nlog2n) |
O(nlog2n) |
O(nlog2n) |
O(1) |
不穩定 |
較複雜 |
氣泡排序 |
O(n2) |
O(n2) |
O(n) |
O(1) |
穩定 |
簡單 |
快速排序 |
O(nlog2n) |
O(n2) |
O(nlog2n) |
O(nlog2n) |
不穩定 |
較複雜 |
歸併排序 |
O(nlog2n) |
O(nlog2n) |
O(nlog2n) |
O(n) |
穩定 |
較複雜 |
基數排序 |
O(d(n+r)) |
O(d(n+r)) |
O(d(n+r)) |
O(n+r) |
穩定 |
較複雜 |
1.插入排序
思想:每步將一個待排序的記錄,按其順序碼大小插入到前面已經排序的合適位置,直到全部插入排序完為止。
關鍵點:在前面已經排好序的序列中找到合適的位置插入。
二分查詢排序,是在普通插入排序的基礎上,引入了二分查詢的思想。
希爾排序是對直接插入排序的改良,關鍵點在於,讓較大或者較小的數,快速靠近自身所屬的位置。
2.選擇排序
思想:每趟從待排序的記錄序列中選擇關鍵字最小的記錄放置到已排序序列的最後位置,直到全部排完
關鍵點:找最小的記錄
堆排序:通過堆,這種可以快速找到最小的記錄的方式。
3.交換(冒泡)排序
思想:利用交換元素的位置進行排序,每次兩兩比較待排序的元素,直到全部排完
關鍵問題:理清楚需要進行幾輪排序
快速排序:通過找分界點,將較大的元素和較小的元素快速區分開,直至各區間只有一個數
4.歸併排序
思想:使用了分治法。分:將待排序的序列進行“對半”分,直至各區間只有一個數;治:將有序序列進行合併。
關鍵問題:分與治
5.基數排序
思想:將待比較的正整數統一為同樣的數位長度,短的補零。然後從最低位開始,依次進行一次排序;從最低位到最高位。
關鍵問題:進位制和數位
計數排序:適用於範圍比較小的整數序列。找到最小值和最大值,然後以此建立陣列,統計出現個數,然後排序。
桶排序:遍歷陣列,找到最小值和最大值,然後依次劃分為多個區間(桶),在桶裡單獨排序。
二、TOP101
題目來源:熱門100題
1.連結串列
1.反轉連結串列
兩種反轉方法:
關鍵點:三個指標,pre, cur,next
class Solution { public: ListNode* ReverseList(ListNode* pHead) {
//關鍵點:三個指標,pre, cur,next ListNode *pre = nullptr; ListNode *cur = pHead; ListNode *nex = nullptr; // 這裡可以指向nullptr,迴圈裡面要重新指向 while (cur) { nex = cur->next; cur->next = pre; pre = cur; cur = nex; } return pre; } };
class Solution { public: ListNode* ReverseList(ListNode* pHead) { ListNode* dummy = new ListNode(0); dummy->next = pHead; ListNode* pre = dummy, *cur = pHead; while(cur->next){ ListNode* temp = cur->next; cur->next = temp->next; temp->next = pre->next; pre->next = temp; } return dummy->next; } };
關鍵點:新增虛擬頭結點,在頭上反轉
2.指定區間反轉
關鍵點:虛擬頭節點簡化操作,使用第二種反轉方法
ListNode* dummy = new ListNode(0)
3.每k個一組反轉
關鍵點:特殊情況處理(k == 1, head == nullptr, cur == nullptr)
1 /** 2 * struct ListNode { 3 * int val; 4 * struct ListNode *next; 5 * }; 6 */ 7 8 class Solution { 9 public: 10 /** 11 * 12 * @param head ListNode類 13 * @param k int整型 14 * @return ListNode類 15 */ 16 void reverse(ListNode* list){ 17 ListNode* pre = nullptr, *cur = list, *next; 18 while(cur){ 19 next = cur->next; 20 cur->next = pre; 21 pre = cur; 22 cur = next; 23 } 24 } 25 26 ListNode* reverseKGroup(ListNode* head, int k) { 27 if(!head || k == 1) return head; 28 // write code here 29 ListNode* dummy = new ListNode(0); 30 dummy->next = head; 31 32 ListNode* pre = dummy, *cur = head, *next = cur, *next_head; 33 while(next->next){ 34 for(int i = 1; i < k; i++){ 35 if(next->next){ 36 next = next->next; 37 }else{ 38 return dummy->next; 39 } 40 } 41 next_head = next->next; 42 pre->next = nullptr; 43 next->next = nullptr; 44 reverse(cur); 45 //connect 46 pre->next = next; 47 cur->next = next_head; 48 //change 49 pre = cur; 50 cur = next_head; 51 next = cur; 52 if(cur == nullptr) break; 53 } 54 return dummy->next; 55 } 56 };View Code
4.合併兩個連結串列
關鍵點:兩個指標指向兩個連結串列的未排序節點
5.合併k個已排序的連結串列
三種解法:分治;順序合併;優先佇列
這道題目可以作為分治法的練習題。
6.判斷連結串列中是否有環
關鍵點:快慢指標,兩指標相遇則說明有環。
7.連結串列中環結點的入口結點
關鍵點:第一次相遇說明有環,快指標返回,再次相遇則是環入口
8.連結串列中倒數最後k個結點
關鍵點:雙指標
9.刪除連結串列的倒數第n個結點
關鍵點:建立虛擬頭結點來避免分類討論,找到需要刪除的結點的前一個結點
10.兩個連結串列的第一個公共結點
關鍵點:確認兩個連結串列的長度,然後將連結串列對齊
11.連結串列相加
關鍵點:先反轉連結串列,再相加,直至兩連結串列均為空,且無進位。
12.單鏈表的排序
關鍵點:使用分治法;用快慢指標將連結串列進行 “ 分 ”,合併有序連結串列為 “ 治 ”
出錯點:head 和 !head的具體含義
13.判斷一個連結串列是否為迴文結構
關鍵點:反轉(摺疊),然後對比
14.連結串列的奇偶重排
關鍵點:終止條件為指向偶數項及其後一項的指標不為空
15.刪除有序連結串列中重複的元素1
關鍵點:需要熟練,
16.刪除有序連結串列中重複的元素2
關鍵點:找到要刪除的元素的前一個元素。
2.二分查詢/排序
17.二分查詢
關鍵點:記錄左右邊界,查詢中點,然後壓縮範圍。l <= r, 注意等於情況
18.二維陣列中的查詢
關鍵點:利用性質,從左下角或者右上角出發尋找
錯誤點:for迴圈的判斷條件要用邏輯運算子連結,不能用逗號
19.尋找峰值
關鍵點:二分查詢,壓縮範圍。
20.陣列中的逆序對
關鍵點:歸併排序;在最外層開闢一個足夠大的陣列,然後傳引到函式