首页
/ LeetCode-Go 题解:203. Remove Linked List Elements 删除链表中指定值的结点

LeetCode-Go 题解:203. Remove Linked List Elements 删除链表中指定值的结点

2026-09-09 18:13:38作者:吴年前Myrtle

导读

本文围绕 LeetCode 第 203 题《Remove Linked List Elements》展开,讲解如何用 Go 语言在一个单链表中删除所有值为 val 的结点。文章以仓库 leetcode/0203.Remove-Linked-List-Elements/README.md 为主体,结合仓库内真实源码 203. Remove Linked List Elements.go 与测试用例 203. Remove Linked List Elements_test.go 进行纵深讲解。读完本文,你将掌握单链表删除结点的两种核心思路(哨兵结点法 / 头结点特判法)、Go 语言中的指针操作细节,以及本仓库统一的链表工具函数与测试运行方式。

题目

原文描述如下:

Remove all elements from a linked list of integers that have value val.

即:给定一个整数链表和一个目标值 val,删除链表中所有值为 val 的结点,并返回处理后的链表头。

题目给出的示例:

Input:  1->2->6->3->4->5->6, val = 6
Output: 1->2->3->4->5

题目大意

删除链表中所有指定值的结点。注意是"所有"而非"第一个",且题目没有限定 val 在链表中出现的位置,因此头结点、中间结点、尾结点以及连续重复结点都需要被正确处理。

解题思路

官方题解的思路只有一句话:"按照题意做即可。"但这道题真正的考察点在于单链表删除操作对前驱指针的依赖

  • 单链表结点只有 Next 指针,删除当前结点 cur 的唯一方式是让它的前驱 pre.Next 直接跳过它,指向 cur.Next
  • 头结点没有前驱,如果头结点的值恰好等于 val,直接删除会丢失链表入口,因此必须特殊处理。

解决这一矛盾有两条经典路径:

  1. 哨兵结点(dummy head)法:人为构造一个虚拟头结点,让真正的头结点也有统一的前驱,从而把"删头"降级为"删普通结点";
  2. 头结点特判法:先循环删除头部连续等于 val 的结点,再对剩余链表做统一删除。

本仓库采用第一种方案,代码更简洁、无需重复分支。

Go 源码实现详解

仓库的实现位于 203. Remove Linked List Elements.go,完整代码如下:

package leetcode

import (
	"github.com/halfrost/LeetCode-Go/structures"
)

// ListNode define
type ListNode = structures.ListNode

/**
 * Definition for singly-linked list.
 * type ListNode struct {
 *     Val int
 *     Next *ListNode
 * }
 */
func removeElements(head *ListNode, val int) *ListNode {
	if head == nil {
		return head
	}
	newHead := &ListNode{Val: 0, Next: head}
	pre := newHead
	cur := head
	for cur != nil {
		if cur.Val == val {
			pre.Next = cur.Next
		} else {
			pre = cur
		}
		cur = cur.Next
	}
	return newHead.Next
}

关键点一:类型别名复用公共结构

文件开头通过 type ListNode = structures.ListNode 将仓库公共模块 structures/ListNode.go 中定义的单链表结点类型直接别名到 leetcode 包内使用:

type ListNode struct {
	Val  int
	Next *ListNode
}

这种写法让所有链表题共用同一份结点定义,避免每个题解重复声明,也使测试辅助函数(如 Ints2ListList2Ints)可以在全部题目中复用。

关键点二:哨兵结点统一删除逻辑

newHead := &ListNode{Val: 0, Next: head}
pre := newHead
cur := head

newHead 是值为 0 的虚拟头结点,它的 Next 指向真正的头结点。此后 pre 永远指向"当前遍历到的最后一个未被删除的结点",也就是 cur 的合法前驱,删除逻辑对头结点与普通结点完全一致:

  • cur.Val == val:执行 pre.Next = cur.Next,跳过当前结点,pre 不动(因为被删结点不能成为新前驱);
  • 否则:pre = cur,前驱正常后移。

最后 return newHead.Next 直接返回新链表的头。这里也顺带处理了两种极端情况:链表为空(head == nil 提前返回)以及整条链表全部等于 val(此时 newHead.Nextnil,返回空链表)。

关键点三:一次遍历,O(1) 额外空间

整段代码只做一次 for 循环遍历,没有任何额外容器,时间复杂度 O(n)、空间复杂度 O(1),与 LeetCode 官方最优解一致。

边界情况梳理

结合仓库测试用例 203. Remove Linked List Elements_test.go 中的 8 组输入,实现需要覆盖的边界包括:

场景 输入 期望输出
删除头结点 [1,2,3,4,5], val=1 [2,3,4,5]
删除中间结点 [1,2,3,4,5], val=2 [1,3,4,5]
全链表同值 [1,1,1,1,1], val=1 []
值连续重复出现 [1,2,3,2,3,2,3,2], val=2 [1,3,3,3]
删除尾结点 [1,2,3,4,5], val=5 [1,2,3,4]
空链表 [], val=5 []
目标值不存在 [1,2,3,4,5], val=10 [1,2,3,4,5]
单结点被删 [1], val=1 []

