連綿的春雨把人困在家鄉,於是我繼續開始刷著演算法題,透過 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)個節點被移除。
於是演算法思路如下:
-
- 透過遍歷獲得連結串列長度L1。
-
- 計算出從頭部開始數是第(L1 - n)個節點。
-
- 用一個指標從head開始變數到第(L - n - 1)個節點,然後將它的next指向它下一個的下一個節點,完成節點移除。
-
- 然後返回原來的連結串列的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個節點就行了。
我簡單繪製了一張圖, 步驟如下所示:
- 定義一個dump節點,它的next指向head,fast和slow都指向dump。
- 用一個fast指標和slow指標分別進行移動,fast指標優遍歷前n個節點,那麼剩下沒遍歷的節點數量剛好就是L1 - n。
- 然後再繼續遍歷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去實現了遍歷。