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 或循环条件不变在循环退出后再特殊处理一次也可)
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】凌霞软件回馈社区,博客园 & 1Panel & Halo 联合会员上线
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】博客园社区专享云产品让利特惠,阿里云新客6.5折上折
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步