19. 删除链表的倒数第 N 个结点

202 阅读3分钟

给你一个链表,删除链表的倒数第 n 个结点,并且返回链表的头结点。

进阶:你能尝试使用一趟扫描实现吗?

image.png

示例 1:

输入:head = [1,2,3,4,5], n = 2
输出:[1,2,3,5]

解法一

数组 自己看答案之前比较笨的做法,就是转为数组,删除了过后,再转为链表,但还是用来两遍循环。

def removeNthFromEnd1(head, n):
    arr = []
    while not head is None:
        arr.append(head.val)
        head = head.next
    del arr[len(arr) - n]

    if len(arr) == 0: return ListNode()

    for i, value in enumerate(arr[::-1]):
        if i == 0:
            ans = ListNode(value)
        else:
            ans = ListNode(value, ans)

    return ans

解法二

计算链表长度 参考力扣官方答案,先计算链表长度,然后就能确定删除倒数第n个节点,就是删除顺数第length - n + 1个节点。然后让删除的节点上一个节点指向下下一个节点就好了。

另外注意的是:在对链表进行操作时,一种常用的技巧是添加一个哑节点(dummy node),它的next 指针指向链表的头节点。这样一来,我们就不需要对头节点进行特殊的判断了。

def removeNthFromEnd2(head, n):
    """计算链表长度"""
    def getLength(head):
        """获取链表长度"""
        length = 0
        while head:
            length += 1
            head = head.next
        return length
    # 添加哑节点,使它的next指向链表的头节点
    dummy = ListNode(0, head)
    length = getLength(head)
    cur = dummy  # 当前节点指向哑节点
    # 第length - n + 1个节点,就是我们需要删除的节点
    for i in range(1, length - n + 1):
        cur = cur.next
    cur.next = cur.next.next
    return dummy.next

解法三

参考力扣官方答案,我们在遍历链表的同时将所有节点依次入栈。根据栈「先进后出」的原则,我们弹出栈的第 n 个节点就是需要删除的节点,并且目前栈顶的节点就是待删除节点的前驱节点。这样一来,删除操作就变得十分方便了。

def removeNthFromEnd3(head, n):
    """栈"""
    dummy = ListNode(0, head)
    stack = list()
    cur = dummy
    while cur:
        stack.append(cur)
        cur = cur.next

    for i in range(n):
        stack.pop()

    prev = stack[-1]
    prev.next = prev.next.next
    return dummy.next

解法四

双指针(快慢指针) 由于我们需要找到倒数第 n 个节点,因此我们可以使用两个指针 first 和 second 同时对链表进行遍历,并且 first 比 second 超前 n 个节点。当 first 遍历到链表的末尾时,second 就恰好处于倒数第 n 个节点。

如果我们能够得到的是倒数第 n 个节点的前驱节点而不是倒数第 n 个节点的话,删除操作会更加方便。因此我们可以考虑在初始时将 second 指向哑节点,其余的操作步骤不变。这样一来,当 first 遍历到链表的末尾时,second 的下一个节点就是我们需要删除的节点。

这是我觉得最好的算法,因为扫描的长度最少。

def removeNthFromEnd4(head, n):
    """双指针,快慢指针"""
    dummy = ListNode(0, head)
    first = head
    second = dummy
    for i in range(n):  # 让first先领先n个节点
        first = first.next

    # first和second同时前进
    # 上面for和下面while遍历的长度和,加起来就是链表的长度L
    while first:
        first = first.next
        second = second.next
    second.next = second.next.next

    return dummy.next
复杂度分析
  • 时间复杂度:O(L)O(L),其中 L 是链表的长度。
  • 空间复杂度:O(1)O(1),只是使用了常数个变量。