链表经典题目

一、虚拟头结点

题目203. 移除链表元素.
给你一个链表的头节点 head 和一个整数 val ,请你删除链表中所有满足 Node.val == val 的节点,并返回 新的头节点 。
在这里插入图片描述
在链表中想要删除一个节点,让节点(待删除节点的上一个节点)的next指针指向下下个节点即可(node.next = node.next.next)。
除了正常的删除,我们还需要考虑特殊的情况,比如,删除的是头节点该怎么办?
第一种方式:直接使用原来的链表进行删除操作
即将头节点向后移动一位即可
在这里插入图片描述
在这里插入图片描述
然后再将原本的头节点删除
虽然这样可以移除头节点,但是因为和删除其他节点的方式不同,需要单独写一段逻辑来处理,似乎有点麻烦。
第二种方式:设置一个虚拟头节点
也就是给链表添加一个虚拟头结点作为新的头节点,此时要移除原本的头节点,就和移动其他节点的方式一致了。
在这里插入图片描述
最后要返回的是dummyNode.next

# 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: ListNode, val: int) -> ListNode:
        dummyNode = ListNode(0)
        dummyNode.next = head
        cur = dummyNode

        while cur.next:
            if cur.next.val == val:
                cur.next = cur.next.next
            else:
                cur = cur.next
        
        return dummyNode.next

二、链表基本操作

707. 设计链表.
在链表类中实现这些功能:

  • get(index):获取链表中第 index 个节点的值。如果索引无效,则返回-1。
  • addAtHead(val):在链表的第一个元素之前添加一个值为 val 的节点。插入后,新节点将成为链表的第一个节点。
  • addAtTail(val):将值为 val 的节点追加到链表的最后一个元素。
  • addAtIndex(index,val):在链表中的第index 个节点之前添加值为 val 的节点。如果 index 等于链表的长度,则该节点将附加到链表的末尾。如果 index大于链表长度,则不会插入节点。如果index小于0,则在头部插入节点。
  • deleteAtIndex(index):如果索引 index有效,则删除链表中的第 index 个节点。

这里可以用到上一节的虚拟头结点,使链表结构永不为空,便于统一代码逻辑(否则插入删除都需要额外的逻辑判断)
对于插入,由于我们创建了虚拟头结点,addAtHead,addAtTail的操作都可以通过addAtIndex来实现,即在有效index范围内找到插入位置的前驱结点pre
删除也一样,在有效index范围内找到删除位置的前驱结点pre,令pre.next = pre.next.next即可删除目标节点
当然,我们还需要自己定义节点类

# 定义节点类
class ListNode:
    def __init__(self, val):
        self.val = val
        self.next = None

class MyLinkedList:

    def __init__(self):
        self.size = 0
        self.head = ListNode(0)


    def get(self, index: int) -> int:
        """
        Get the value of the index-th node in the linked list. If the index is invalid, return -1.
        """
        if index < 0 or index >= self.size:
            return -1
        cur = self.head
        for _ in range(index+1):
            cur = cur.next
        return cur.val
            

    def addAtHead(self, val: int) -> None:
        """
        Add a node of value val before the first element of the linked list. After the insertion, the new node will be the first node of the linked list.
        """
        self.addAtIndex(0, val)


    def addAtTail(self, val: int) -> None:
        """
        Append a node of value val to the last element of the linked list.
        """
        self.addAtIndex(self.size, val)

    def addAtIndex(self, index: int, val: int) -> None:
        """
        Add a node of value val before the index-th node in the linked list. If index equals to the length of linked list, the node will be appended to the end of linked list. If index is greater than the length, the node will not be inserted.
        """
        # 如果index大于链表长度则不会插入
        if index > self.size:
            return
        
        # 如果index是负数则插入链表头部
        if index < 0:
            index = 0
        
        self.size += 1
        # 找到要添加的前一个节点位置
        pre = self.head
        for _ in range(index):
            pre = pre.next
        # 创建要添加的节点
        node = ListNode(val)
        # 插入
        node.next = pre.next
        pre.next = node


    def deleteAtIndex(self, index: int) -> None:
        """
        Delete the index-th node in the linked list, if the index is valid.
        """
        if index < 0 or index >= self.size:
            return
        
        self.size -= 1
        # 找到要删除的前一个节点位置
        pre = self.head
        for _ in range(index):
            pre = pre.next
        # 删除
        pre.next = pre.next.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 = [1,2,3,4,5]
输出:[5,4,3,2,1]

