1. 程式人生 > >面試常見連結串列題目總結

面試常見連結串列題目總結

160. 相交連結串列

編寫一個程式,找到兩個單鏈表相交的起始節點。

如下面的兩個連結串列:

在節點 c1 開始相交。

示例 1:

輸入:intersectVal = 8, listA = [4,1,8,4,5], listB = [5,0,1,8,4,5], skipA = 2, skipB = 3
輸出:Reference of the node with value = 8
輸入解釋:相交節點的值為 8 (注意,如果兩個列表相交則不能為 0)。從各自的表頭開始算起,連結串列 A 為 [4,1,8,4,5],連結串列 B 為 [5,0,1,8,4,5]。在 A 中,相交節點前有 2 個節點;在 B 中,相交節點前有 3 個節點。

示例 2:

輸入:intersectVal = 2, listA = [0,9,1,2,4], listB = [3,2,4], skipA = 3, skipB = 1
輸出:Reference of the node with value = 2
輸入解釋:相交節點的值為 2 (注意,如果兩個列表相交則不能為 0)。從各自的表頭開始算起,連結串列 A 為 [0,9,1,2,4],連結串列 B 為 [3,2,4]。在 A 中,相交節點前有 3 個節點;在 B 中,相交節點前有 1 個節點。

示例 3:

輸入:intersectVal = 0, listA = [2,6,4], listB = [1,5], skipA = 3, skipB = 2
輸出:null
輸入解釋:從各自的表頭開始算起,連結串列 A 為 [2,6,4],連結串列 B 為 [1,5]。由於這兩個連結串列不相交,所以 intersectVal 必須為 0,而 skipA 和 skipB 可以是任意值。
解釋:這兩個連結串列不相交,因此返回 null。

注意:

  • 如果兩個連結串列沒有交點,返回 null.
  • 在返回結果後,兩個連結串列仍須保持原有的結構。
  • 可假定整個連結串列結構中沒有迴圈。
  • 程式儘量滿足 O(n) 時間複雜度,且僅用 O(1) 記憶體。

設定快慢指標

public ListNode getIntersectionNode (ListNode headA, ListNode headB) {
    if (headA == null || headB == null) return null;
    ListNode p1 = headA;
    ListNode p2 = headB;
    while (p1 != p2) {
        if (p1 == null) p1 = headB;
        else p1 = p1.next;
        if (p2 == null) p2 = headA;
        else p2 = p2.next;
    }
    return p1;
}

206. 反轉連結串列

反轉一個單鏈表。

示例:

輸入: 1->2->3->4->5->NULL
輸出: 5->4->3->2->1->NULL

進階:
你可以迭代或遞迴地反轉連結串列。你能否用兩種方法解決這道題?


遞迴法:

public ListNode reverseList(ListNode head) {
    if(head == null || head.next == null){
        return head;
    }
    ListNode rest = head.next;
    ListNode newHead = reverseList(rest);
    rest.next = head;
    head.next = null;
    return newHead;
}

迭代法:

public ListNode reverseList(ListNode head) {
    ListNode prev = null;
    ListNode cur = head;
    while(cur != null){
        ListNode next = cur.next;
        cur.next = prev;
        prev = cur;
        cur = next;
    }
    return prev;
}

21. 合併兩個有序連結串列

將兩個有序連結串列合併為一個新的有序連結串列並返回。新連結串列是通過拼接給定的兩個連結串列的所有節點組成的。

示例:

輸入:1->2->4, 1->3->4
輸出:1->1->2->3->4->4

雙指標思想

public ListNode mergeTwoLists(ListNode l1, ListNode l2) {
    if(l1 == null) return l2;
    if(l2 == null) return l1;

    if(l1.val < l2.val){
        l1.next = mergeTwoLists(l1.next,l2);
        return l1;
    }else{
        l2.next = mergeTwoLists(l1,l2.next);
        return l2;
    }
}

83. 刪除排序連結串列中的重複元素

給定一個排序連結串列,刪除所有重複的元素,使得每個元素只出現一次。

示例 1:

輸入: 1->1->2
輸出: 1->2

示例 2:

輸入: 1->1->2->3->3
輸出: 1->2->3

一次遍歷,注意邊界條件。

public ListNode deleteDuplicates(ListNode head) {
    ListNode cur = head;
    while(cur != null && cur.next != null){
        if(cur.val == cur.next.val)
            cur.next = cur.next.next;
        else
            cur = cur.next;
    }
    return head;
}