哨兵结点方案天然覆盖以上全部场景:全链表同值不会造成死循环(cur 始终后移,pre 停在哨兵处);连续重复值场景中,前一个被删结点不会成为 pre,因此不会"跳过"后续应删结点。

仓库测试与工具函数佐证

测试用例的组织方式

测试文件 203. Remove Linked List Elements_test.go 沿用了本仓库统一的"para/ans 表格驱动测试"风格:question203 把输入 para203{one []int, n int} 与期望 ans203{one []int} 成对组织,Test_Problem203 中循环遍历 8 组用例,并通过公共工具函数完成数组与链表的互转:

structures.List2Ints(removeElements(structures.Ints2List(p.one), p.n))

这里 Ints2List[]int 数组转换为链表输入,List2Ints 把删除后的链表还原为数组以便与期望值断言,两个函数的实现都位于 structures/ListNode.go

测试工具函数的实现细节

structures/ListNode.go 可以看到:

  • Ints2List(nums []int) *ListNode:顺序构造链表,空数组返回 nil
  • List2Ints(head *ListNode) []int:顺序遍历还原数组,并内置 limit := 100 的链条深度保护——若链表深度超过 100 会直接 panic 提示"链条深度超过 100,可能出现环状链条",避免测试中误入环而死循环。

如何运行本题测试

本仓库为 Go module 结构(见根目录 go.mod),且 gotest.sh 中给出了统一的测试命令:

go test -covermode=atomic -coverprofile=coverage.txt ./leetcode/...

针对本题,可以在仓库根目录执行:

go test -v -run Test_Problem203 ./leetcode/0203.Remove-Linked-List-Elements/

输出会包含每组用例的 【input】【output】 打印(来自测试中 fmt.Printf 的调试信息),配合表格断言即可验证实现正确性。仓库的 coverage.txt 与持续集成配置也表明该项目要求题解保持 100% 测试覆盖率,因此每个题目目录都配套了完整的 _test.go 文件。

同类题横向参考

链表的删除/反转/合并是 LeetCode 高频考点,本仓库的以下题目与本题共享 ListNode 数据结构与工具函数,可作为延伸练习:

总结

LeetCode 203 题的核心价值在于:单链表删除必须借助前驱指针,而哨兵结点让头结点不再特殊。本仓库的 Go 实现用不到 20 行代码一次遍历完成删除,配合 structures/ListNode.go 提供的构造/还原工具与表格驱动测试,既保证了正确性,也保持了代码的可读性与可复用性。掌握这一"哨兵结点"模式后,几乎所有涉及链表头删除的题目(如 82、83、19 等)都可以直接套用。

登录后查看全文
热门项目推荐
相关项目推荐

项目优选

收起
kernelkernel
deepin linux kernel
C
33
18
docsdocs
暂无描述
Markdown
899
5.83 K
ops-transformerops-transformer
本项目是CANN提供的transformer类大模型算子库,实现网络在NPU上加速计算。
C++
1.14 K
2.76 K
pytorchpytorch
作为 Ascend for PyTorch 社区的核心组件,TorchNPU 是昇腾专为 PyTorch 打造的深度学习适配插件,使 PyTorch 框架能够直接调用昇腾 NPU,为开发者提供昇腾 AI 处理器的超强算力。
Python
860
1.35 K
ops-nnops-nn
本项目是CANN提供的神经网络类计算算子库,实现网络在NPU上加速计算。
C++
925
1.85 K
jiuwenswarmjiuwenswarm
JiuwenSwarm 是一款基于openJiuwen开发的智能AI Agent,它能够将大语言模型的强大能力,通过你日常使用的各类通讯应用,直接延伸至你的指尖。
Python
3.84 K
1.02 K
kernelkernel
openEuler内核是openEuler操作系统的核心,既是系统性能与稳定性的基石,也是连接处理器、设备与服务的桥梁。
C
533
601
ops-mathops-math
本项目是CANN提供的数学类基础计算算子库,实现网络在NPU上加速计算。
C++
1.37 K
1.46 K
AscendNPU-IRAscendNPU-IR
AscendNPU-IR是基于MLIR(Multi-Level Intermediate Representation)构建的,面向昇腾亲和算子编译时使用的中间表示,提供昇腾完备表达能力,通过编译优化提升昇腾AI处理器计算效率,支持通过生态框架使能昇腾AI处理器与深度调优
C++
548
395
cann-learning-hubcann-learning-hub
CANN 学习中心仓,支持在线互动运行、边学边练,提供教程、示例与优化方案,一站式助力昇腾开发者快速上手。
Jupyter Notebook
1.04 K
525