可以再定义一个链表,实现对链表元素的反转,但这样额外消耗了内存空间。
其实比较容易想到的是直接改变链表中节点next指针的指向,原地反转链表:
在这里插入图片描述
原始链表头节点是元素1,反转后链表头节点为元素5,每个链表节点的next指针都改变了。
具体该如何反转呢?看一下动画演示:
在这里插入图片描述
我们定义了两个指针:

  • cur指针指向头节点
  • pre指针初始为null

然后开始反转:

  1. 使用一个临时指针tmp保存cur.next节点,因为我们要将cur的下一个节点指向pre,以此反转第一个节点,如果不先保存就会失去该节点
  2. 在满足条件的情况下往后移动cur和pre指针 ,令pre等于当前节点cur,cur指向原本的下一个节点,也就是保存的tmp
  3. 反转结束后返回的是pre指针,因为此时pre指针指向的是新的头节点
class Solution:
    def reverseList(self, head: ListNode) -> ListNode:
        cur = head
        pre = None

        while cur:
            tmp = cur.next
            cur.next = pre
            pre = cur
            cur = tmp
        return pre

相关题目:234. 回文链表.

四、 双指针法+虚拟头结点

给你一个链表,删除链表的倒数第 n 个结点,并且返回链表的头结点。
输入:head = [1,2,3,4,5], n = 2
输出:[1,2,3,5]

双指针法:要删除倒数第n个节点,先让fast指针移动n步,然后slow从头结点开始,和fast同时移动,这样他们之间的距离就是n,当fast走到链表末尾时,slow指向的即为倒数第n个节点,删除即可。
虚拟头结点:定义fast和slow指针初始值为虚拟头结点,方便处理删除实际头节点的逻辑。具体步骤:

  • fast先走n+1步,让slow能够指向被删除节点的上一个节点,方便我们做删除操作;
  • fast和slow同时移动,直到fast指向末尾(None)
  • 删除slow的下一个节点
class Solution:
    def removeNthFromEnd(self, head: ListNode, n: int) -> ListNode:
        dummyNode = ListNode(0)
        dummyNode.next = head
        fast = dummyNode
        slow = dummyNode

        for _ in range(n+1):
            fast = fast.next
        
        while fast:
            fast = fast.next
            slow = slow.next
        
        slow.next = slow.next.next
        return dummyNode.next

双指针题目: 面试题 02.07. 链表相交.

五、数学证明

环形链表 II.
给定一个链表,返回链表开始入环的第一个节点。 如果链表无环,则返回 null。
在这里插入图片描述
输入:head = [3,2,0,-4]
输出:返回索引为 1 的链表节点
解释:链表中有一个环,其尾部连接到第二个节点。

本题考察链表操作以及数学运算:

  • 判断链表是否环
  • 如果有环,如何找到这个环的入口

1.判断链表是否有环
使用快慢指针,头节点出发,快指针每次移动两个节点,慢指针每次移动一个节点,如果两个指针相遇,说明链表有环。

  • 为什么这样就可以判断是否有环?
    快指针一定先进入环中,如果快慢指针相遇,一定是在环中;

  • 为什么两个指针一定会相遇?
    快指针走两步,慢指针走一步,相当于快指针在一个节点一个节点地接近慢指针,因此两者一定会相遇
    在这里插入图片描述
    此时fast走两步,slow走一步,两者就相遇了
    动图:
    在这里插入图片描述
    2.如何找到环的入口?
    假设从头结点到环形入口节点距离为x。
    环形入口节点到指针相遇节点距离为y。
    从相遇节点再到环形入口节点距离为 z:
    在这里插入图片描述
    那么相遇时:
    slow指针走过的距离为: x + y;
    fast指针走过的距离:x + y + n (y + z);n为fast指针遇到slow时在环内走了n圈
    又fast一次走两步,slow一次走一步,所以:
    (x + y) * 2 = x + y + n (y + z)
    化简得:
    x = (n - 1) (y + z) + z
    x表示头结点到环形入口节点的的距离,n≥1,即快指针至少要绕一圈才能和慢指针相遇
    此时,让一个指针位于头节点,一个指针位于相遇节点,相同速度同时移动,相遇点即为环形入口处节点:
    在这里插入图片描述

 class Solution:
    def detectCycle(self, head: ListNode) -> ListNode:
        slow, fast = head, head
        while fast and fast.next:
            fast = fast.next.next
            slow = slow.next
            # 如果相遇
            if slow == fast:
                fast = head
                while fast != slow:
                    fast = fast.next
                    slow = slow.next
                return fast
        return None
  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值