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

68 阅读2分钟

leetcode.cn/problems/re…

image.png

解法一:计算链表长度

得到链表的长度 L。随后我们再从头节点开始对链表进行一次遍历,当遍历到第 Len−n 个节点时,它就是我们需要删除的节点。

/**
 * Definition for singly-linked list.
 * type ListNode struct {
 *     Val int
 *     Next *ListNode
 * }
 */
func removeNthFromEnd(head *ListNode, n int) *ListNode {
    // 统计链表的长度
    len := 0
    cur := head
    for cur != nil{
        len++
        cur = cur.Next
    }
    // 考虑到有可能被删除的是头节点,因此前面申请一个哨兵节点
    dummy := &ListNode{
        Val: -1,
        Next: head,
    }
    cur = dummy
    // 走到倒数第n个节点的前面一个位置,需要走len-n-1步
    for i := 0; i<len-n; i++{
        cur = cur.Next
    }
    // 删除节点
    cur.Next = cur.Next.Next
    return dummy.Next
}

注意,最后不能直接 return head,必须返回 dummy.Next,因为head 变量在一开始被赋值为链表的头节点,后续代码并没有修改 head 本身,而是通过 dummy 进行操作,如果 head 被删除(即 n == 链表长度),直接 return head 就会返回一个已删除的节点,导致错误。

假设链表是 [1,2,3,4,5]n = 5(删除第 1 个节点,即 1)。

  • 原始 head: 1 -> 2 -> 3 -> 4 -> 5
  • 添加 dummy: (-1) -> 1 -> 2 -> 3 -> 4 -> 5
  • 执行 cur.Next = cur.Next.Next 后:(-1) -> 2 -> 3 -> 4 -> 5
  • dummy.Next 变为 2,但 head 仍然是 1

如果 return head,返回的仍然是 1,但 1 已经被删除,程序会报错。所以必须返回 dummy.Next,即 新的头节点 2

解法二:快慢指针

只扫描一次链表就可以得到答案

/**
 * Definition for singly-linked list.
 * type ListNode struct {
 *     Val int
 *     Next *ListNode
 * }
 */
func removeNthFromEnd(head *ListNode, k int) *ListNode {
    dummy := &ListNode{
        Val: -1,
        Next: head,
    }
    // 倒数第k个节点,即正数第len-k个节点
    // 为了删除该节点,需要找到其前驱节点,倒数第k+1个节点,即正数第len-k-1个节点
    slow, fast := dummy, dummy
    // 快指针先走k+1步
    for i:=0; i<k+1; i++{
        if fast == nil{ // 链表长度不足
            return nil
        }
        fast = fast.Next
    }
    // 快慢指针一同前进,当快指针到达结尾之外时,慢指针走了len-(k+1)步,刚好指向倒数第k+1个节点
    for fast != nil{
        slow = slow.Next
        fast = fast.Next
    }
    // 此时慢指针指向倒数第k+1个节点,删除下一个节点(即删除倒数第k个节点)
    slow.Next = slow.Next.Next
    return dummy.Next
}
  • 时间复杂度:O(n)
  • 空间复杂度:O(1)

另一种写法

/**
     * Definition for singly-linked list.
     * type ListNode struct {
     *     Val int
     *     Next *ListNode
     * }
     */
    func removeNthFromEnd(head *ListNode, n int) *ListNode {
        // 虚拟头结点
        dummy := &ListNode{-1, head}
        // 删除倒数第 n 个,要先找倒数第 n + 1 个节点
        x := findFromEnd(dummy, n+1)
        // 删掉倒数第 n 个节点
        x.Next = x.Next.Next
        return dummy.Next
    }

    // 返回链表的倒数第 k 个节点
    func findFromEnd(head *ListNode, k int) *ListNode {
        p1, p2 := head, head
        // p1 先走 k 步
        for i := 0; i < k; i++ {
            p1 = p1.Next
        }
        // p1 和 p2 同时走 n - k 步
        for p1 != nil {
            p2 = p2.Next
            p1 = p1.Next
        }
        // p2 现在指向第 n - k 个节点
        return p2
    }