移除链表元素
头节点单独处理
class Solution:
def removeElements(self, head: Optional[ListNode], val: int) -> Optional[ListNode]:
while(head is not None and head.val==val):
head=head.next
tmp=head
while(tmp is not None and tmp.next!=None):
if(tmp.next.val==val):
tmp.next=tmp.next.next
else:
tmp=tmp.next
return head
改动列表的时候都是用tmp操作,不要用head直接操作,否则没法直接返回头节点
注意两点:
- 头节点的删除方式和其他节点不太一样,需要单独处理
- 注意判断一下头节点非空,否则会报错 "have no attribute"
虚拟头节点法
# Definition for singly-linked list.
class ListNode:
def __init__(self, val=0, next=None):
self.val = val
self.next = next
class Solution:
def removeElements(self, head: Optional[ListNode], val: int) -> Optional[ListNode]:
dummy_head=ListNode(next=head)
tmp=dummy_head
while(tmp.next!=None):
if tmp.next.val==val:
tmp.next=tmp.next.next
else:
tmp=tmp.next
return dummy_head.next
虚拟头节点就是一套标准的模板了,相比于头节点单独处理,形式上更通顺,而且不用额外判断头节点是否为空
设计链表
不使用虚拟头节点
#错误代码
class ListNode:
def __init__(self,val=None,next=None):
self.val=val
self.next=next
class MyLinkedList:
def __init__(self):
self.head=None #不设置虚拟头节点
self.len=0
def get(self, index: int) -> int:
if index<0 or index>=self.len:
return -1
current=self.head
for _ in range(index):
current=current.next
return current.val
def addAtHead(self, val: int) -> None:
New_Head=ListNode(val=val,next=self.head)
self.head=New_Head
self.len+=1
def addAtTail(self, val: int) -> None:
current=self.head
if current==None:
addAtHead(val) # error1:self.addAtHead(val)
else:
New_Node=ListNode(val)
while(current.next!=None):
current=current.next
current.next=New_Node
self.len+=1
def addAtIndex(self, index: int, val: int) -> None:
if index<0 or index>self.len:
return
current=self.head
if current==None: #error2:index=0的时候也需要插入头节点
addAtHead(val) # 同 error1
else:
New_Node=ListNode(val)
for _ in range(index-1):
current=current.next
tmp=current.next
current.next=New_Node
New_Node.next=tmp
self.len+=1
def deleteAtIndex(self, index: int) -> None:
if index<0 or index>=self.len:
return
current=self.head
if index==0:
self.head=self.head.next
self.len-=1
else:
for _ in range(index-1):
current.next=current.next.next #error3:移动current和删除节点操作搞混了
self.len-=1
#正确代码
class ListNode:
def __init__(self,val=0,next=None):
self.val=val
self.next=next
class MyLinkedList:
def __init__(self):
self.len=0
self.head=None
def get(self, index: int) -> int:
if index<0 or index>=self.len:
return -1
current=self.head
for _ in range(index):
current=current.next #这里不用考虑current为None的情况
return current.val
def addAtHead(self, val: int) -> None:
New_Node=ListNode(val=val,next=self.head)
self.head=New_Node #记得换头节点
self.len+=1
def addAtTail(self, val: int) -> None:
New_Node=ListNode(val)
current=self.head
if current==None:
self.addAtHead(val) #空链表,加在尾步和加在头部没差
return
while(current.next!=None):
current=current.next
current.next=New_Node
self.len+=1
def addAtIndex(self, index: int, val: int) -> None:
if index<0 or index>self.len:
return
current=self.head
if index==0: #在头节点前插入
self.addAtHead(val)
return
for _ in range(index-1):
current=current.next
New_Node=ListNode(val)
tmp=current.next
current.next=New_Node
New_Node.next=tmp
self.len+=1
def deleteAtIndex(self, index: int) -> None:
if index<0 or index>=self.len:
return
if index==0:
self.head=self.head.next
self.len-=1
else:
current=self.head
for _ in range(index-1):
current=current.next
current.next=current.next.next
self.len-=1
易错点总结
- 不使用虚拟头节点的话,初始化self.head=None,而不是self.head=ListNode()
- get()的边界判断:合法下标范围是 0~len-1,所以 index<0 or index>=len的时候都是不合法的
- 调用类的成员函数的时候要在成员函数之前加上self.,调用类的属性时也是
- addAtTail需要处理current为None的情况,addAtIndex,deleteAtIndex需要单独处理在头节点前添加节点和删除头节点的代码
虚拟头节点
class ListNode:
def __init__(self,val,next=None):
self.val=val
self.next=next
class MyLinkedList:
def __init__(self):
self.len=0
self.head=ListNode(0) #虚拟头节点
def get(self, index: int) -> int:
if index<0 or index>=self.len:
return -1
else:
current=self.head.next
for _ in range(index):
current=current.next
return current.val
def addAtHead(self, val: int) -> None:
self.addAtIndex(0,val)
def addAtTail(self, val: int) -> None:
self.addAtIndex(self.len,val)
def addAtIndex(self, index: int, val: int) -> None:
if index<0 or index>self.len:
return
else:
current=self.head
New_Node=ListNode(val)
for _ in range(index): #这里是index,不用index-1
current=current.next
tmp=current.next
current.next=New_Node
New_Node.next=tmp
self.len+=1
def deleteAtIndex(self, index: int) -> None:
if index<0 or index>=self.len:
return
else:
current=self.head
for _ in range(index):
current=current.next
current.next=current.next.next
self.len-=1
深刻的体会到了用虚拟头节点的好处->不用单独处理头节点了
后续二刷
二刷的时候可以尝试一下设计一个双向链表
反转链表
双指针法
class Solution:
def reverseList(self, head: Optional[ListNode]) -> Optional[ListNode]:
if head==None:
return
else:
pre=None
cur=head
while(cur!=None):
tmp=cur.next
cur.next=pre
pre=cur
cur=tmp
return pre
这道题最大的难点就在于我在当前节点的位置,既需要有上一个节点的位置(用于翻转指向),又要有下一个节点的位置(用于遍历链表)。上一个节点的位置用pre指针指向(初始为None),下一个节点的位置可以在改变当前节点指向之前用用tmp暂存。移动pre,cur指针的时候,先移动pre,再移动cur,如果先移动了cur,pre就找不到cur的位置了。
这道题我自己做的时候,非常的绕,绕了半天没绕出来。我觉得原因应该是:
- pre 可以初始化为None(我好像不太习惯初始化为None这个操作,设计链表那个题也是)
- 移动顺序捋不清,就很容易把自己绕进去(什么时候动指针,什么时候改链表指向)
关于虚拟头节点
什么时候值得加虚拟头节点 dummy?
通常是当操作涉及“某个节点的前一个位置”时,尤其是:
- 删除节点
- 在某个节点前插入节点
- 需要统一处理头节点和普通节点
因为头节点没有前驱节点,所以它往往是特殊情况。
创建链表
class ListNode:
def __init__(self,val=0,next=None):
self.val=val
self.next=next
#倒序创建列表
node3=ListNode(3)
node2=ListNode(2,node3)
node1=ListNode(1,node2)
#先赋值,再连接
node1=ListNode(1)
node2=ListNode(2)
node3=ListNode(3)
node1.next=node2
node2.next=node3