1. 程式人生 > >【LeetCode題解】連結串列Linked List

【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/ 題目描述: 給定一個連結串列,判斷連結串列中是否有環。 進階: 你能否不使用額外空間解決此