19. 刪除連結串列的倒數第N個節點

給定一個連結串列,刪除連結串列的倒數第 n 個節點,並且返回連結串列的頭結點。

示例:

給定一個連結串列: 1->2->3->4->5, 和 n = 2.

當刪除了倒數第二個節點後,連結串列變為 1->2->3->5.

說明:

給定的 n 保證是有效的。

進階:

你能嘗試使用一趟掃描實現嗎?


設定啞節點1,讓它走 n+1 步,再設定啞節點2,然後啞節點1和啞節點2一起移動,直到啞節點1走完連結串列,此時啞節點1和啞節點2之間正好隔著 n 個節點,再通過啞節點2刪除倒數第 n 個節點。

public ListNode removeNthFromEnd(ListNode head, int n) {
    ListNode dummy = new ListNode(0);
    dummy.next = head;

    ListNode first = dummy;

    for (int i = 1; i <= n + 1;i++){
        first = first.next;
    }

    ListNode second = dummy;
    while(first != null){
        first = first.next;
        second = second.next;
    }

    second.next = second.next.next;

    return dummy.next;
}

234. 迴文連結串列

請判斷一個連結串列是否為迴文連結串列。

示例 1:

輸入: 1->2
輸出: false

示例 2:

輸入: 1->2->2->1
輸出: true

進階:
你能否用 O(n) 時間複雜度和 O(1) 空間複雜度解決此題?


設定快慢指標,移動速度分別是2和1。當快指標到達連結串列尾部的時候,慢指標就在正中間(奇數個節點的情況下)或者正中間的左邊(偶數個節點的情況下),再將慢指標向後移動一位,反轉以慢指標為頭的連結串列,再逐個節點對比是否相等。

public boolean isPalindrome (ListNode head) {
    if (head == null || head.next == null) return true;
    ListNode slow = head;
    ListNode fast = head.next;
    while (fast != null && fast.next != null) {

        slow = slow.next;
        fast = fast.next.next;
    }

    slow = slow.next;
    slow = reverse(slow);

    while (slow != null) {
        if (head.val == slow.val) {
            head = head.next;
            slow = slow.next;
        } else
            return false;
    }
    return true;

}

private ListNode reverse (ListNode head) {
    ListNode newHead = null;
    while (head != null) {
        ListNode nextNode = head.next;
        head.next = newHead;
        newHead = head;
        head = nextNode;
    }
    return newHead;
}

24. 兩兩交換連結串列中的節點

給定一個連結串列,兩兩交換其中相鄰的節點,並返回交換後的連結串列。

你不能只是單純的改變節點內部的值,而是需要實際的進行節點交換。

示例:

給定 1->2->3->4, 你應該返回 2->1->4->3.

設定啞節點,注意迴圈條件,指標移動的速度是2(因為需要兩兩交換節點)。

public ListNode swapPairs (ListNode head) {
    ListNode dummy = new ListNode(-1);
    dummy.next = head;
    ListNode pre = dummy;
    while (pre.next != null && pre.next.next != null) {
        ListNode l1 = pre.next;
        ListNode l2 = pre.next.next;
        l1.next = l2.next;
        l2.next = l1;
        pre.next = l2;
        pre = l1;
    }
    return dummy.next;
}

445. 兩數相加 II

給定兩個非空連結串列來代表兩個非負整數。數字最高位位於連結串列開始位置。它們的每個節點只儲存單個數字。將這兩數相加會返回一個新的連結串列。

你可以假設除了數字 0 之外,這兩個數字都不會以零開頭。

進階:

如果輸入連結串列不能修改該如何處理?換句話說,你不能對列表中的節點進行翻轉。

示例:

輸入: (7 -> 2 -> 4 -> 3) + (5 -> 6 -> 4)
輸出: 7 -> 8 -> 0 -> 7

既然不能改變連結串列的結構(翻轉連結串列),那就用一個棧來儲存連結串列中的值,可以做到逆向輸出。

相加部分的程式碼邏輯就按常規思路寫。

