在Golang里操作链表时,删除节点最核心的是理解指针的传递方式。由于Go的函数参数是值传递,直接传入头节点指针并不能修改调用方的头指针变量,需要特殊处理才能正确删除。

单链表的基本结构
我们先定义一个简单的单链表节点:
type ListNode struct {
Val int
Next *ListNode
}
为什么普通指针参数删不掉头节点
如果删除函数只接收 *ListNode,函数内修改的只是副本指向,调用方的头变量不会改变。例如下面这种写法在删除头节点时会失效:
// 错误示例:无法删除头节点
func deleteNodeWrong(head *ListNode, val int) {
if head == nil {
return
}
if head.Val == val {
head = head.Next // 仅修改了副本
return
}
// 后续遍历省略
}
正确方法一:返回新的头节点
让函数返回删除后的头指针,调用方接收返回值即可:
func deleteNode(head *ListNode, val int) *ListNode {
if head == nil {
return nil
}
if head.Val == val {
return head.Next
}
cur := head
for cur.Next != nil {
if cur.Next.Val == val {
cur.Next = cur.Next.Next // 跳过目标节点
break
}
cur = cur.Next
}
return head
}
正确方法二:使用指针的指针
若想在原地修改头变量,可传入 **ListNode:
func deleteNodePP(head **ListNode, val int) {
if *head == nil {
return
}
if (*head).Val == val {
*head = (*head).Next
return
}
cur := *head
for cur.Next != nil {
if cur.Next.Val == val {
cur.Next = cur.Next.Next
return
}
cur = cur.Next
}
}
指针理解要点
- Go中所有参数都是值传递,指针变量本身也是值。
- 要改头节点变量,必须让函数能访问该变量的地址。
- 中间节点删除只需修改前置节点的Next指向。
删除节点不是销毁内存,而是让链表不再引用它,Go的GC会自动回收。
完整使用示例
func main() {
head := &ListNode{Val: 1, Next: &ListNode{Val: 2, Next: &ListNode{Val: 3}}}
head = deleteNode(head, 2) // 删除值为2的节点
for p := head; p != nil; p = p.Next {
println(p.Val)
}
}
小结
掌握Golang链表删除节点的关键,在于分清何时该返回新头,何时用双重指针。理清指针拷贝逻辑,就能写出稳定可靠的链表代码。