代码随想录刷题Day5

2 阅读4分钟

移除链表元素

头节点单独处理

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直接操作,否则没法直接返回头节点

注意两点:

  1. 头节点的删除方式和其他节点不太一样,需要单独处理
  2. 注意判断一下头节点非空,否则会报错 "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

易错点总结

  1. 不使用虚拟头节点的话,初始化self.head=None,而不是self.head=ListNode()
  2. get()的边界判断:合法下标范围是 0~len-1,所以 index<0 or index>=len的时候都是不合法的
  3. 调用类的成员函数的时候要在成员函数之前加上self.,调用类的属性时也是
  4. 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