移除链表元素
给你一个链表的头节点 head
和一个整数 val
,请你删除链表中所有满足 Node.val == val
的节点,并返回 新的头节点 。
题解一,迭代法:
解题思路:
1. 首先需要考虑,链表为空或head节点的值等于val的情况,这里可以引入一个虚拟的哑节点dummy_node,该哑节点指向头节点,引入哑节点的好处就是不用特殊考虑链表为空或头节点为空的情况。
2. 定义一个current变量,指向哑节点dummy_node;
3. 从current.next开始遍历,直到为None;
4. 遍历过程中,分两种情况处理,当current.next.val==val时,那么需要删掉该节点,指向下一个节点;
5. 当current.next.val!=val时,继续执行遍历
代码实现:
class Solution:
def remove_elements(self,head:Optional(ListNode), val:int)-> Optional(ListNode):
# 定义一个虚拟哑节点
dummy_node = ListNode(0)
# 将哑节点指向头节点
dummy_node.next = head
# 定义一个current变量指向哑节点
current = dummy_node
# 从current.next开始遍历
while current.next:
if current.next.val == val:
# 这里current.next指向了新的节点,那dummy_node也重新指向新的头节点
current.next = current.next.next
else:
# 继续遍历
current = current.next
return dummy_node.next
# 为什么没有返回head,而是返回的dummy_node.next,因为当head.val==val时,并没有删除头节点即head,但是current.next = current.next.next却更新了dummy_node.next,即新的头节点
题解二,递归法:
解题思路:
代码实现:
class Solution:
def removeElements(self, head: Optional[ListNode], val: int) -> Optional[ListNode]:
if not head:
return head
# 这里使用了递归调用 self.removeElements(head.next, val),它的作用是删除链表中除头节点外值为 val 的节点。这个调用会不断地向链表的后面部分递归,直到链表的末尾。
head.next = self.removeElements(head.next,val)
if head.val==val:
return head.next
else:
return head
讨论:
关于递归,举个例子,比如链表[1,2,1,3],val=1,该参数传入到上面的递归法中计算,你会发现,虽然返回新的头节点是2,但是第一个1到2的指针并没有断开,关于这点,做以下讨论:
在返回新链表头(值为2的节点)之前,原来的链表中第一个值为1的节点的
next
指针仍然指向值为2的节点。重要的是要理解,递归调用返回新链表头后,原来的链表中指向值为1的节点的引用可能不再被使用(除非有外部引用保持对该节点的引用)。递归函数返回新链表头后,调用该函数的上下文会继续使用这个新链表头,而不是原来的链表头。
如果我们从外部观察这个递归过程,会看到如下情况:
- 初始时,外部引用指向原链表的头节点(值为1的节点)。
- 递归调用开始,逐步深入到链表内部。
- 当递归调用返回时,每个递归层级都会根据当前节点的值是否等于
val
来决定是否更新返回链表的头节点。- 最终的递归调用返回后,原始链表中的第一个值为1的节点的
next
指针仍然指向值为2的节点,但是这个指向关系对于新的链表来说已经不再重要,因为新的链表头节点(值为2的节点)已经被返回,并且后续的链表结构(2 -> 3)已经按照预期被构建。在实际使用中,调用
removeElements
函数的代码会接收返回的新链表头,并可能用这个新链表头来更新它自己的引用,从而“断开”与原来链表中第一个值为1的节点的连接。但是,这个“断开”是在逻辑上的,即在新的链表结构中不再包含原来的头节点。实际的物理内存中,原来的链表结构仍然存在,直到没有任何引用指向它,从而被垃圾回收机制回收。在大多数现代编程语言中,如果一个对象(在这里是
1
节点)没有任何引用指向它,那么它就被认为是不可达的,并且最终会被垃圾回收器自动清理掉,以释放其占用的内存。重要的是,在函数内部,我们没有改变任何节点的
next
指针来“物理上”断开连接。我们只是返回了一个新的链表头,这个新的链表头指向的链表中不包含值为val
的节点。因此,虽然第一个值为1的节点的
next
指针在递归过程中没有改变,但是返回的新链表已经不再包含这个节点,实现了从逻辑上移除该节点的效果。