- leetcode-206-從尾到頭打印鏈表
- leetcode-19-刪除鏈表的倒數第N個節點
- leetcode83-刪除鏈表中重複元素
- leetcode-82-刪除鏈表中重複元素II
- leetcode-24-兩兩交換鏈表中的節點
- leetcode-25-k個一組翻轉鏈表
- leetcode-92-反轉鏈表範圍II
- leetcode-61-旋轉鏈表
- leetcode-234-迴文鏈表
- leetcode-21-和並兩個排序的鏈表
- leetcode-83-合併K個排序鏈表
- leetcode-141-環形鏈表[offer23]
- leetcode-142-環的入口節點[offer23]
- leetcode-160-兩個鏈表的第一個公共節點[offer-52]
- leetcode-86-分隔鏈表
自己總結的鏈表題目
leetcode-206-從尾到頭打印鏈表
輸入一個鏈表,按鏈表值從尾到頭的順序返回一個ArrayList
struct ListNode {
int val;
ListNode *next;
ListNode(int x) : val(x), next(NULL) {}
};
/**
* 方法一:使用頭插法創建節點
* 超級Bug,leetcode中 C++用C的malloc方式寫不能通過!
* 然後又改成了下面使用new()的方式
*/
ListNode* reverseListII(ListNode* head) {
ListNode *L = (ListNode*)malloc(sizeof(ListNode));
L->next = NULL;
ListNode *s;
while (head) {
s = (ListNode*)malloc(sizeof(ListNode));
s->val = head->val;
s->next = L->next;
L->next = s;
head = head->next;
}
return L->next;
}
/**
* 方法一:使用頭插法創建節點
* 改成使用new()的方式創建鏈表節點就可以通過了
*/
ListNode* reverseListII_CPP(ListNode* head) {
ListNode *L = new ListNode(-1);
ListNode *s;
while (head) {
s = new ListNode(head->val);
s->next = L->next;
L->next = s;
head = head->next;
}
return L->next;
}
/**
* 方法二: 使用棧存儲
* C++編譯器,使用malloc()方式創建節點無法通過
*/
ListNode* reverseList(ListNode* head) {
stack<int> stk;
while (head) {
stk.push(head->val);
head = head->next;
}
ListNode *L = (ListNode*)malloc(sizeof(ListNode));
L->next = NULL;
ListNode *r, *s;
r = L;
while (!stk.empty()) {
s = (ListNode*)malloc(sizeof(ListNode));
s->val = stk.top();
stk.pop();
s->next = NULL;
r->next = s;
r = s;
}
r->next = NULL;
return L->next;
}
leetcode-19-刪除鏈表的倒數第N個節點
/**
* 方法一:使用快慢指針,讓fast先走(n+1)步,slow再從表頭開始走,這樣fast走到末尾時,slow剛好走到要刪除節點的先驅節點
* 然後slow->next = slow->next->next就可以刪除了
*/
ListNode* removeNthFromEnd(ListNode* head, int n) {
ListNode *fast = head, *slow = head;
for (int i = 0; i < n; i++) {
fast = fast->next;
}
// 若刪除的是頭結點,則fast先走n步之後就到NULL了,此時,直接返回頭結點的next節點即可
if (fast == NULL) {
return head->next;
}
while (fast->next) {
fast = fast->next;
slow = slow->next;
}
slow->next = slow->next->next;
return head;
}
leetcode83-刪除鏈表中重複元素
給定一個排序鏈表,刪除所有重複的元素,使得每個元素只出現一次。
示例 1:
輸入: 1->1->2
輸出: 1->2
示例 2:輸入: 1->1->2->3->3
輸出: 1->2->3
/**
* 方法一:使用一個指針,簡潔明瞭
*/
ListNode* deleteDuplicates(ListNode* head) {
ListNode *cur = head;
while (cur && cur->next) {
if (cur->val == cur->next->val) {
cur->next = cur->next->next;
} else {
cur = cur->next;
}
}
return head;
}
/**
* 方法二:遞歸方法
* 首先我們判斷是否至少有兩個結點,若不是的話,直接返回head。否則對head->next調用遞歸函數,
* 並賦值給head->next。這裏可能比較暈,我們先看後面一句,返回的時候,head結點先跟其身後的
* 結點進行比較,如果值相同,那麼返回後面的一個結點,當前的head結點就被跳過了,而如果不同的
* 話,還是返回head結點。我們發現了,進行實質上的刪除操作是在最後一句進行了,再來看第二句,
* 我們對head後面的結點調用遞歸函數,那麼就應該suppose返回來的鏈表就已經沒有重複項了,此時
* 接到head結點後面,再第三句的時候再來檢查一下head是否又duplicate了,實際上遞歸一直走到了
* 末尾結點,再不斷的回溯回來,進行刪除重複結點,
*/
ListNode* deleteDuplicatesII(ListNode* head) {
if (!head || !head->next) return head;
head->next = deleteDuplicates(head->next);
return (head->val == head->next->val) ? head->next : head;
}
leetcode-82-刪除鏈表中重複元素II
deleteDuplicatesII-刪除鏈表中重複元素II
給定一個排序鏈表,刪除所有含有重複數字的節點,只保留原始鏈表中 沒有重複出現 的數字。
示例 1:
輸入: 1->2->3->3->4->4->5
輸出: 1->2->5
示例 2:輸入: 1->1->1->2->3
輸出: 2->3
/**
* 方法二:增加一個頭節點(代碼更精簡!)
*/
ListNode* deleteDuplicatesII(ListNode* head) {
if (head == NULL || head->next == NULL)
return head;
ListNode *head_pre = new ListNode(-1);
head_pre->next = head;
ListNode *pre = head_pre, *cur = head, *last = NULL;
while (cur && cur->next) {
last = cur->next;
if (cur->val == last->val) {
while (last && cur->val == last->val) {
last = last->next;
}
pre->next = last;
cur = last;
} else {
pre = cur;
cur = last;
}
}
return head_pre->next;
}
leetcode-24-兩兩交換鏈表中的節點
給定一個鏈表,兩兩交換其中相鄰的節點,並返回交換後的鏈表。
你不能只是單純的改變節點內部的值,而是需要實際的進行節點交換。示例:
給定 1->2->3->4, 你應該返回 2->1->4->3.
/**
* 方法一:自己畫圖理解一下,比較容易弄糊塗,一定要畫圖
* (經常使用,必須要會!)(注意這裏是兩兩交換,還不是最一般的形式)
* (下面的翻轉部分纔是最一般的形式)
*/
ListNode* swapPairs(ListNode* head) {
ListNode *header = new ListNode(-1), *pre = header;
header->next = head;
while (pre->next && pre->next->next) {
ListNode *t = pre->next->next;
pre->next->next = t->next;
t->next = pre->next;
pre->next = t;
pre = t->next;
}
return header->next;
}
/**
* 方法二:遞歸寫法,回溯思想(遞歸的很不熟悉)
*/
ListNode* swapPairs(ListNode* head) {
if (!head || !head->next) return head;
ListNode *t = head->next;
head->next = swapPairs(head->next->next);
t->next = head;
return t;
}
leetcode-25-k個一組翻轉鏈表
給出一個鏈表,每 k 個節點一組進行翻轉,並返回翻轉後的鏈表。k 是一個正整數,它的值小於或等於鏈表的長度。如果節點總數不是 k 的整數倍,那麼將最後剩餘節點保持原有順序。
示例 :
給定這個鏈表:1->2->3->4->5
當 k = 2 時,應當返回: 2->1->4->3->5
當 k = 3 時,應當返回: 3->2->1->4->5說明 :
你的算法只能使用常數的額外空間。
你不能只是單純的改變節點內部的值,而是需要實際的進行節點交換。
/**
* 方法一:
* 這道題讓我們以每k個爲一組來翻轉鏈表,實際上是把原鏈表分成若干小段,
* 然後分別對其進行翻轉,那麼肯定總共需要兩個函數,一個是用來分段的,
* 一個是用來翻轉的,我們就以題目中給的例子來看,對於給定鏈表1->2->3->4->5,
* 一般在處理鏈表問題時,我們大多時候都會在開頭再加一個dummy node,因爲翻轉
* 鏈表時頭結點可能會變化,爲了記錄當前最新的頭結點的位置而引入的dummy node,
* 那麼我們加入dummy node後的鏈表變爲-1->1->2->3->4->5,如果k爲3的話,
* 我們的目標是將1,2,3翻轉一下,那麼我們需要一些指針,pre和next分別指向要翻
* 轉的鏈表的前後的位置,然後翻轉後pre的位置更新到如下新的位置:
-1->1->2->3->4->5
| | |
pre cur next
-1->3->2->1->4->5
| | |
cur pre next
* 以此類推,只要cur走過k個節點,那麼next就是cur->next,就可以調用翻轉函數來
* 進行局部翻轉了,注意翻轉之後新的cur和pre的位置都不同了,那麼翻轉之後,cur應
* 該更新爲pre->next,而如果不需要翻轉的話,cur更新爲cur->next
*/
ListNode* reverseKGroup(ListNode* head, int k) {
if (!head || k == 1) return head;
ListNode *header = new ListNode(-1), *pre = header, *cur = head;
header->next = head;
for (int i = 1; cur; ++i) {
if (i % k == 0) {
pre = reverseOneGrop(pre, cur->next);
cur = pre->next;
} else {
cur = cur->next;
}
}
return header->next;
}
/**
* 將改組鏈表翻轉,並返回翻轉後的改組頭結點3,即翻轉前的改組尾節點3
*/
ListNode *reverseOneGrop(ListNode *pre, ListNode *next) {
ListNode *last = pre->next, *cur = last->next;
while (cur != next) { // 先畫圖理解記憶,不行就找規律背下來,簡單
last->next = cur->next;
cur->next = pre->next;
pre->next = cur;
cur = last->next;
}
return last;
}
leetcode-92-反轉鏈表範圍II
反轉從位置 m 到 n 的鏈表。請使用一趟掃描完成反轉。
說明:
1 ≤ m ≤ n ≤ 鏈表長度。示例:
輸入: 1->2->3->4->5->NULL, m = 2, n = 4
輸出: 1->4->3->2->5->NULL
/**
* 方法一:常用方法,構建一個頭節點
* 然後,下面的方法很經典,必須要會!!!
* 首先遍歷到m位置的前一個位置,做爲我們的pre,
* cur = pre->next; 建立一個臨時節點t,指向cur->next 即t = cur->next;
* (注意,使用臨時變量保存某個節點就是爲了首先斷開該節點和前面節點之間的聯繫!!!)
* 然後不斷交換pre之後的節點,這裏要畫圖理解。
* 比如原始鏈表如下,m = 2, n = 4,即pre指向 1,cur = 2,t = 3
* 1 -> 2 -> 3 -> 4 -> 5 -> NULL
* | | |
* pre cur t
* 第一次交換後就是:
* 1 -> 3 -> 2 -> 4 -> 5 -> NULL
* | | |
* pre t cur
* 然後繼續交換,t = cur->next;
* 1 -> 3 -> 2 -> 4 -> 5 -> NULL
* | | |
* pre cur t
* 第二次交換後的結果爲:
* 1 -> 4 -> 3 -> 2 -> 5 -> NULL
* | | |
* pre t cur
* 完成交換
*/
ListNode* reverseBetween(ListNode* head, int m, int n) {
ListNode *header = new ListNode(-1), *pre = header;
header->next = head;
for (int i = 0; i < m - 1; ++i) pre = pre->next;
ListNode *cur = pre->next;
for (int i = m; i < n; ++i) {
ListNode *t = cur->next;
cur->next = t->next;
t->next = pre->next;
pre->next = t;
}
return header->next;
}
leetcode-61-旋轉鏈表
給定一個鏈表,旋轉鏈表,將鏈表每個節點向右移動 k 個位置,其中 k 是非負數。
示例 1:
輸入: 1->2->3->4->5->NULL, k = 2
輸出: 4->5->1->2->3->NULL
解釋:
向右旋轉 1 步: 5->1->2->3->4->NULL
向右旋轉 2 步: 4->5->1->2->3->NULL示例 2:
輸入: 0->1->2->NULL, k = 4
輸出: 2->0->1->NULL
解釋:
向右旋轉 1 步: 2->0->1->NULL
向右旋轉 2 步: 1->2->0->NULL
向右旋轉 3 步: 0->1->2->NULL
向右旋轉 4 步: 2->0->1->NULL
/**
* 方法一:使用快慢指針,快指針先走k步,然後兩個指針一起走,當快指針到末尾時,
* 慢指針的下一個位置是新的順序的頭結點,這樣就可以旋轉鏈表了。但再這之前需要處理
* 一下k,可能k的值遠遠大於鏈表的長度,我們需要首先遍歷一下鏈表,得到鏈表的長度len,
* 然後,用 k %= len,這樣 k 肯定小於鏈表長度了,就可以按照上面的方法了。
*/
ListNode* rotateRight(ListNode* head, int k) {
if (!head) return NULL;
int n = 0;
ListNode *cur = head;
while (cur) {
n++;
cur = cur->next;
}
k %= n;
ListNode *fast = head, *slow = head;
for (int i = 0; i < k; ++i) {
if (fast) fast = fast->next;
}
if (!fast) return head;
while (fast->next) {
fast = fast->next;
slow = slow->next;
}
fast->next = head;
fast = slow->next;
slow->next = NULL;
return fast;
}
/**
* 方法二:和上面方法類似,不用快慢指針,只用一個指針,原理是先遍歷整個鏈表獲得鏈表
* 長度n,然後此時把鏈表頭和尾鏈接起來,在往後走n - k % n個節點就到達新鏈表的頭結點
* 前一個點,這時斷開鏈表即可。
*/
ListNode* rotateRight(ListNode* head, int k) {
if (!head) return NULL;
int n = 1;
ListNode *cur = head;
while (cur->next) {
++n;
cur = cur->next;
}
cur->next = head;
int stride = n - k % n;
for (int i = 0; i < stride; ++i) {
cur = cur->next;
}
ListNode *new_head = cur->next;
cur->next = NULL;
return new_head;
}
leetcode-234-迴文鏈表
請判斷一個鏈表是否爲迴文鏈表。
示例 1:
輸入: 1->2
輸出: false示例 2:
輸入: 1->2->2->1
輸出: true
/**
* 方法一:使用快慢指針以及棧
* 使用快慢指針找鏈表的中點。使用fast和slow兩個指針,每次快指針走兩步,
* 慢指針走一步,等快指針走完時,慢指針的位置就是中點。我們還需要用棧,每次慢指針
* 走一步,都把值存入棧中,等到達中點時,鏈表的前半段都存入棧中了,由於棧的後進先
* 出的性質,就可以和後半段鏈表按照迴文對應的順序比較了。
*/
bool isPalindrome(ListNode* head) {
if (!head || !head->next) return true;
stack<int> stk;
ListNode *fast = head, *slow = head;
stk.push(head->val);
while (fast->next && fast->next->next) {
fast = fast->next->next;
slow = slow->next;
stk.push(slow->val);
}
if (fast->next == NULL) { // 即此時鏈表長爲偶數
stk.pop();
}
slow = slow->next;
while (!stk.empty()) {
if (stk.top() == slow->val) {
slow = slow->next;
stk.pop();
} else {
return false;
}
}
return true;
}
/**
* 方法二:使用快慢指針,不使用棧
* 使用快慢指針找到中點後,將後半部分鏈表翻轉,然後對應比較即可
*/
bool isPalindromeII(ListNode* head) {
if (!head || !head->next) return true;
ListNode *fast = head, *slow = head;
while (fast->next && fast->next->next) {
fast = fast->next->next;
slow = slow->next;
}
ListNode *cur = slow->next, *pre = head;
while (cur->next) {
ListNode *t = cur->next;
cur->next = t->next;
t->next = slow->next;
slow->next = t;
}
slow = slow->next;
while (slow) {
if (slow->val == pre->val) {
slow = slow->next;
pre = pre->next;
} else {
return false;
}
}
return true;
}
leetcode-21-和並兩個排序的鏈表
輸入兩個單調遞增的鏈表,輸出兩個鏈表合成後的鏈表,當然我們需要合成後的鏈表滿足單調不減規則。
/**
* 方法一:迭代法
*/
ListNode* mergeTwoLists(ListNode* l1, ListNode* l2) {
ListNode *C, *s, *r;
C = new ListNode(-1);
r = C;
while (l1 && l2) {
if (l1->val <= l2->val) {
s = new ListNode(l1->val);
r->next = s;
r = s;
l1 = l1->next;
} else {
s = new ListNode(l2->val);
r->next = s;
r = s;
l2 = l2->next;
}
}
while (l1) {
s = new ListNode(l1->val);
r->next = s;
r = s;
l1 = l1->next;
}
while (l2) {
s = new ListNode(l2->val);
r->next = s;
r = s;
l2 = l2->next;
}
return C->next;
}
/**
* 方法二:遞歸方法
*/
ListNode* mergeTwoLists(ListNode* l1, ListNode* l2) {
if (l1 == NULL) return l2; // 注意這裏的返回
if (l2 == NULL) return l1;
ListNode *C = NULL;
if (l1->val <= l2->val) {
C = l1;
C->next = mergeTwoLists(l1->next, l2);
} else {
C = l2;
C->next = mergeTwoLists(l1, l2->next);
}
return C;
}
leetcode-83-合併K個排序鏈表
/**
* 方法二:採用分治思想
* 簡單來說就是不停的對半劃分,比如k個鏈表先劃分爲合併兩個k/2個鏈表的任務,
* 再不停的往下劃分,直到劃分成只有一個或兩個鏈表的任務,開始合併。舉個例子
* 來說比如合併6個鏈表,那麼按照分治法,我們首先分別合併0和3,1和4,2和5。
* 這樣下一次只需合併3個鏈表,我們再合併1和3,最後和2合併就可以了。代碼中的
* k是通過 (n+1)/2 計算的,這裏爲啥要加1呢,這是爲了當n爲奇數的時候,k能始
* 終從後半段開始,比如當n=5時,那麼此時k=3,則0和3合併,1和4合併,最中間
* 的2空出來。當n是偶數的時候,加1也不會有影響,比如當n=4時,此時k=2,那麼
* 0和2合併,1和3合併,完美解決問題。
*/
ListNode* mergeKLists(vector<ListNode*>& lists) {
if (lists.empty()) return NULL;
int n = lists.size();
while (n > 1) {
int k = (n + 1) / 2;
for (int i = 0; i < n / 2; i++) {
lists[i] = mergeTwoLists(lists[i], lists[i + k]);
}
n = k;
}
return lists[0];
}
ListNode *mergeTwoLists(ListNode* A, ListNode *B) {
ListNode *C = new ListNode(-1);
ListNode *p = C; // 尾指針,指向鏈表的最後一個節點
while (A && B) {
if (A->val < B->val) {
p->next = A;
p = A;
A = A->next;
} else {
p->next = B;
p = B;
B = B->next;
}
}
if (A) p->next = A;
if (B) p->next = B;
return C->next;
}
/**
* 方法二:使用最小堆(不熟,要多看幾遍)
* 我們首先把k個鏈表的首元素都加入最小堆中,它們會自動排好序。然後我們
* 每次取出最小的那個元素加入我們最終結果的鏈表中,然後把取出元素的下一
* 個元素再加入堆中,下次仍從堆中取出最小的元素做相同的操作,以此類推,
* 直到堆中沒有元素了,此時k個鏈表也合併爲了一個鏈表,返回首節點即可
*/
ListNode* mergeKLists(vector<ListNode*>& lists) {
auto cmp = [](ListNode*& a, ListNode*& b) {
return a->val > b->val;
};
priority_queue<ListNode*, vector<ListNode*>, decltype(cmp) > q(cmp);
for (auto node : lists) {
if (node) q.push(node);
}
ListNode *dummy = new ListNode(-1), *cur = dummy;
while (!q.empty()) {
auto t = q.top();
q.pop();
cur->next = t;
cur = cur->next;
if (cur->next)
q.push(cur->next);
}
return dummy->next;
}
leetcode-141-環形鏈表[offer23]
給定一個鏈表,判斷鏈表是否有環
/**
* 方法一:使用快慢指針
*/
bool hasCycle(ListNode *head) {
if (!head || head->next == NULL) return false;
ListNode *fast = head, *slow = head;
while (fast && fast->next) {
fast = fast->next->next;
slow = slow->next;
if (fast == slow)
return true;
}
return false;
}
leetcode-142-環的入口節點[offer23]
給一個鏈表,若其中包含環,請找出該鏈表的環的入口結點,否則,輸出null。
/**
* 方法一:使用快慢指針
*/
ListNode *detectCycle(ListNode *head) {
ListNode *fast = head, *slow = head;
while (fast && fast->next) {
fast = fast->next->next;
slow = slow->next;
if (slow == fast) break;
}
if (!fast || !fast->next) return NULL; // 遍歷到了NULL,說明不存在環
// 當fast 和 slow相遇時,此時fast已近比slow多走了 x = n即環的長度,
// 此時slow再從head開始走,當fast和slow相遇時,即是環的入口,即倒數第n個節點
slow = head;
while (fast != slow) {
fast = fast->next;
slow = slow->next;
}
return slow;
}
leetcode-160-兩個鏈表的第一個公共節點[offer-52]
/**
* 方法一:先計算連個鏈表的長度,然後計算兩個鏈表差多少lenDis,讓長的鏈表先走lenDif,然後短的
* 鏈表再開始出發,然後當他們第一次相遇的時候,就是他們的第一個公共節點了。
*/
ListNode *getIntersectionNode(ListNode *headA, ListNode *headB) {
int lenA = 0, lenB = 0, lenDis;
ListNode *p, *pLong, *pShort;
p = headA;
while (p) {
lenA++;
p = p->next;
}
p = headB;
while (p) {
lenB++;
p = p->next;
}
lenDis = lenA - lenB;
pLong = headA;
pShort = headB;
if (lenDis < 0) {
pLong = headB;
pShort = headA;
lenDis = -lenDis;
}
for (int i = 0; i < lenDis; i++) {
pLong = pLong->next;
}
while (pShort != pLong) {
pLong = pLong->next;
pShort = pShort->next;
if (!pLong || !pShort) // 有一個遍歷到了NULL末尾,則說明他們沒有相交節點
return NULL;
}
return pShort;
}
leetcode-86-分隔鏈表
給定一個鏈表和一個特定值 x,對鏈表進行分隔,使得所有小於 x 的節點都在大於或等於 x 的節點之前。
你應當保留兩個分區中每個節點的初始相對位置。
示例:
輸入: head = 1->4->3->2->5->2, x = 3
輸出: 1->2->2->4->3->5
/**
* 方法一:首先找到第一個大於或等於給定值的節點,用題目中給的例子來說就是先找到4,
* 然後再找小於3的值,每找到一個就將其取出置於4之前即可.
* (其中的交換鏈表節點是很常用的方法)
*/
ListNode* partition(ListNode* head, int x) {
ListNode *header = new ListNode(-1);
header->next = head;
ListNode *pre = header, *cur = head;
while (pre->next && pre->next->val < x) {
pre = pre->next;
}
cur = pre;
while (cur->next) {
if (cur->next->val < x) {
ListNode *t = cur->next;
cur->next = t->next;
t->next = pre->next;
pre->next = t;
pre = pre->next;
} else {
cur = cur->next;
}
}
return header->next;
}
/**
* 方法二:將所有小魚給定值的節點取出,組成一個新的鏈表,此時原鏈表中
* 剩餘的節點的值都大於或等於給定值,只要將只要將原鏈表直接接在新鏈表後即可
*/
ListNode* partition(ListNode* head, int x) {
if (head == NULL) return head;
ListNode *header_A = new ListNode(-1);
ListNode *header_B = new ListNode(-1);
header_A->next = head;
ListNode *cur = header_A, *p = header_B;
while (cur->next) {
if (cur->next->val < x) {
p->next = cur->next;
p = p->next;
cur->next = cur->next->next;
p->next = NULL;
} else {
cur = cur->next;
}
}
p->next = header_A->next;
return header_B->next;
}