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

文章目录

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

题目描述

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

示例 1:

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

输出:[1,2,3,5]

示例 2:

输入:head = [1], n = 1

输出:[]

示例 3:

输入:head = [1,2], n = 1

输出:[1]

提示:

  • 链表中结点的数目为 sz
  • 1 <= sz <= 30
  • 0 <= Node.val <= 100
  • 1 <= n <= sz

解题思路

常用三种方法:

  • 双指针一次遍历(推荐):快指针先走 n 步,然后快慢同步移动,快指针到尾时,慢指针在待删除结点的前一个结点。
  • :遍历入栈,再弹出 n 次找到目标结点的前驱,修改指针。
  • 两次遍历:第一次统计长度 sz,第二次定位到 sz-n 的前驱删除。

双指针法(一次遍历)

否 是 新建哑结点dummy->head fast=slow=dummy fast先走n步 fast到尾? fast与slow同步向前 slow.Next为待删 slow.Next = slow.Next.Next 返回dummy.Next

  • 设置 dummy 可以统一删除头结点的场景
  • 边界:n 一定合法(1<=n<=sz)
  • 时间 O(sz),空间 O(1)

栈法

  • 遍历链表全部入栈
  • 弹出 n 次,栈顶即为待删结点的前驱
  • 修改前驱的 Next 指向
  • 时间 O(sz),空间 O(sz)

两次遍历

  • 第一次统计长度 sz
  • 找到第 sz-n 个结点的前驱并删除
  • 时间 O(sz),空间 O(1)

代码要点

  • 使用 dummy 统一处理删除头结点
  • 快指针先走 n 步,再一起走
  • 注意 slow.Next 是否为空的判定

本仓库 19/main.go 给出双指针法实现,并在 main() 中包含示例自检。

完整题解代码

go 复制代码
package main

import (
	"fmt"
)

type ListNode struct {
	Val  int
	Next *ListNode
}

// removeNthFromEnd 双指针法:快指针先走n步,然后快慢同步,快到尾时慢在待删前一个
// 时间复杂度: O(sz);空间复杂度: O(1)
func removeNthFromEnd(head *ListNode, n int) *ListNode {
	dummy := &ListNode{Next: head}
	fast, slow := dummy, dummy

	// fast 先走 n 步
	for i := 0; i < n; i++ {
		if fast != nil {
			fast = fast.Next
		}
	}
	// fast 和 slow 同步走,直到 fast 到尾
	for fast != nil && fast.Next != nil {
		fast = fast.Next
		slow = slow.Next
	}
	// slow.Next 就是待删除结点
	if slow != nil && slow.Next != nil {
		slow.Next = slow.Next.Next
	}
	return dummy.Next
}

// 辅助:从切片构造链表
func buildList(vals []int) *ListNode {
	if len(vals) == 0 {
		return nil
	}
	head := &ListNode{Val: vals[0]}
	curr := head
	for i := 1; i < len(vals); i++ {
		curr.Next = &ListNode{Val: vals[i]}
		curr = curr.Next
	}
	return head
}

// 辅助:链表转切片
func listToSlice(head *ListNode) []int {
	res := []int{}
	for head != nil {
		res = append(res, head.Val)
		head = head.Next
	}
	return res
}

func main() {
	// 示例1: head=[1,2,3,4,5], n=2 -> [1,2,3,5]
	h1 := buildList([]int{1, 2, 3, 4, 5})
	ans1 := removeNthFromEnd(h1, 2)
	fmt.Printf("示例1: %v\n", listToSlice(ans1))

	// 示例2: head=[1], n=1 -> []
	h2 := buildList([]int{1})
	ans2 := removeNthFromEnd(h2, 1)
	fmt.Printf("示例2: %v\n", listToSlice(ans2))

	// 示例3: head=[1,2], n=1 -> [1]
	h3 := buildList([]int{1, 2})
	ans3 := removeNthFromEnd(h3, 1)
	fmt.Printf("示例3: %v\n", listToSlice(ans3))

	// 额外: 删除头结点场景 head=[1,2], n=2 -> [2]
	h4 := buildList([]int{1, 2})
	ans4 := removeNthFromEnd(h4, 2)
	fmt.Printf("额外: %v\n", listToSlice(ans4))
}