203.移除链表元素是一道经典的链表操作题目,要求我们删除链表中所有值等于给定val的节点。这道题看似简单,但实际处理时需要特别注意边界条件和指针操作的正确性。
链表删除操作的核心在于正确处理节点的next指针。与数组不同,链表不能直接通过索引访问元素,必须通过指针逐个遍历。删除节点时,我们需要将被删除节点的前驱节点的next指针指向被删除节点的后继节点,从而跳过待删除节点。
这道题有两个经典解法:
两种方法各有优劣,第一种方法代码稍显复杂但节省内存,第二种方法逻辑统一但需要额外空间。作为刷题者,我们需要掌握这两种方法,并理解它们的时间复杂度都是O(n),空间复杂度都是O(1)。
链表是一种线性数据结构,通过指针将一组零散的内存块串联起来。每个节点包含两部分:
在Python中,链表节点通常定义为:
python复制class ListNode:
def __init__(self, val=0, next=None):
self.val = val
self.next = next
链表与数组的主要区别:
这道题看似简单,但有几个关键点需要注意:
提示:链表问题中,处理头节点和尾节点时最容易出错,需要特别小心。
python复制class Solution(object):
def removeElements(self, head, val):
# 处理头节点等于val的情况
while head is not None and head.val == val:
head = head.next
if head is None:
return None
p = head
while p.next is not None:
if p.next.val == val:
p.next = p.next.next
else:
p = p.next
return head
处理头节点:
遍历剩余节点:
返回处理后的头节点
python复制class Solution(object):
def removeElements(self, head, val):
dummy = ListNode(next=head) # 创建虚拟头节点
curr = dummy
while curr.next is not None:
if curr.next.val == val:
curr.next = curr.next.next
else:
curr = curr.next
return dummy.next
创建虚拟头节点:
统一处理所有节点:
返回dummy.next作为新头节点
| 特性 | 直接操作原链表 | 使用虚拟头节点 |
|---|---|---|
| 代码复杂度 | 较高 | 较低 |
| 内存使用 | 更优(无dummy) | 多一个节点 |
| 逻辑统一性 | 需要特殊处理头节点 | 统一处理 |
| 适用场景 | 内存敏感场景 | 代码简洁优先 |
未处理连续多个头节点:
python复制# 错误示例
if head.val == val:
head = head.next
这样只能处理第一个头节点,无法处理[7,7,7]删除7的情况
指针移动错误:
python复制while p is not None:
if p.val == val:
p = p.next
这样只是移动了指针,没有实际删除节点
访问空指针:
python复制while p.next is not None:
if p.val == val: # 应该检查p.next.val
p.next = p.next.next
打印链表辅助调试:
python复制def print_list(head):
while head:
print(head.val, end=" -> ")
head = head.next
print("None")
使用简单测试用例:
画图辅助理解:
在纸上画出链表和指针变化,有助于理解指针操作
虚拟头节点(dummy node)是解决链表问题的利器,可以:
使用模式:
python复制dummy = ListNode(next=head)
curr = dummy
# 统一处理逻辑
return dummy.next
链表问题必须考虑:
python复制class Solution:
def removeElements(self, head: ListNode, val: int) -> ListNode:
if not head:
return None
head.next = self.removeElements(head.next, val)
return head.next if head.val == val else head
递归解法简洁但空间复杂度为O(n),了解即可,面试中更常考察迭代解法。
在实际刷题过程中,我建议先从迭代解法入手,确保完全掌握指针操作后,再尝试递归解法。链表问题的核心在于理解指针操作和边界条件处理,多练习才能形成直觉。