多種方法從尾部移除指定位置的連結串列節點

freephp發表於2024-04-04

連綿的春雨把人困在家鄉,於是我繼續開始刷著演算法題,透過 19. Remove 年th Node From End of List複習了一波連結串列的操作,這道題也是比較典型的連結串列問題,值得分享一下。

題目如下所示:


Given the head of a linked list, remove the nth node from the end of the list and return its head.

 

Example 1:


Input: head = [1,2,3,4,5], n = 2
Output: [1,2,3,5]
Example 2:

Input: head = [1], n = 1
Output: []
Example 3:

Input: head = [1,2], n = 1
Output: [1]

簡單來說,就是給一個單連結串列,然後給一個數字n,讓我們把尾部第n個節點刪除掉,然後返回這個連結串列的頭部。具體例子如上面描述所說,這個題目寫得還是比較清晰的。

如果說是給定數字n,是從連結串列頭部開始算位置,那麼這道題就非常簡單,直接迴圈遍歷到第n個節點,然後把第n個節點之前的節點的next改為第n+1個節點就行了。
但現在卻是從尾部開始數n個節點,所以需要做一些處理。

最直接的思路是,把從尾部的問題變成從頭部遍歷的問題,從尾部開始往前第n個節點,就是從頭部往後第(連結串列長度 - n)個節點被移除。

於是演算法思路如下:

    1. 透過遍歷獲得連結串列長度L1。
    1. 計算出從頭部開始數是第(L1 - n)個節點。
    1. 用一個指標從head開始變數到第(L - n - 1)個節點,然後將它的next指向它下一個的下一個節點,完成節點移除。
    1. 然後返回原來的連結串列的head。

用Python實現的程式碼如下:

class Solution(object):
    def removeNthFromEnd(self, head, n):
        """
        :type head: ListNode
        :type n: int
        :rtype: ListNode
        """
        if head.next == None:
            return None
        
        current = head
        length_list = 0
        while current != None:
            length_list += 1
            current = current.next

        pos = length_list - n

        # it means that we should remove the first node
        if pos <= 0: return head.next
        
        # define the pointer to start to iterate the nodes
        moving_pointer = head;
        for i in range(pos - 1):
            moving_pointer = moving_pointer.next


        # If the node that we want to remove is not exist, just return the original list
        if moving_pointer.next == None:
            return head
        
        # Skip the deleted node
        moving_pointer.next = moving_pointer.next.next

        return head

這個方案比較符合人性,也容易想到,但是缺點是必須先遍歷一遍整個連結串列獲得連結串列長度,然後再移動指標到刪除的那個節點之前的節點。

有沒有辦法不先獲取連結串列長度,又能順利從連結串列頭部移動到想要刪除的節點之前呢?我想起了古代小兵探路的故事,可以讓一個指標先行一步,探出一條準確的步數,然後再讓一個指標走L1(連結串列長度) - n - 1個節點就行了。

我簡單繪製了一張圖, 步驟如下所示:

  1. 定義一個dump節點,它的next指向head,fast和slow都指向dump。
  2. 用一個fast指標和slow指標分別進行移動,fast指標優遍歷前n個節點,那麼剩下沒遍歷的節點數量剛好就是L1 - n。
  3. 然後再繼續遍歷fast和slow,就能順利遍歷到第L1 - n -1個節點,然後移除掉下一個節點,然後返回修改後的list。

那麼想好之後,快速寫出程式碼:


class Solution(object):
   def removeNthFromEnd(self, head, n):
        """
        :type head: ListNode
        :type n: int
        :rtype: ListNode
        """
        dump = ListNode(0)
        dump.next = head
        fast = dump
        slow = dump

        for i in range(n+1):
            fast = fast.next

        # move the fast to the end of the list
        while fast != None:
            fast = fast.next
            slow = slow.next
        
        if slow.next == None:
            return dump.next
        
        slow.next = slow.next.next

        return dump.next

Js版本類似:

/**
 * @param {ListNode} head
 * @param {number} n
 * @return {ListNode}
 */
var removeNthFromEnd = function(head, n) {

    let dump = new ListNode(0);
    dump.next = head;
    let fastPointer = dump;
    let slowPointer = dump;

    for (let i = 0; i <= n; i++) {
        fastPointer = fastPointer.next;
    }

    while(fastPointer != undefined) {
        fastPointer = fastPointer.next;
        slowPointer = slowPointer.next;
    }

    slowPointer.next = slowPointer.next.next;

    return dump.next;
};

如下圖所示,這個演算法的Runtime非常快,但是memory使用就比較大,畢竟用了2個指標以及dump去實現了遍歷。

相關文章