public ListNode addTwoNumbers (ListNode l1, ListNode l2) {
    Stack<Integer> l1Stack = listNodetoStack(l1);
    Stack<Integer> l2Stack = listNodetoStack(l2);
    int carry = 0;
    ListNode head = new ListNode(-1);
    while (!l1Stack.isEmpty() || !l2Stack.isEmpty() || carry != 0) {
        int x = l1Stack.isEmpty() ? 0 : l1Stack.pop();
        int y = l2Stack.isEmpty() ? 0 : l2Stack.pop();
        int sum = x + y + carry;
        ListNode node = new ListNode(sum % 10);
        carry = sum / 10;
        node.next = head.next;
        head.next = node;
    }
    return head.next;
}

private Stack<Integer> listNodetoStack (ListNode head) {
    Stack<Integer> stack = new Stack<>();
    while (head != null) {
        stack.push(head.val);
        head = head.next;
    }
    return stack;
}

725. 分隔連結串列

給定一個頭結點為 root 的連結串列, 編寫一個函式以將連結串列分隔為 k 個連續的部分。

每部分的長度應該儘可能的相等: 任意兩部分的長度差距不能超過 1,也就是說可能有些部分為 null。

這k個部分應該按照在連結串列中出現的順序進行輸出,並且排在前面的部分的長度應該大於或等於後面的長度。

返回一個符合上述規則的連結串列的列表。

舉例: 1->2->3->4, k = 5 // 5 結果 [ [1], [2], [3], [4], null ]

示例 1:

輸入: 
root = [1, 2, 3], k = 5
輸出: [[1],[2],[3],[],[]]
解釋:
輸入輸出各部分都應該是連結串列,而不是陣列。
例如, 輸入的結點 root 的 val= 1, root.next.val = 2, \root.next.next.val = 3, 且 root.next.next.next = null。
第一個輸出 output[0] 是 output[0].val = 1, output[0].next = null。
最後一個元素 output[4] 為 null, 它代表了最後一個部分為空連結串列。

示例 2:

輸入: 
root = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10], k = 3
輸出: [[1, 2, 3, 4], [5, 6, 7], [8, 9, 10]]
解釋:
輸入被分成了幾個連續的部分,並且每部分的長度相差不超過1.前面部分的長度大於等於後面部分的長度。

提示:

  • root 的長度範圍: [0, 1000].
  • 輸入的每個節點的大小範圍:[0, 999].
  • k 的取值範圍: [1, 50].

先統計出連結串列長度,除以 k, 求商和餘數,其中:

  • 餘數代表最後結果中有多少個長連結串列
  • 商代表每個短連結串列的長度(結果集中後部的連結串列)
  • 長連結串列比短連結串列多一個節點
public ListNode[] splitListToParts (ListNode root, int k) {
    ListNode cur = root;
    int len = 0;
    while (cur != null) {
        cur = cur.next;
        len++;
    }

    int mod = len % k;
    int size = len / k;
    cur = root;
    ListNode[] ans = new ListNode[k];
    for (int i = 0; cur != null && i < k; i++) {
        ans[i] = cur;
        int curSize = size + (mod-- > 0 ? 1 : 0);
        for (int j = 0; j < curSize - 1; j++) {
            cur = cur.next;
        }
        ListNode next = cur.next;
        cur.next = null;
        cur = next;
    }
    return ans;
}

328. 奇偶連結串列

給定一個單鏈表,把所有的奇數節點和偶數節點分別排在一起。請注意,這裡的奇數節點和偶數節點指的是節點編號的奇偶性,而不是節點的值的奇偶性。

請嘗試使用原地演算法完成。你的演算法的空間複雜度應為 O(1),時間複雜度應為 O(nodes),nodes 為節點總數。

示例 1:

輸入: 1->2->3->4->5->NULL
輸出: 1->3->5->2->4->NULL

示例 2:

輸入: 2->1->3->5->6->4->7->NULL 
輸出: 2->3->6->7->1->5->4->NULL

說明:

  • 應當保持奇數節點和偶數節點的相對順序。
  • 連結串列的第一個節點視為奇數節點,第二個節點視為偶數節點,以此類推。

設定三個指標,其中奇指標和偶指標是很自然能想到的,evenHead起輔助作用,用於將奇連結串列和偶連結串列結合起來。

public ListNode oddEvenList (ListNode head) {
    ListNode odd = head;
    ListNode even = head.next;
    ListNode evenHead = even;
    while (even != null && even.next != null) {
        odd.next = even.next;
        odd = odd.next;
        even.next = odd.next;
        even = even.next;
    }
    odd.next = evenHead;
    return head;
}