前段时间太忙了,这几天努力补卡中,上一天是休息日,今天开始学习链表啦!继续加油!
203.移除链表元素
文章讲解:代码随想录
视频讲解:手把手带你学会操作链表 | LeetCode:203.移除链表元素_哔哩哔哩_bilibili
思路:读完题目并且结合刚刚读完的关于链表的知识,能够想到删除链表中的一个节点只需要把指针越过要删除的节点指向下一个节点即可。自己尝试了半天测试都没有通过,看完文章解析才明白自己没有对头节点和非头节点进行区分。
原链表直接删除:分别对头节点和非头节点两种情况考虑,头节点只需要将head移到下一位即可,非头节点则需要将指针越过要删除的节点指向其后一个节点。值得注意的是,python,java会自动释放掉删除的内存,c和c++需要手动释放。下面是代码实现:
# 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]:
# 头节点
while head is not None and head.val == val: # 当头节点不为空且是需要删除的节点
head = head.next # 将下一个节点作为新的头节点
# 非头节点
current = head # 此时从新的head开始
while current is not None and current.next is not None:
if current.next.val == val: # 如果此时的节点的下一个节点的值满足题意要删除
current.next = current.next.next # 就把节点的next指针移向下下一个节点
else:
current = current.next # 看下一个节点
return head
另外还有一种方法可以统一的对所有节点的删除进行操作,那就是设置一个虚拟头节点。
虚拟头节点:设置一个虚拟头节点,这样原链表的所有节点都可以采用同样的原理方式进行删除操作,即将指针越过要删除的节点指向其后一个节点。以下为代码实现:
# 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)
# 遍历链表,并且删除满足题意的节点
current = dummy_head
while current.next is not None and current is not None:
if current.next.val == val:
current.next = current.next.next
else:
current = current.next
return dummy_head.next
两种方法的时间复杂度都为O(n)。
707.设计链表
文章讲解:代码随想录
视频讲解:帮你把链表操作学个通透!LeetCode:707.设计链表_哔哩哔哩_bilibili
思路:
虚拟头指针单链表法:这道题直接看了视频讲解,其主要思路仍旧沿用了上一题的虚拟头指针的方法,这样在进行各种操作时都比较方便。这道题包括了五种对于链表的基本操作,下面我将详细总结一下每道小题的思路。
1.获取链表中下标为index的值:根据题目要求,要返回下标为index的节点的val值,如果index无效要返回-1,所以首先判断index的有效性,如果index小于0或者超出链表范围就返回-1。其次,初始化current指针指在虚拟头节点的下一个节点,即原链表的头指针,循环遍历链表中的index,每次都要将current指向下一个节点,返回下标为index的节点的val值。
2.在头节点前插入值为val的节点:因为我们提前设置了虚拟头节点,所以虚拟头节点指向的下一个节点就是头节点。此时要在头节点前插入一个节点,要先将这个要插入的节点指向虚拟节点的下一个节点(头节点),代码中写的不是很明显,ListNode(val, self.dummy_head.next) 在这句代码里,可以看到新节点的值为val,指针指向了虚拟节点的下一个节点(头节点),这里解释一下为什么要先让新节点指向头节点,而不是让虚拟节点先指向新节点:因为一旦先让虚拟节点先指向新节点后dummy_head.next表示的就是新节点了,无法再让新节点指向头节点,因为头节点我们只能用dummy_head.next来表示。
接着将虚拟节点指向要插入的节点。
解释的部分一定要仔细理解,这个指向的先后顺序很重要,后边的题目还会用到。
3.在最后一个节点后追加一个值为val的节点:关于这道题,我们首先要做的就是找到最后一个节点,所以循环,当current.next指向的为空时,说明此时的current就是最后一个节点。
找到后,将current指向要追加的节点。
4.将值为val的节点插入下标为index的节点之前:首先要先判断index的合法性,其后插入的原理和第二题相同,初始化current为虚拟头节点,遍历链表中的每一个index,更新current为index的前一个节点,current.next才是下标为index的节点,此时current直接指向要插入的节点就完成了在index前插入节点的操作。
注意:所有的操作都要找到index的前一个节点进行操作。
5.删除下标为index的节点:同样先判断index的合法性,然后循环遍历,同理更新current为index的前一个节点,current.next才是下标为index的节点。然后将下标为index的节点直接指向下一个节点,就删除了下标为index的节点。
总的来说,有几个地方还是比较绕的,可以自己画图再好好思考一下。下面是代码:
# 定义链表
class ListNode:
def __init__(self, val=0, next=None):
self.val = val
self.next = next
class MyLinkedList:
# 设置一个虚拟头节点方便操作,定义链表的长度
def __init__(self):
self.dummy_head = ListNode()
self.size = 0
# 获取链表中下标为index的值
def get(self, index: int) -> int:
if index < 0 or index > self.size:
return -1
current = self.dummy_head.next # 从链表的头节点开始遍历
for i in range(index):
current = current.next
return current.val
# 在头节点前插入值为val的节点
def addAtHead(self, val: int) -> None:
# 头节点不用找,就是虚拟头节点的下一个节点,将虚拟头节点的指针指向值为val的节点
self.dummy_head.next = ListNode(val, self.dummy_head.next)
# 链表长度加一
self.size += 1
# 在最后一个节点后追加一个值为val的节点
def addAtTail(self, val: int) -> None:
current = self.dummy_head
# 找最后一个节点,当current.next为空的时候停止循环
while current.next:
current = current.next
# 找到后,将指针指向值为val的节点
current.next = ListNode(val)
self.size += 1
# 将值为val的节点插入下标为index的节点之前
def addAtIndex(self, index: int, val: int) -> None:
# 先判断index的合法性
if index < 0 or index > self.size:
return -1
# 下面的操作与插入头节点原理相同
current = self.dummy_head
for i in range(index):
current = current.next
current.next = ListNode(val, current.next)
self.size += 1
# 删除下标为index的节点
def deleteAtIndex(self, index: int) -> None:
# 先判断index的合法性
if index < 0 or index >= self.size:
return -1
# 循环遍历,current为下标为index节点的前一个节点
current = self.dummy_head
for i in range(index):
current = current.next
current.next = current.next.next
self.size -= 1
# 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. 反转链表
文章讲解:代码随想录
视频讲解:帮你拿下反转链表 | LeetCode:206.反转链表 | 双指针法 | 递归法_哔哩哔哩_bilibili
思路:要反转链表,如果再重新创建一个新链表会占用内存,所以最直接的方法就是直接将指针换个方向。有以下两种办法:
双指针法:初始化两个指针,current指向头指针,pre指向头指针前面,为空。先保存一下cuurent的下一个节点,因为之后要移动current要知道它的下一个节点。保存完之后,将current指向pre这样就完成了一次反转。然后先移动pre往前一位即current所在位置,再将current往前一位即刚刚保存过的节点位置。循环操作整个链表,就完成了链表的反转。代码如下:
# Definition for singly-linked list.
# class ListNode:
# def __init__(self, val=0, next=None):
# self.val = val
# self.next = next
class Solution:
def reverseList(self, head: Optional[ListNode]) -> Optional[ListNode]:
# 初始化两个指针,current指向头指针,pre指向头指针前面,为空
current = head
pre = None
# 循环改变节点的指针指向
while current:
tmp = current.next # 先保存一下current的下一个节点,因为要开始反转指针了
current.next = pre # 此时把current节点的指针反转,指向了头节点前面的空节点Null
pre = current # 将pre向后移动一位,移到current的位置
current = tmp # 将current也向后移动一位,移到之前保存好的current的下一个节点
return pre
递归法:逻辑和双指针是相同的,但是代码相对难理解。自己要多理解几遍。
# Definition for singly-linked list.
# class ListNode:
# def __init__(self, val=0, next=None):
# self.val = val
# self.next = next
class Solution:
def reverseList(self, head: Optional[ListNode]) -> Optional[ListNode]:
# 这里实现的就是递归
return self.reverse(head, None)
def reverse(self, current: Optional[ListNode], pre: Optional[ListNode]) -> Optional[ListNode]:
# 这里就是current为空的话,也在头指针前一个位置即pre的位置
if current == None:
return pre
tmp = current.next # 同样保存current的后一个节点的位置
current.next = pre # 此时反转,将current指向pre的位置
return self.reverse(tmp, current)
两种方法的时间复杂度都为O(n)。
今日心得:
首先今天学习到了在对链表进行一些基础操作时,可以使用设置虚拟头节点的方式,可以更加方便我们进行各种操作。
其次有两个要注意的点,一是在对链表进行循环遍历的时候要注意current.next才是要操作的对象,二是在进行增加节点操作时,要先将新节点指向current.next再将current指向新节点。
最后反转链表的原理不难理解,对递归方法还要多复习几遍。