题目和思路:
链表基础:
复习定义链表结点:
class ListNode:
def __init__(self, val, next=None):
self.val = val
self.next = None
循环遍历链表,每次循环需要查看next的val值,如果等于目标值,将当前节点的next指向再后面一个节点,跳过当前节点的next。由于每次查看的下一个节点的目标值,所以定义虚拟头节点,用于防止头节点的val值等于目标值。最终返回虚拟头节点的next即可。代码如下:
class Solution:
def removeElements(self, head: Optional[ListNode], val: int) -> Optional[ListNode]:
flag = ListNode(next = head)
cur = flag
while cur.next:
if cur.next.val == val:
cur.next = cur.next.next
else: cur = cur.next
return flag.next
实际写代码过程中,犯了如下错误:
while cur.next:
if cur.next.val == val:
cur.next = cur.next.next
cur = cur.next
这样写错误的原因是,不管删没删cur.next,cur都会向后移动一次,那么假如cur.next被删除,cur会直接跳到cur.next.next而不会管cur.next.next是否是目标值也不会检查它是不是None,会漏删、以及链表尾部是目标值时会报NoneType没有next方法的错误。所以必须只有不删cur.next的时候才能往后移动cur,删除了就原地呆着等下一轮循环。
很糟心,代码如下:
class ListNode:
def __init__(self, val = 0, next = None):
self.val = val
self.next = next
class MyLinkedList:
def __init__(self):
self.head = ListNode() #这是一个虚拟头节点
def get(self, index: int) -> int:
if index < 0: return -1
cur = self.head.next
cnt = 0
while cur:
if cnt == index:
return cur.val
else:
cur = cur.next
cnt += 1
return -1
def addAtHead(self, val: int) -> None:
node = ListNode(val)
node.next = self.head.next
self.head.next = node
def addAtTail(self, val: int) -> None:
cur = self.head
while cur.next:
cur = cur.next
cur.next = ListNode(val)
def addAtIndex(self, index: int, val: int) -> None:
node = ListNode(val)
cnt = 0
cur = self.head
while cur.next:
if cnt == index:
node.next = cur.next
cur.next = node
break
else:
cur = cur.next
cnt += 1
if cnt == index:
cur.next = node
def deleteAtIndex(self, index: int) -> None:
cur = self.head
cnt = 0
while cur.next:
if cnt == index:
cur.next = cur.next.next
break
else:
cur = cur.next
cnt += 1
第一遍完全错误的点是,把初始化的head当成了头节点,结果直接头部插入节点直接会变成1->0(head),然后把head赋给了1,整一个理解错误。
第二遍错的点是在插入环节,node插在cur和cur.next之间,我写成了node.next =cur.next.next, cur.next = node.next,整段垮掉。
第三遍错在delete,本题跟上题有区别,本题删除的是index处的值,没有在if条件下删除后面加break跳出循环,导致把后面全删完了...
这里贴一段比较好的代码,下次可以参考:
class ListNode:
def __init__(self, val=0, next=None):
self.val = val
self.next = next
class MyLinkedList:
def __init__(self):
self.size = 0
self.head = ListNode(0) # Sentinel node as pseudo-head
def get(self, index):
if index < 0 or index >= self.size:
return -1
current = self.head
for _ in range(index + 1):
current = current.next
return current.val
def addAtHead(self, val):
self.head.next = ListNode(val, self.head.next)
self.size += 1
def addAtTail(self, val):
self.size += 1
new_node = ListNode(val)
current = self.head
while current.next:
current = current.next
current.next = new_node
def addAtIndex(self, index, val):
if index > self.size:
return
index = max(0, index)
self.size += 1
pred = self.head
for _ in range(index):
pred = pred.next
to_add = ListNode(val)
to_add.next = pred.next
pred.next = to_add
def deleteAtIndex(self, index):
if index < 0 or index >= self.size:
return
self.size -= 1
pred = self.head
for _ in range(index):
pred = pred.next
pred.next = pred.next.next
for循环的好处是到位置就停,然后开始操作!学!
1、双指针法,左指针指向左边反转好的内容,右指针指向还没反转的部分。假如右指针指到None,迭代终止了,返回左指针即可。如果右指针不为None,那么先记录一下cur.next,记为temp。然后将右指针指向的节点连接到左指针指向的节点,cur.next = prev,然后把左指针移动到cur,cur移动到temp进行下一轮迭代。代码如下:
class Solution:
def reverseList(self, head: Optional[ListNode]) -> Optional[ListNode]:
prev, cur = None, head
while cur:
temp = cur.next
cur.next = prev
prev = cur
cur = temp
return prev
2、递归法一(尾递归):根据上述双指针法写的递归,需要新建一个函数,reverse,传入左指针和右指针的指向,原主函数用于初始化。递归终止的情况就是右指针指向None,左指针就指向了反转好的头节点,递归结束反转完毕。
一般情况下,也就是从头开始,把右指针的next保存一下,把右指针指向左指针节点,完成了局部反转,返回调用自身,相当于把左指针和右指针向右移了一下,只不过是用cur和temp表示。
举个栗子:假设我们有一个链表1->2->3->None,我们要使用这个递归方法来反转它。
- 初始调用
- reverse(1, None)
- cur 指向 1, prev 是 None
- 递归调用层级 1
- 在 reverse 函数内部:
- temp = cur.next 保存了 2
- cur.next = prev 使得 1 指向 None(断开 1 -> 2,现在 1 -> None)
- 调用 reverse(2, 1)
- 在 reverse 函数内部:
- 递归调用层级 2
- reverse(2, 1)
- cur 指向 2, prev 指向 1
- 在 reverse 函数内部:
- temp = cur.next 保存了 3
- cur.next = prev 使得 2 指向 1(断开 2 -> 3,现在 2 -> 1 -> None)
- 调用 reverse(3, 2)
- 递归调用层级 3
- reverse(3, 2)
- cur 指向 3, prev 指向 2
- 在 reverse 函数内部:
- temp = cur.next 保存了 None
- cur.next = prev 使得 3 指向 2(现在 3 -> 2 -> 1 -> None)
- 调用 reverse(None, 3)
- 递归调用层级 4
- reverse(None, 3)
- cur 是 None, prev 指向 3
- 基本情况满足,返回 prev,即 3
class Solution:
def reverseList(self, head: Optional[ListNode]) -> Optional[ListNode]:
return self.reverse(head, None)
def reverse(self, cur, prev):
#基本情况,右指针指向None,直接返回左指针就是已经反转好的链表。
if cur == None:
return prev
#递归反转
temp = cur.next
cur.next = prev
return self.reverse(temp,cur)
3、递归法二:(头递归):基本情况是当前的链表只有一个节点或者是空,那就直接返回原链表head。递归地反转head之后的节点,返回为reversed_head(这是反转好的头节点,也就是原链表的尾节点!!),这时候head.next是反转后最后一个节点,把他指向head,再给head断开连接,就完成了反转,这时只要继续返回reversed_head即可。
举个栗子:假设我们有一个链表1->2->3->4->None,我们要使用这个递归方法来反转它。
-
递归调用层级 1
head指向1- 递归调用
reverseList以反转2 -> 3 -> 4 -> None
-
递归调用层级 2
head指向2- 递归调用
reverseList以反转3 -> 4 -> None
-
递归调用层级 3
head指向3- 递归调用
reverseList以反转4 -> None
-
递归调用层级 4
head指向4- 到达基本情况:
head.next是None,所以返回head(即4)
递归返回并反转链接
-
返回到递归调用层级 3
head是3- 执行
head.next.next = head(即4 -> 3) - 设置
head.next = None(断开3 -> 4,现在4 -> 3 -> None) - 返回新的头节点
4
-
返回到递归调用层级 2
head是2- 执行
head.next.next = head(即3 -> 2) - 设置
head.next = None(断开2 -> 3,现在4 -> 3 -> 2 -> None) - 返回新的头节点
4
-
返回到递归调用层级 1
head是1- 执行
head.next.next = head(即2 -> 1) - 设置
head.next = None(断开1 -> 2,现在4 -> 3 -> 2 -> 1 -> None) - 返回新的头节点
4
class Solution:
def reverseList(self, head: Optional[ListNode]) -> Optional[ListNode]:
# 基本情况:空链表或只有一个节点
if head is None or head.next is None:
return head
# 递归反转除了头节点外的子链表
reversed_head = self.reverseList(head.next)
# 将原始头节点连接到新反转链表的末尾
head.next.next = head
head.next = None
# 返回新的头节点
return reversed_head
今日总结:
以前本科时候就很讨厌链表,因为常常出错。但是逃避是没有用哒!尽管今天还是很多错误,但是找出了错误的原因,并且从这些错误中吸取到了教训,下次也许会好一些。
尽管今天没有额外的题目,但是第二题和第三题让我头大。第二题主要是对类和链表的理解有问题,太久没有写代码生疏了很多很多,一遍一遍回滚直到AC,在debug中成长...
第三题双指针方法很容易就写出来了,对两种递归方法一直没有理清头绪。两种方法截然相反,实在很影响理解。第一种尾递归实际就是迭代,第二种头递归是在递归返回的时候进行反转操作,有很大区别,理解head.next.next=head成为关键。
文章讲述了链表基础操作,包括移除链表元素时的错误分析与修复,链表设计中的伪头节点概念,以及反转链表的双指针法和递归实现。作者反思了编程过程中的问题和学习心得。
783





