【LeetCode題解】連結串列Linked List
1. 連結串列
陣列是一種順序表,index與value之間是一種順序對映,以\(O(1)\)的複雜度訪問資料元素。但是,若要在表的中間部分插入(或刪除)某一個元素時,需要將後續的資料元素進行移動,複雜度大概為\(O(n)\)。連結串列(Linked List)是一種鏈式表,克服了上述的缺點,插入和刪除操作均不會引起元素的移動;資料結構定義如下:
public class ListNode {
String val;
ListNode next;
// ...
}
常見的連結串列有單向連結串列(也稱之為chain),只有next指標指向後繼結點,而沒有previous指標指向前驅結點。
連結串列的插入與刪除操作只涉及到next指標的更新,而不會移動資料元素。比如,要在FAT與HAT插入結點GAT,如圖所示:
Java實現:
ListNode fat, gat;
gat.next = fat.next;
fat.next = gat;
又比如,要刪除結點GAT,如圖所示:
Java實現:
fat.next = fat.next.next;
從上述程式碼中,可以看出:因為沒有前驅指標,一般在做插入和刪除操作時,我們需要通過操作前驅結點的next指標開始。
2. 題解
LeetCode題目 | 歸類 |
---|---|
237. Delete Node in a Linked List | 刪除 |
203. Remove Linked List Elements | |
19. Remove Nth Node From End of List | |
83. Remove Duplicates from Sorted List | |
82. Remove Duplicates from Sorted List II | |
24. Swap Nodes in Pairs | 移動 |
206. Reverse Linked List | |
92. Reverse Linked List II | |
61. Rotate List | |
86. Partition List | |
328. Odd Even Linked List | |
21. Merge Two Sorted Lists | 合併 |
23. Merge k Sorted Lists | |
141. Linked List Cycle | 有環 |
142. Linked List Cycle II | 有環 |
234. Palindrome Linked List | |
143. Reorder List | |
160. Intersection of Two Linked Lists | |
2. Add Two Numbers | |
445. Add Two Numbers II |
237. Delete Node in a Linked List
刪除指定結點。由於是單向連結串列,因此只需更新待刪除節點即可。
public void deleteNode(ListNode node) {
node.val = node.next.val;
node.next = node.next.next;
}
203. Remove Linked List Elements
刪除指定值的結點。用兩個指標實現,curr用於遍歷,prev用於暫存前驅結點。
public ListNode removeElements(ListNode head, int val) {
ListNode fakeHead = new ListNode(Integer.MIN_VALUE);
fakeHead.next = head;
for (ListNode curr = head, prev = fakeHead; curr != null; curr = curr.next) {
if (curr.val == val) { // remove
prev.next = curr.next;
} else { // traverse
prev = prev.next;
}
}
return fakeHead.next;
}
19. Remove Nth Node From End of List
刪除連結串列的倒數第n個結點。思路:因為單向連結串列是沒有前驅指標的,所以應找到倒數第n+1個結點;n有可能等於連結串列的長度,故先new一個head的前驅結點fakeHead。用兩個指標slow、fast從fakeHead開始,先移動fast n+1步,使得其距離slow為n+1;然後,兩個指標同步移動,當fast走到null時,slow即處於倒數第n+1個結點,刪除slow的next結點即可。
public ListNode removeNthFromEnd(ListNode head, int n) {
ListNode fakeHead = new ListNode(Integer.MIN_VALUE);
fakeHead.next = head;
ListNode slow = fakeHead, fast = fakeHead;
for (int i = 1; i <= n + 1; i++) {
fast = fast.next;
}
while(fast != null) {
fast = fast.next;
slow = slow.next;
}
slow.next = slow.next.next; // the n-th node from end is `slow.next`
return fakeHead.next;
}
83. Remove Duplicates from Sorted List
刪除有序連結串列中的重複元素。處理思路有上一問題類似,不同的是判斷刪除的條件。
public ListNode deleteDuplicates(ListNode head) {
ListNode fakeHead = new ListNode(Integer.MIN_VALUE);
fakeHead.next = head;
for (ListNode curr = head, prev = fakeHead; curr != null && curr.next != null; curr = curr.next) {
if (curr.val == curr.next.val) { // remove
prev.next = curr.next;
} else {
prev = prev.next;
}
}
return fakeHead.next;
}
82. Remove Duplicates from Sorted List II
上一問題的升級版,刪除所有重複元素結點。在裡層增加一個while迴圈,跳過重複元素結點。
public ListNode deleteDuplicates(ListNode head) {
ListNode fakeHead = new ListNode(Integer.MIN_VALUE);
fakeHead.next = head;
for (ListNode curr = head, prev = fakeHead; curr != null; curr = curr.next) {
while (curr.next != null && curr.val == curr.next.val) { // find the last duplicate
curr = curr.next;
}
if (prev.next == curr) prev = prev.next;
else prev.next = curr.next;
}
return fakeHead.next;
}
24. Swap Nodes in Pairs
連結串列中兩兩交換。按step = 2 遍歷連結串列並交換;值得注意的是在更新next指標是有次序的。
public ListNode swapPairs(ListNode head) {
ListNode fakeHead = new ListNode(Integer.MIN_VALUE);
fakeHead.next = head;
for (ListNode prev = fakeHead, p = head; p != null && p.next != null; ) {
ListNode temp = p.next.next;
prev.next = p.next; // update next pointer
p.next.next = p;
p.next = temp;
prev = p;
p = temp;
}
return fakeHead.next;
}
206. Reverse Linked List
逆序整個連結串列。逆序操作可以看作:依次遍歷連結串列,將當前結點插入到連結串列頭。
public ListNode reverseList(ListNode head) {
ListNode newHead = null;
for (ListNode curr = head; curr != null; ) {
ListNode temp = curr.next;
curr.next = newHead; // insert to the head of list
newHead = curr;
curr = temp;
}
return newHead;
}
92. Reverse Linked List II
上一問題的升級,指定區間[m, n]內做逆序;相當於把該區間的連結串列逆序後,再拼接到原連結串列中。
public ListNode reverseBetween(ListNode head, int m, int n) {
ListNode newHead = null, curr = head, firstHead = null, firstHeadPrev = null;
for (int i = 1; curr != null && i <= n; i++) {
if (i < m - 1) {
curr = curr.next;
continue;
}
if (i == m - 1) {
firstHeadPrev = curr; // mark first head previous node
curr = curr.next;
} else {
if (i == m) firstHead = curr; // mark first head node
ListNode temp = curr.next;
curr.next = newHead;
newHead = curr;
curr = temp;
}
}
firstHead.next = curr;
if (firstHeadPrev != null) firstHeadPrev.next = newHead;
if (m == 1) return newHead;
return head;
}
61. Rotate List
指定分隔位置,將連結串列的左右部分互換。只需修改左右部分的最後節點的next指標即可,有一些special case需要注意,諸如:連結串列為空,k為連結串列長度的倍數等。為了得到連結串列的長度,需要做一次pass。故總共需要遍歷連結串列兩次。
public ListNode rotateRight(ListNode head, int k) {
if (head == null || k == 0) return head;
int n = 0, i;
ListNode curr, leftLast = head, rightFist, rightLast = head;
for (curr = head; curr != null; curr = curr.next) { // get the length of list
n++;
}
k %= n; // k maybe larger than n
if (k == 0) return head;
for (i = 1, curr = head; i <= n; i++, curr = curr.next) { // mark the split node
if (i == n - k) leftLast = curr;
if (i == n) rightLast = curr;
}
rightFist = leftLast.next;
leftLast.next = null;
rightLast.next = head;
return rightFist;
}
86. Partition List
類似於quick sort的partition,不同的是要保持連結串列的原順序。思路:用兩個連結串列,一個保留小於指定數x,一個保留不大於指定數x;最後拼接到一起即可。
public ListNode partition(ListNode head, int x) {
if (head == null) return null;
ListNode lt = new ListNode(-1), gte = new ListNode(-2); // less than, greater than and equal
ListNode p, p1, p2;
for (p = head, p1 = lt, p2 = gte; p != null; p = p.next) {
if (p.val < x) {
p1.next = p;
p1 = p1.next;
} else {
p2.next = p;
p2 = p2.next;
}
}
p2.next = null;
p1.next = gte.next;
return lt.next;
}
328. Odd Even Linked List
連結串列分成兩部分:偶數編號與奇數編號,將偶數連結串列拼接到奇數連結串列的後面。
public ListNode oddEvenList(ListNode head) {
if (head == null || head.next == null) return head;
ListNode odd = head, even = head.next, evenHead = head.next;
while (odd.next != null && odd.next.next != null) {
odd.next = odd.next.next;
odd = odd.next;
if (even != null && even.next != null) {
even.next = even.next.next;
even = even.next;
}
}
odd.next = evenHead; // splice even next to odd
return head;
}
21. Merge Two Sorted Lists
合併兩個有序連結串列。比較簡單,分情況比較。
public ListNode mergeTwoLists(ListNode l1, ListNode l2) {
ListNode head = new ListNode(-1), p, p1, p2;
for (p = head, p1 = l1, p2 = l2; p1 != null || p2 != null; p = p.next) {
if (p1 != null) {
if (p2 != null && p1.val > p2.val) {
p.next = p2;
p2 = p2.next;
} else {
p.next = p1;
p1 = p1.next;
}
} else {
p.next = p2;
p2 = p2.next;
}
}
return head.next;
}
23. Merge k Sorted Lists
合併k個有序連結串列。思路:藉助於堆,堆的大小為k,先將每個連結串列的首結點入堆,堆頂元素即為最小值,堆頂出堆後將next入堆;依此往復,即可得到整個有序連結串列。
public ListNode mergeKLists(ListNode[] lists) {
if (lists.length == 0) return null;
PriorityQueue<ListNode> minHeap = new PriorityQueue<>(lists.length, new Comparator<ListNode>() {
@Override
public int compare(ListNode o1, ListNode o2) {
return o1.val - o2.val;
}
});
// initialization
for (ListNode node : lists) {
if (node != null)
minHeap.offer(node);
}
ListNode head = new ListNode(-1);
for (ListNode p = head; !minHeap.isEmpty(); ) {
ListNode top = minHeap.poll();
p.next = top;
p = p.next;
if (top.next != null)
minHeap.offer(top.next);
}
return head.next;
}
141. Linked List Cycle
判斷連結串列是否有環。用兩個指標,一個快指標一個慢指標,一個每次移動兩步,一個每次移動一步;最後兩者相遇,即說明有環。
public boolean hasCycle(ListNode head) {
if (head == null) return false;
for (ListNode slow = head, fast = head; fast.next != null && fast.next.next != null; ) {
slow = slow.next;
fast = fast.next.next;
if (slow == fast) return true;
}
return false;
}
142. Linked List Cycle II
找出連結串列中環的起始節點\(s\)。解決思路:用兩個指標——fast、slow,先判斷是否環;兩者第一次相遇的節點與\(s\)的距離 == 連結串列起始節點與\(s\)的距離(有興趣可以證明一下)。
public ListNode detectCycle(ListNode head) {
if (head == null || head.next == null) return null;
ListNode slow = head, fast = head;
boolean isCycled = false;
while (slow != null && fast != null && fast.next != null) { // first meeting
slow = slow.next;
fast = fast.next.next;
if (slow == fast) {
isCycled = true;
break;
}
}
if (!isCycled) return null;
for (fast = head; slow != fast; ) { // find the cycle start node
slow = slow.next;
fast = fast.next;
}
return slow;
}
234. Palindrome Linked List
判斷連結串列\(L\)是否中心對稱。中心對稱的充分必要條件:對於任意的 i <= n/2 其中n為連結串列長度,有L[i] = L[n+1-i]成立。因此先找出middle結點(在距離首結點n/2處),然後逆序右半部分連結串列,與左半部分連結串列的結點一一比較,即可得到結果。在找出middle結點時也用到了小技巧——快慢兩個指標遍歷連結串列,當fast遍歷完成時,slow即為middle結點(證明分n為奇偶情況);當n為偶數時,middle結點有兩個,此時slow為左middle結點。換句話說,無論n為奇數或偶數,此時的slow為右半部分子連結串列的第一個結點的前驅結點。
public boolean isPalindrome(ListNode head) {
if (head == null) return true;
ListNode slow = head, fast = head, p;
while (fast.next != null && fast.next.next != null) {
slow = slow.next;
fast = fast.next.next;
}
ListNode q = reverseList(slow.next); // the first node of the right half is `slow.next`
for (p = head; q != null; p = p.next, q = q.next) {
if (p.val != q.val) return false;
}
return true;
}
143. Reorder List
對於除去首結點外的連結串列,將右半部分子連結串列從後往前依次插入進左半部分連結串列。解決思路與上類似,找出middle結點,然後依次插入。值得注意:Java的物件傳參是引用型別,需要更新左半部份子連結串列的最後一個結點的next指標,不然則連結串列的結點的無限迴圈導致OOM。
public void reorderList(ListNode head) {
if (head == null || head.next == null) return;
ListNode slow = head.next, fast = head.next;
while (fast.next != null && fast.next.next != null) {
slow = slow.next;
fast = fast.next.next;
}
ListNode p, q = reverseList(slow.next);
slow.next = null; // update the next pointer of the left half's last node
for (p = head; q != null; ) {
ListNode pNext = p.next, qNext = q.next;
p.next = q; // insert qNode into the next of p node
q.next = pNext;
p = pNext;
q = qNext;
}
}
160. Intersection of Two Linked Lists
求兩個連結串列相交的第一個結點\(P\)。假定兩個連結串列的長度分別為m、n,相交的第一個結點\(P\)分別距離兩個連結串列的首結點為a、b,則根據連結串列相交的特性:兩個連結串列的尾節點都是同一個,即m-a = n-b;移項後有m+b = n+a。根據上述性質,在遍歷完第一個連結串列後,再往右b個結點,即到達了結點\(P\)。
public ListNode getIntersectionNode(ListNode headA, ListNode headB) {
if (headA == null || headB == null) return null;
ListNode ptrA = headA, ptrB = headB;
while (ptrA != ptrB) { // in case ptrA == ptrB == null
ptrA = (ptrA != null) ? ptrA.next : headB;
ptrB = (ptrB != null) ? ptrB.next : headA;
}
return ptrA;
}
2. Add Two Numbers
模擬兩個連結串列的加法。開始的時候沒理解清楚題意,被坑了多次WA。連結串列的head表示整數的個位,則應從首端對齊開始做加法。
public ListNode addTwoNumbers(ListNode l1, ListNode l2) {
ListNode head = new ListNode(-1);
boolean carry = false; // mark whether has carry
for (ListNode p = l1, q = l2, r = head; p != null || q != null || carry; r = r.next) {
int pVal = (p == null) ? 0 : p.val;
int qVal = (q == null) ? 0 : q.val;
int sum = carry ? pVal + qVal + 1 : pVal + qVal;
carry = sum >= 10;
r.next = new ListNode(sum % 10);
if (p != null) p = p.next;
if (q != null) q = q.next;
}
return head.next;
}
445. Add Two Numbers II
與上一題不同的是,連結串列的head表示整數的最高位,則應是尾端對齊相加。為了尾端對齊,將採用stack來逆序連結串列,之後相加步驟與上類似;但建立新連結串列應使用頭插法。
public ListNode addTwoNumbers(ListNode l1, ListNode l2) {
ListNode p;
Stack<Integer> s1 = new Stack<>();
Stack<Integer> s2 = new Stack<>();
for (p = l1; p != null; p = p.next) {
s1.push(p.val);
}
for (p = l2; p != null; p = p.next) {
s2.push(p.val);
}
ListNode head = new ListNode(-1);
boolean carry = false; // mark whether has carry
for (ListNode r = null; !s1.isEmpty() || !s2.isEmpty() || carry; ) {
int pVal = (s1.isEmpty()) ? 0 : s1.pop();
int qVal = (s2.isEmpty()) ? 0 : s2.pop();
int sum = carry ? pVal + qVal + 1 : pVal + qVal;
carry = sum >= 10;
ListNode node = new ListNode(sum % 10);
node.next = r;
head.next = node;
r = node;
}
return head.next;
}
相關推薦
【LeetCode題解】連結串列Linked List
1. 連結串列 陣列是一種順序表,index與value之間是一種順序對映,以\(O(1)\)的複雜度訪問資料元素。但是,若要在表的中間部分插入(或刪除)某一個元素時,需要將後續的資料元素進行移動,複雜度大概為\(O(n)\)。連結串列(Linked List)是一種鏈式表,克服了上述的缺點,插入和刪除操作均
【LeetCode】連結串列 linked list(共34題)
p.p1 { margin: 0.0px 0.0px 0.0px 0.0px; font: 12.0px Helvetica } 【2】Add Two Numbers 【19】Remove Nth Node From End of List (2018年10月30日
【資料結構】連結列表 Linked list
連結列表(Linked list)連結列表 是 資料元素的線性集合,但是 並不會按照 線性的順序存取資料。相反的是,每個元素 指向 另一個元素。連結列表是一個由一組代表了線性的節點組成的的資料結構。最簡單的情況下,每個節點 由 資料 和 指向另一個節點的指標 組成。基礎概念連
【LeetCode題解】142_環形連結串列2(Linked-List-Cycle-II)
目錄 描述 解法一:雜湊表 思路 Java 實現 Python 實現 複雜度分析 解法二:雙指標 思路 Java 實現 Python 實現 複雜度分析 描述 給定一個連結串列,返回連結串列開始入
【LeetCode題解】206_反轉連結串列(Reverse-Linked-List)
更多 LeetCode 題解筆記可以訪問我的 github。 文章目錄 描述 解法一:迭代 思路 Java 實現 Python 實現 複雜度分析 解法二:遞迴 思路
【LeetCode題解】61_旋轉連結串列(Rotate-List)
目錄 描述 解法:雙指標 思路 Java 實現 Python 實現 複雜度分析 描述 給定一個連結串列,旋轉連結串列,將連結串列每個節點向右移動 k 個位置,其中 k 是非負數。 示例 1: 輸入: 1->2->3->4-
【LeetCode題解】19_刪除連結串列的倒數第N個節點(Remove-Nth-Node-From-End-of-List)
更多 LeetCode 題解筆記可以訪問我的 github。 文章目錄 描述 解法:雙指標 思路 Java 實現 Python 實現 複雜度分析 描述 給定一個連結串列,
【LeetCode】#141環形連結串列(Linked List Cycle)
【LeetCode】#141環形連結串列(Linked List Cycle) 題目描述 給定一個連結串列,判斷連結串列中是否有環。 為了表示給定連結串列中的環,我們使用整數 pos 來表示連結串列尾連線到連結串列中的位置(索引從 0 開始)。 如果 pos 是 -1,則在該連結串列
【LeetCode題解】24_兩兩交換連結串列中的節點(Swap-Nodes-in-Pairs)
目錄 描述 解法一:迭代 思路 Java 實現 Python 實現 複雜度分析 解法二:遞迴(不滿足空間複雜度要求) 思路 Java 實現 Python 實現 複雜度分析 更多 LeetCo
【LeetCode題解】21_合併兩個有序連結串列
21_合併兩個有序連結串列 文章目錄 21_合併兩個有序連結串列 描述 解法一:迭代 思路 Java 實現 Python 實現 解法二:遞迴 思路 J
【LeetCode題解】237_刪除連結串列中的節點
237_刪除連結串列中的節點 文章目錄 237_刪除連結串列中的節點 描述 解法 思路 Java 實現 Python 實現 描述 請編寫一個函式,使其可以刪除
【LeetCode題解】25_k個一組翻轉連結串列(Reverse-Nodes-in-k-Group)
目錄 描述 解法一:迭代 思路 Java 實現 Python 實現 複雜度分析 解法二:遞迴(不滿足空間複雜度) 思路 Java 實現 Python 實現 複雜度分析 更多 LeetCode
【LeetCode題解】206_反轉連結串列
目錄 206_反轉連結串列 描述 反轉一個單鏈表。 示例: 輸入: 1->2->3->4->5->NULL 輸出: 5->4->3->2->1->NULL 進階: 你可以迭代或遞迴地反轉連結串列。你能否用兩種方法解決這道題? 實現方式一:迭代 思路
【LeetCode題解】141_環形連結串列
目錄 141_環形連結串列 描述 給定一個連結串列,判斷連結串列中是否有環。 進階: 你能否不使用額外空間解決此題? 解法一:雜湊表 思路 判斷一個連結串列是否包含環,可以轉化為判斷是否有一個節點之前已經出現過。非常自然的一個想法就是:遍歷連結串列的每個節點,用一個雜湊表記錄每個節點的引用(或記憶體地址);
【小白學演算法】5.連結串列(linked list)、連結串列的新增
連結串列其實也就是 線性表的鏈式儲存結構,與之前講到的順序儲存結構不同。 我們知道順序儲存結構中的元素地址都是連續的,那麼這就有一個最大的缺點:當做插入跟刪除操作的時候,大量的元素需要移動。 如圖所示,元素在記憶體中的位置是挨著的,當中有元素被刪除,就產生空隙,於是乎後面的元素需要向前挪動去彌補。 ![](
【小白學演算法】5.連結串列(linked list),連結串列的插入、讀取
連結串列其實也就是 線性表的鏈式儲存結構,與之前講到的順序儲存結構不同。 我們知道順序儲存結構中的元素地址都是連續的,那麼這就有一個最大的缺點:當做插入跟刪除操作的時候,大量的元素需要移動。 如圖所示,元素在記憶體中的位置是挨著的,當中有元素被刪除,就產生空隙,於是乎後面的元素需要向前挪動去彌補。 ![](
【LeetCode題解】61_旋轉鏈表(Rotate-List)
__init__ span leetcode 我們 分享 圖片 表示 執行 elf 目錄 描述 解法:雙指針 思路 Java 實現 Python 實現 復雜度分析 描述 給定一個鏈表,旋轉鏈表,將鏈表每個節點向右移動 k 個位置,其中 k 是非負數。 示例 1: 輸入
【LeetCode題解】160_相交鏈表
結果 user 實現 ini tno href 假設 分享圖片 pytho 目錄 160_相交鏈表 描述 解法一:哈希表 思路 Java 實現 Python 實現 解法二:雙指針(推薦) 思路 Java 實現 Python 實現 160_相交鏈表 描述 編寫一個程
【LeetCode題解】347_前K個高頻元素(Top-K-Frequent-Elements)
目錄 描述 解法一:排序演算法(不滿足時間複雜度要求) Java 實現 Python 實現 複雜度分析 解法二:最小堆 思路 Java 實現 Python 實現 複雜度分析 解法三:桶排序(bucket s
Leetcode題解之連結串列(6) 環形連結串列
題目:https://leetcode-cn.com/explore/interview/card/top-interview-questions-easy/6/linked-list/46/ 題目描述: 給定一個連結串列,判斷連結串列中是否有環。 進階: 你能否不使用額外空間解決此