ARTICLE DETAIL

资讯详情

深耕网站视觉设计与运营推广的一线实战洞察。

LeetCode 203. Remove Linked List Elements(Go 实现):单链表删除指定值结点的完整解法与源码剖析

LeetCode 203. Remove Linked List Elements(Go 实现):单链表删除指定值结点的完整解法与源码剖析 LeetCode 203. Remove Linked List ElementsGo 实现单链表删除指定值结点的完整解法与源码剖析【免费下载链接】LeetCode-Go✅ Solutions to LeetCode by Go, 100% test coverage, runtime beats 100% | LeetCode 题解项目地址: https://gitcode.com/GitHub_Trending/le/LeetCode-Go本文以 LeetCode-Go 仓库中 203. Remove Linked List Elements 题解文档为核心系统讲解在单链表中删除所有指定值结点的标准解法从题目语义、示例推导到 Go 语言源码的逐行剖析再到测试用例与复杂度验证。读完本文你将掌握基于“哨兵dummy结点 双指针”的链表删除范式并能直接复用本仓库的 ListNode 工具与测试框架进行验证。一、题目与题意题目原文如下见 题解文档Remove all elements from a linked list of integers that have value val.即给定一个整数单链表的头结点head和一个整数val删除链表中所有值等于val的结点返回删除后的新链表头结点。题目示例Input: 1-2-6-3-4-5-6, val 6 Output: 1-2-3-4-5从示例可以看出两个关键点目标值val在链表中可能出现多次示例中6出现在第 3 位和第 7 位必须全部删除而不是只删除第一个删除后其余结点的相对顺序保持不变即该操作属于“就地in-place删除”不改变非目标结点的先后次序。题目大意删除链表中所有指定值的结点。二、解题思路按题意直接模拟题解文档给出的解题思路非常直接——Just follow the problem statement按照题意做即可。这句话背后隐藏着一个经典的链表删除套路其核心难点在于链表是单向的每个结点只能通过前驱结点访问若被删除的结点恰好是头结点常规的“pre.Next cur.Next”写法需要单独处理头指针更新目标值可能连续出现如1-1-1删除1删除后前驱指针不能错误地跳过新暴露出的结点。因此最稳妥的做法是引入一个虚拟头结点dummy / sentinel head统一所有结点的删除逻辑避免对头结点做特判。三、仓库源码实现LeetCode-Go 仓库中该题的标准解答位于 leetcode/0203.Remove-Linked-List-Elements/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 }3.1 逐行解析代码片段作用if head nil { return head }空链表直接返回nil避免后续解引用空指针这同时覆盖了“链表为空”的边界用例newHead : ListNode{Val: 0, Next: head}创建哨兵结点Val取 0 仅为占位实际不会被读取Next指向原头结点此后所有删除逻辑都能以“统一的前驱-后继”视角处理pre : newHead/cur : head双指针pre始终指向“当前遍历位置的前一个结点”cur为当前待检查结点if cur.Val val { pre.Next cur.Next }命中目标值让pre直接跨过cur指向cur.Next完成逻辑删除此时pre不移动因为新的后继结点可能仍等于val处理连续重复值else { pre cur }未命中pre前进到cur维持前驱关系cur cur.Nextcur总是向后移动保证每个结点都被检查且链表不会因删除而断裂return newHead.Next返回哨兵结点的后继即删除后的新头结点若全部结点都被删除则为nil这里有一个容易被忽略的细节命中删除分支时pre不前进。以1-1-1、val 1为例若删除cur后贸然令pre cur那么下一个1就会漏删。源码中pre与cur的推进时机分离正是保证“删除后连续结点也被正确检查”的关键。3.2 ListNode 数据结构的来源上述代码中的ListNode并非本文件内定义而是通过类型别名复用了仓库公共数据结构包 structures/ListNode.go 中的定义// ListNode 是链接节点 // 这个不能复制到*_test.go文件中。会导致Travis失败 type ListNode struct { Val int Next *ListNode }从源码注释可以看出该结构体被设计为整个 LeetCode-Go 仓库所有链表题的公共基础设施并在 leetcode/0203.Remove-Linked-List-Elements/203. Remove Linked List Elements.go 中通过type ListNode structures.ListNode完成别名引入。这种设计使得各题解之间无需重复定义结点结构也避免了测试文件复制定义可能引发的 CITravis问题。同时structures/ListNode.go 还提供了两个配套的链表转换工具直接服务于本题的测试Ints2List(nums []int) *ListNode将整数切片顺序构造成链表[]int{1,2,6,3,4,5,6}得到1-2-6-3-4-5-6List2Ints(head *ListNode) []int将链表还原为整数切片便于断言结果该函数内置了 100 层深度限制若超出会panic提示“链条深度超过 100可能出现环状链条”可有效拦截测试中的误用。四、测试用例与验证仓库为本题配套了完整的表驱动测试 leetcode/0203.Remove-Linked-List-Elements/203. Remove Linked List Elements_test.go通过Test_Problem203覆盖了 8 组场景输入链表val期望输出覆盖点1-2-3-4-512-3-4-5删除头结点1-2-3-4-521-3-4-5删除中间结点1-1-1-1-11空链表连续重复值全部删除1-2-3-2-3-2-3-221-3-3-3目标值多次出现、间隔分布1-2-3-4-551-2-3-4删除尾结点空链表5空链表空输入1-2-3-4-5101-2-3-4-5目标值不存在原链表不变11空链表单结点且即删测试中通过structures.Ints2List(p.one)构造链表调用removeElements后立即用structures.List2Ints(...)转回切片并打印输入/输出对例如fmt.Printf(【input】:%v 【output】:%v\n, p, structures.List2Ints(removeElements(structures.Ints2List(p.one), p.n)))以上用例完整覆盖了本题的全部边界情形空链表、单结点、头结点删除、尾结点删除、连续重复值、多段分布值以及目标值不存在。在仓库根目录执行go test ./leetcode/0203.Remove-Linked-List-Elements/...或仓库提供的gotest.sh即可本地复现验证。五、复杂度分析时间复杂度O(n)。算法仅需一次遍历每个结点至多被访问一次n为链表长度。空间复杂度O(1)。只额外创建了一个哨兵结点未使用与输入规模相关的辅助空间。六、两种常见替代写法思路对照在理解仓库标准解的基础上掌握以下两种变形有助于应对面试中的追问。6.1 不引入哨兵结点单独处理头结点func removeElements(head *ListNode, val int) *ListNode { for head ! nil head.Val val { head head.Next } if head nil { return head } pre, cur : head, head.Next for cur ! nil { if cur.Val val { pre.Next cur.Next } else { pre cur } cur cur.Next } return head }该写法先用循环“跳过”开头连续的多个目标结点再从中间开始正常删除。逻辑等价但头结点处理与主循环分离代码分支更多容易出现遗漏。6.2 递归写法func removeElements(head *ListNode, val int) *ListNode { if head nil { return head } head.Next removeElements(head.Next, val) if head.Val val { return head.Next } return head }递归版本思路简洁先递归处理后缀子链表再判断当前结点是否该删。代价是空间复杂度退化为O(n)递归调用栈深度对于超长链表存在栈溢出风险因此仓库题解选择迭代实现更贴合工程实践。七、小结本题的核心是单链表的按值删除难点在于头结点删除与连续重复值删除标准解法采用“哨兵结点 双指针”模式用一个newHead统一删除逻辑pre在删除分支保持不动从而天然处理连续重复仓库实现完整覆盖了空链表、单结点、首尾删除、连续重复、目标值缺失等边界场景并有 8 组表驱动测试佐证可直接在 leetcode/0203.Remove-Linked-List-Elements/203. Remove Linked List Elements_test.go 中查看并运行验证该“哨兵结点”范式同样适用于 82. Remove Duplicates from Sorted List II 等需要统一处理头结点删除的题目建议结合练习。【免费下载链接】LeetCode-Go✅ Solutions to LeetCode by Go, 100% test coverage, runtime beats 100% | LeetCode 题解项目地址: https://gitcode.com/GitHub_Trending/le/LeetCode-Go创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考
返回列表