连绵的春雨把人困在家乡,于是我继续开始刷着算法题,通过 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去实现了遍历。