Day3 链表Part1

链表基础

单链表节点中存储着它的值和它的后继节点。在链表中,若知道插入删除位置,由于不需要像数组那样大量的移动元素,而只是修改几个节点的后继,插入删除的时间复杂度为O(1),而查询只能遍历链表,所以查询的时间复杂度为O(n)

#单链表节点
class ListNode():
def __init__(self,val = 0,next = None):
self.val = val
self.next = next

任务

203. 移除链表元素

给你一个链表的头节点 head 和一个整数 val ,请你删除链表中所有满足 Node.val == val 的节点,并返回新的头节点。

思路

对于满足要求的头节点外的任意节点,删除逻辑是将该节点的前一个节点的后继修改为指向该节点的后继。但是当头节点满足要求时,就直接将head指向下一个节点。为了统一所有节点的逻辑,特引入虚拟节点dummy,它是头节点的前一个节点,此时所有满足要求的节点的逻辑就可以统一。具体逻辑为没找到符合要求的元素时,prev和cur均向后移动,找到时,走单个节点的删除逻辑,并且cur自然向后移动一位,继续后续处理。

class Solution:
def removeElements(self, head: Optional[ListNode], val: int) -> Optional[ListNode]:
if head == None:
return None
dummy = ListNode()
dummy.next = head
prev,cur = dummy,head
while cur!=None:
if cur.val == val:
prev.next = cur.next
else:
prev = prev.next
cur = cur.next
return dummy.next

还有一种写法是用一个遍历指针,比如cur,然后使用类似cur->next.val == val 的语句判断,符合时用 cur->next = cur->next->next 删除单个元素,不符合时cur向后移动即可,这种方式更加的简洁,但是循环条件是cur!=None and cur.next!=None, 可能也是长时间没有刷题,个人会本能的使用多加变量让逻辑更直接的方法,即上述加一个prev指针的逻辑,觉得逻辑上比较清晰,比如循环条件,由于该题比较简单,所以使用哪种都可以。

707. 设计链表

  • 实现 MyLinkedList 类:
  • MyLinkedList() 初始化 MyLinkedList 对象。
  • int get(int index) 获取链表中下标为 index 的节点的值。如果下标无效,则返回 -1 。
  • void addAtHead(int val) 将一个值为 val 的节点插入到链表中第一个元素之前。在插入完成后,新节点会成为链表的第一个节点。
  • void addAtTail(int val) 将一个值为 val 的节点追加到链表中作为链表的最后一个元素。
  • void addAtIndex(int index, int val) 将一个值为 val 的节点插入到链表中下标为 index 的节点之前。如果 index 等于链表的长度,那么该节点会被追加到链表的末尾。如果 index 比长度更大,该节点将 不会插入 到链表中。
  • void deleteAtIndex(int index) 如果下标有效,则删除链表中下标为 index 的节点。

思路

由于头节点可以由虚拟头节点得到,为了不冗余维护两个关联的信息,这里使用dummyhead维护链表的虚拟头节点。注意边界条件特别是插入位置为链表的后一个时(即index == size时)的特殊处理。这里有些逻辑写的不是很直观的原因是没有维护链表的长度,因此在一些边界条件的处理需要特别的注意,后续有时间补上维护链表长度的写法,循环逻辑会更简单(不用在循环逻辑中考虑越界问题),但是有修改size的地方需要修改。

class ListNode():
def __init__(self,val = 0,next = None):
self.val = val
self.next = next
class MyLinkedList:
def __init__(self):
self.dummyhead = ListNode()
def get(self, index: int) -> int:
if index < 0:
return -1
cur = self.dummyhead.next
while cur:
if index == 0:
return cur.val
index-=1
cur=cur.next
return -1
def addAtHead(self, val: int) -> None:
newNode = ListNode(val,self.dummyhead.next)
self.dummyhead.next = newNode
def addAtTail(self, val: int) -> None:
prev = self.dummyhead
cur = self.dummyhead.next
while cur:
prev= cur
cur = cur.next
newNode = ListNode(val)
prev.next = newNode
def addAtIndex(self, index: int, val: int) -> None:
if index < 0:
return
prev = self.dummyhead
cur = self.dummyhead.next
while prev:
if cur==None and index >0: break
if index == 0:
newNode = ListNode(val,cur)
prev.next = newNode
break
else:
index-=1
cur = cur.next
prev = prev.next
def deleteAtIndex(self, index: int) -> None:
prev = self.dummyhead
cur = self.dummyhead.next
while cur:
if index == 0:
prev.next = cur.next
break
else:
index-=1
cur = cur.next
prev = prev.next
# Your MyLinkedList object will be instantiated and called as such:
# obj = MyLinkedList()
# param_1 = obj.get(index)
# obj.addAtHead(val)
# obj.addAtTail(val)
# obj.addAtIndex(index,val)
# obj.deleteAtIndex(index)

206. 反转链表

给你单链表的头节点 head ,请你反转链表,并返回反转后的链表。

思路

利用虚拟头节点依次遍历处理即可,注意遍历后处理原来的head节点,指向为None而不是统一操作后的指向的dummy。

class Solution:
def reverseList(self, head: Optional[ListNode]) -> Optional[ListNode]:
if not head:
return None
dummy = ListNode()
dummy.next = head
prev = dummy
cur = dummy.next
while cur:
tmp = cur.next
cur.next = prev
prev = cur
cur = tmp
head.next = None
head = prev
return head

总结

今天的任务是对于链表中单个或者多个节点的增删查,这类问题不涉及像数组中一些问题的算法思想,只要按照题目描述去遍历处理即可,难点在于循环条件和单次遍历的指针变化,以及用临时变量防止修改一些指针域后无法找到需要的节点,引入dummy头节点方便统一逻辑,注意遍历链表时的边界条件,以及单次处理时next域的处理和指针的正确处理。思考时尽量做到将大多数情况做对,不必强求一次通过,这类题目很容易在边界出错,遇到特殊的节点或没考虑到的问题出错了也不用慌,调试和画图分析即可解决(如707. 设计链表 中插入index位置 index为size时,如果循环条件为cur则到达插入位置时无法进入循环,因此循环条件为prev 或循环条件不变在循环退出后再特殊处理一次也可)

posted @   haohaoscnblogs  阅读(10)  评论(0编辑  收藏  举报
点击右上角即可分享
微信分享提示