Leetcode刷题笔记—双指针在链表中的面试高频考题

双指针在链表中的应用

素材来自网络

链表子串数组题,用双指针别犹豫。
双指针家三兄弟,各个都是万人迷。
快慢指针最神奇,链表操作无压力。
归并排序找中点,链表成环搞判定。

左右指针最常见,左右两端相向行。
反转数组要靠它,二分搜索是弟弟。
滑动窗口老猛男,子串问题全靠它
左右指针滑窗口,一前一后齐头进
自诩十年老司机,怎料农村道路滑。
一不小心滑到了,鼻青脸肿少颗牙。
算法思想很简单,出了bug想升天

前言:文章有点长,这是博主本人的写作风格,大家无需一口气把文章从头到尾看完,只看自己有疑惑的地方即可

力扣上给的是伪代码,对链表的题型无法在自己的本地编辑器上调试,这里给大家一个创建链表的模板可以方便大家调试

    def create_list(linklist):
        """
        :param linklist:拿来构建链表的数组
        :return: head-返回链表的头结点
        """
        node_list = []
        for i in range(len(linklist)):
            node = ListNode()
            node.val = linklist[i]
            node_list.append(node)
        head = node_list[0]
        p = head
        for node in node_list[1:]:
            p.next = node
            p = p.next
        return head

链表删除的相关题型

第一题

Leetcode203:移除链表元素:简单题

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

情形1对应解法:不借助于虚拟头结点,直接使用原来的链表来进行移除节点操作,那么删除头结点的操作需要另做考虑
python题解代码

class Solution:
    def removeElements(self, head: Optional[ListNode], val: int) -> Optional[ListNode]:
        while head and head.val == val:	# [7, 7, 7, 7]对于要移除的节点为链表的头结点需要用另外的逻辑来处理
            head = head.next
        if not head:	# 如果链表为空,直接返回
            return head
        prev = head		# prev指向头结点(用来指向待移除节点的前一个节点)
        while prev.next:	# 注意这里的条件!!!
            if prev.next.val == val:
                prev.next = prev.next.next
            else:
                prev = prev.next
        return head

情形2对应解法:设置一个虚拟头结点在进行删除操作(设置虚拟头结点后,对原链表中头结点则无需做特殊处理)
python题解代码

class Solution:
    def removeElements(self, head: Optional[ListNode], val: int) -> Optional[ListNode]:
        if not head:
            return head
        dummy = ListNode()	# 创建一个虚拟头结点
        dummy.next = head	# 和原链表连接起来
        prev = dummy		# prev指针指向虚拟头结点
        while prev.next:
            if prev.next.val == val:
                prev.next = prev.next.next
            else:
                prev = prev.next
        return dummy.next

注意上面prev指针为什么不直接指向待删除的节点,第二个while循环的条件中为什么是while prev.next而不是while prev,别急,第二题中会有详细介绍为什么

第二题

Leetcode19:删除链表的倒数第 N 个结点:中等题 详情请点击链接看原题

给你一个链表,删除链表的倒数第 n 个结点,并且返回链表的头结点

题目提示中:链表中结点的数目为sz1 <= sz <= 301 <= n <= sz,所以我们不用担心n会超出链表的长度

快慢指针大显神威

step1:题目要求返回链表的头结点,所以这里我们创建一个虚拟头结点,让它的next指针指向head【常规操作】用于最后返回链表的头结点

step2:快慢指针同时指向头结点head,快指针先行 n 步,然后快慢指针同时移动,当快指针指向最后一个节点(即 fast.next=None 的时候),慢指针指向节点的下一个节点(slow.next)即我们要删除的倒数第n个节点

step3:快慢指针同时移动之前需要先判断快指针是否指向末尾,如果快指针为None说明,n 正好等于链表的长度即要删除的倒数第 n 个节点正好是正数第1个节点
python题解代码

class Solution:
    def removeNthFromEnd(self, head: Optional[ListNode], n: int) -> Optional[ListNode]:
        dummy_head = ListNode()		# 创建一个虚拟头结点
        dummy_head.next = head
        slow, fast = head, head	# 快慢指针同时指向链表头结点
        for i in range(n):
            fast = fast.next
        if not fast:  # 如果快指针指到头了,第一个节点就是待删除的节点
            return slow.next
        while fast.next:	# 注意这里的条件 !!!
            fast = fast.next
            slow = slow.next
        slow.next = slow.next.next	# 删除 slow.next 节点操作
        return dummy_head.next

注:请同学们思考,为什么快指针指向最后一个节点的时候,慢指针指向节点的下一个节点才是我们要删除的倒数第n个节点呢?

假设链表的长度为5,我们要删除倒数第2个节点,同学们可以在纸上画一下,我们先让快指针移动2步,快慢指针同时移动,当快指针指向None的时候,慢指针正好指向倒数第2个节点(即我们要删除的节点)

But…

但由于链表的特性,对链表的删除操作而言,我们是无法直接删除某个指针所指向的节点的,但我们可以很轻松的删除该指针指向的节点的下一个节点,所以我们必须要让slow指针停留在待删除节点的前一个位置,所以我们让fast指针指向最后一个节点,slow指向待删除节点的前驱结点的时候就跳出循环

第三题

Leetcode237:删除链表中的节点:中等题 详情请点击链接看原题

有一个单链表的 head,我们想删除它其中的一个节点 node,给你一个需要删除的节点 node 。在本题中你将无法访问头结点 head
题目的限制条件:链表的所有值都是唯一的,并且保证给定的节点 node 不是链表中的最后一个节点(这里不做过多赘述,请看原题)

题目分析
我们删除链表中节点的操作一般是修改要删除节点的上一个节点的指针,将该指针指向要删除节点的下一个节点
相信同学们看到这道题的第一反应肯定很懵,要你删除链表中的节点却没有给出链表头结点head,我们如何从头结点遍历到待删除节点的上一个节点

其实不然,这道题无需你从头结点遍历到指定的节点,题目所给的node参数已经指向了要删除的节点,but 我们也无法直接删除node指针指向的节点

狸猫换太子…
第二题我们可以知道,既然我们无法直接删除给定的节点,那我们可以将所给节点的下一个节点的val拿过来覆盖自己本来的val,然后删除所给节点的下一个节点就可以达到同样的目的
python题解代码【一共就两行代码】

class Solution:
    def deleteNode(self, node):
        """
        :type node: ListNode
        :rtype: void Do not return anything, modify node in-place instead.
        """
        node.val = node.next.val
        node.next = node.next.next

第四题

Leetcode83:删除排序链表中的重复元素:简单题

给定一个已排序的链表的头 head , 删除所有重复的元素,使每个元素只出现一次,返回已排序的链表

细心的同学会发现这道题和 Leetcode26-删除有序数组中的重复项 这道题非常类似,只不过这里是有序链表,解题思路完全一致,只不过对链表和数组的处理稍有不一致,这篇文章中有对删除有序数组中的重复项的题解

python题解代码

class Solution:
    def deleteDuplicates(self, head: Optional[ListNode]) -> Optional[ListNode]:
        if not head:
            return head
        dummy = ListNode()
        dummy.next = head
        slow, fast = head, head
        while fast:
            if slow.val != fast.val:
                slow.next = fast
                slow = slow.next
            fast = fast.next
        slow.next = fast
        return dummy.next

第五题

Leetcode82:删除排序链表中的重复元素 II:中等题

给定一个已排序的链表的头 head, 删除原始链表中所有重复数字的节点,只留下不同的数字 。返回 已排序的链表

和上一题不同的是这道题中只要存在重复元素,就删除所有包含重复元素的节点,上一题对重复元素的处理是保留一个

链表合并的相关题型

第一题

Leetcode21:合并两个有序链表:简单题

将两个升序链表合并为一个新的 升序 链表并返回。新链表是通过拼接给定的两个链表的所有节点组成的

python题解代码

class Solution:
    def mergeTwoLists(self, list1: Optional[ListNode], list2: Optional[ListNode]) -> Optional[ListNode]:
        dummy = ListNode()  # 创建一个虚拟头结点
        cur = dummy  # 创建一个指针指向虚拟头结点
        l1, l2 = list1, list2
        while l1 and l2:
            if l1.val < l2.val:
                cur.next = l1
                l1 = l1.next
            else:
                cur.next = l2
                l2 = l2.next
            cur = cur.next
        cur.next = l1 if l1 else l2
        return dummy.next

第二题

Leetcode23:合并K个升序链表:困难题

给你一个链表数组,每个链表都已经按升序排列。
请你将所有链表合并到一个升序链表中,返回合并后的链表

这道题是一道困难题,关于这道题的解题方法在力扣上流行的有四种,这里主要给大家介绍两种比较难以理解的,至于其他两种,大家可以自行去力扣上查看其他博主的题解
方法1:最小堆(需要具备堆的基础概念)

方法1需要大家对堆的基础知识有一定的了解,如果不熟悉的同学请先去了解一下最大堆最小堆的概念以及如何进行堆调整,堆中节点的下沉上浮,没搞清楚这些概念之前这个方法会很难理解

分析

  • 合并后的第一个节点first一定是某个链表的头结点(因为链表已经升序排列),
  • 合并后的第二个节点可能是某个链表的头结点,也可能是first的下一个节点,那么每当我们找到一个节点值最小的节点x,我们就把节点 x.next 加入【可能是最小节点的集合】中因此我们可以利用最小堆来充当这个可能是最小节点的集合

在了解了上述概念之后其实并不用大家动手写关于堆的代码,python中有个标准库heapq里面封装好了关于堆的操作方法,现在让我来给大家介绍一下

heapq.heappush(heap, num):先创建一个空堆,然后将数据一个一个地添加到堆中。每添加一个数据后,heap都满足小顶堆的特性
heapq.heappop(heap):将堆顶的数据出堆,并将堆中剩余的数据构造成新的小顶堆
heapq自动帮我们维护了最小堆,自动帮我们弹出我们需要的最小元素

python题解代码

class Solution:
    def mergeKLists(self, lists: List[ListNode]) -> ListNode:
        import heapq
        dummy = ListNode(0)
        p = dummy
        head = []
        for i in range(len(lists)):
            if lists[i]:
                heapq.heappush(head, (lists[i].val, i))	# 将所有链表的头结点入堆(这里根据lists[i].val构建最小堆)
                lists[i] = lists[i].next	# 指针后移
        while head:
            val, idx = heapq.heappop(head) # 将堆顶的最小元素弹出
            p.next = ListNode(val)
            p = p.next
            if lists[idx]:	# 如果弹出的最小节点的下个节点不为空(则有可能为最小)
                heapq.heappush(head, (lists[idx].val, idx))	 # 继续入堆(重新自动构建最小堆)
                lists[idx] = lists[idx].next
        return dummy.next

扩展:算法能力强或者对堆的操作比较熟悉的同学可以自己手动实现 heapq.heappush()heapq.heappop()这两个函数实现的功能

heapq.heappush(heap, num)就是在heap中插入元素num然后进行上浮操作维护一个最小堆
heapq.heappop(heap)则是删除堆顶元素进行下沉操作,有兴趣的同学可以去看下博主的关于堆的另一篇文章

对了堆也是大厂面试中的高频考点,对于这道题而言,主要考察的是你对于链表的操作,在面试中你直接使用pythonheapq封好好的功能也是没有问题的

方法2:未完待续

链表相关的其他题型

第一题

Leetcode24:两两交换链表中的节点

给你一个链表,两两交换其中相邻的节点,并返回交换后链表的头节点。你必须在不修改节点内部的值的情况下完成本题(即只能进行节点交换)

方法一:分析
初始时,cur指向虚拟头结点
这里建议大家画图,能用纸笔画就用纸笔画,身边没纸笔用你的画图工具
Leetcode刷题笔记—双指针在链表中的面试高频考题_第1张图片
Leetcode刷题笔记—双指针在链表中的面试高频考题_第2张图片
注意,链表的索引只能根据上一个节点的next来找到下一个节点的位置

cur.next = cur.next.next	# 步骤一

所以执行完骤一(将头结点指向原本的第一个节点改为指向第二个节点)之后我们无法找到第一个节点

temp1 = cur.next	# 先将第一个节点的位置暂存起来

同理执行完步骤二(将第二个节点和链表断开)后我们也无法找到第三个节点

temp2 = cur.next.next.next		# 将第三个节点的位置暂存起来

方法1总结

因为要通过改变 cur节点nextcur.next.next节点next 的指向来交换cur.nextcur.next.next 节点
所以我们需要先将cur节点cur.next.next节点 这两个节点原来的指向暂存起来,即temp1 = cur.next, temp2 = cur.next.next.next

python题解代码

class Solution:
    def swapPairs(self, head: Optional[ListNode]) -> Optional[ListNode]:
        dummy = ListNode()
        dummy.next = head
        cur = dummy
        while cur.next and cur.next.next:
            temp1 = cur.next
            temp2 = cur.next.next.next
            cur.next = cur.next.next
            cur.next.next = temp1
            cur.next.next.next = temp2
            cur = cur.next.next     # cur移动两位,准备下一轮交换
        return dummy.next

方法2分析:递归法

class Solution:
    def swapPairs(self, head: Optional[ListNode]) -> Optional[ListNode]:
        if not head or not head.next:
            return head

        # 待翻转的两个node分别是pre和cur
        pre = head
        cur = head.next
        next_node = head.next.next

        cur.next = pre
        pre.next = self.swapPairs(next_node)

        return cur

第二题

Leetcode160:相交链表:简单题 详情请点击链接看原题

给你两个单链表的头节点 headAheadB ,请你找出并返回两个单链表相交的起始节点。如果两个链表不存在相交节点,返回 null

分析
严格证明取自力扣K神大佬题解
设【第一个公共节点为】node,【链表 headA】的节点数量为 a ,【链表 headB】的节点数量为 b ,【两链表的公共尾部】的节点数量为 c ,则有:

  • 头节点headAnode 前共有 a - c 个节点
  • 头节点headBnode 前共有 b - c 个节点
  • 指针 A 先遍历完链表 headA ,再开始遍历链表 headB ,当走到 node时,共走步数为:a + (b − c)
  • 指针 B 先遍历完链表 headB ,再开始遍历链表 headA,当走到 node 时,共走步数为:b + (a − c)

因为a + (b - c) = b + (a - c),此时指针A, B重合(两指针相遇即链表相交),并有两种情况:

case1:若两链表有公共尾部(即c > 0):指针AB同时指向第一个公共节点node
case2:若两链表无公共尾部(即c=0),指针A,B同时指向None

python题解代码

class Solution:
    def getIntersectionNode(self, headA: ListNode, headB: ListNode) -> ListNode:
        A, B = headA, headB
        while A != B:	# 当A==B时推出循环,两指针同时扫描到交点或同时指向 None
            A = A.next if A else headB
            B = B.next if B else headA
        return A

换种更容易理解的思路,若两链表相交,交点为c,链表A的长度为a + c,链表B的长度为b + c,两个头节点指针分别走过对方来时的路则有a + c + b + c = b + c + a + c,则证明两链表相交与一点,若不相交,两头节点互相走过对方来时的路有a + b = b + a,两指针刚好同时指向None
两个人走的路一样,走路的速度一样,两条路有交点必在交点相遇,没交点则在在终点(None)相遇

第三题

Leetcode1721:交换链表中的节点:中等题 详情请点击链接看原题

给你链表的头节点 head 和一个整数 k
交换 链表正数第 k 个节点和倒数第 k 个节点的值后,返回链表的头节点(链表 从 1 开始索引

分析
本题有两种解法,值交换(偷天换日)和节点交换,力扣上博主的题解总结的很好,这里只介绍第一种解法,节点交换需要考虑的边界条件太复杂,不想写了
方法1:值交换-找到倒数第 k 个节点和第 k 个节点后进行值交换

找第k个节点非常简单,找倒数第k个节点和前文中的删除倒数第k个节点的方法是一样的

python题解代码

class Solution:
    def swapNodes(self, head: Optional[ListNode], k: int) -> Optional[ListNode]:
        slow, fast = head, head
        # 因为从头节点(第一个节点)开始遍历,循环k-1次即指向第k个节点
        for _ in range(k - 1):
            fast = fast.next
        cur = fast
        # 同理当cur指向最后一个节点(cur.next为空)的时候,slow正好指向倒数第k个节点
        while cur.next:
            slow = slow.next
            cur = cur.next
        fast.val, slow.val = slow.val, fast.val		# 第k个节点和倒数第k个节点做值交换
        return head

第四题

Leetcode141:环形链表I:简单题 详情请点击链接看原题

给你一个链表的头节点 head ,判断链表中是否有环

分析
设有两个指针 slowfast 初始时均指向头结点,slow向后走一次,fast 向后走两次,在每轮移动之后,fastslow 的距离就会增加 1,如果链表有环,fast指针一定先进入环中(继续移动),等slow指针进入环中后,二者继续移动,fast指针和slow指针的距离+1,可是在环中,距离+1也意味着距离-1(距离拉开的同时距离也正在缩小)

根据物理学上的相对运动来说,其实就相当于slow指针静止,fast指针每次移动一个距离向slow指针靠近,所以二者是一定会在环中相遇的

python题解代码

class Solution:
    def hasCycle(self, head: Optional[ListNode]) -> bool:
        slow, fast = head, head
        while fast and fast.next:
            slow = slow.next
            fast = fast.next.next
            if fast == slow:
                return True
        return False

第五题

Leetcode142:环形链表II:中等题 详情请点击链接看原题

给定一个链表的头节点 head ,返回链表开始入环的第一个节点。 如果链表无环,则返回 null

分析
这道题的题解分析很多博主都给出非常严格的数学证明,在本文中,作者尽量用最少的文字简化分析让大家能够快速理解
Leetcode刷题笔记—双指针在链表中的面试高频考题_第3张图片

从上一题可以知道,slow走一步,fast走两步,链表有环则二者一定会相遇,假设从头结点到环形入口节点 的节点数为x。 环形入口节点到相遇节点节点数为 y 。 从相遇节点再到环形入口节点节点数为 z

从上一题的分析中我们可以知道,fast指针不可能跨过slow指针而不与slow指针相遇,所以slow指针不存在绕环一圈的情况
则相遇时slow指针走过的节点数为 x + y, fast 指针走过的节点数:x + y + n (y + z)x可能很长,环很短,fast指针在环中走了n圈才遇到slow指针

fast指针走过的节点数 = slow指针走过的节点数 * 2

(x + y) * 2 = x + y + n (y + z),利用初中所学的数学知识进行化简,x = (n - 1) (y + z) + z 注意这里 n 一定是大于等于 1 的,因为 fast 指针至少要多走一圈才能相遇 slow 指针,当 n1 的时候,公式就化解为 x = z

x = z:意味着从头结点出发一个指针,从相遇节点也出发一个指针,这两个指针每次只走一个节点, 那么当这两个指针相遇的时候就是环形入口的节点

到此,我们可以在相遇节点处定义一个指针index1,在头结点处定义一个指针index2index1index2 同时移动,每次移动一个节点,那么它们相遇的地方就是环形的入口节点,如果 n > 1 则说明 index1 指针在环里多转了 (n - 1) 圈再遇到 index2,相遇节点依然是环形的入口节点

python题解代码

class Solution:
    def detectCycle(self, head: Optional[ListNode]) -> Optional[ListNode]:
        slow, fast = head, head
        while fast and fast.next:
            fast = fast.next.next
            slow = slow.next
            if fast == slow:
                index1 = fast	# 定义index1指向相遇节点
                index2 = head	# 定义index2指向头结点
                while index2 != index1:	# 同时移动index1和index2直到二者相遇
                    index1 = index1.next
                    index2 = index2.next
                return index1
        return None

第六题

Leetcode206:反转链表:简单题 详情请点击链接看原题

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

方法1: 头插法反转单链表
python题解代码

class Solution:
    def reverseList(self, head: Optional[ListNode]) -> Optional[ListNode]:
        dummy = ListNode()
        cur_node = head
        while cur_node:
            next_node = cur_node.next      # 将头结点断链,保存剩余链表到 next_node
            cur_node.next = dummy.next
            dummy.next = cur_node
            cur_node = next_node       # cur_node 重新指向剩余链表的头结点
        return dummy.next

方法2: 递归法

链表应用相关题型

第一题

Leetcode2:两数相加:中等题 详情请点击链接看原题
使用链表实现大数加法

给你两个 非空 的链表,表示两个非负的整数。它们每位数字都是按照 逆序 的方式存储的,并且每个节点只能存储 一位 数字。
请你将两个数相加,并以相同形式返回一个表示和的链表。
你可以假设除了数字 0 之外,这两个数都不会以 0 开头

分析

  1. 将两个链表看成是相同长度进行遍历,如果一个数较短则在前面补0987 + 23 = 987 + 023

数不以0开头,但是当某个数较短的时候需要在前面补0数字在链表中是按照 逆序 的方式存储的,所以当我们遍历到链表末尾的时候若其中一个链表不为空另一个链表为空,则需要在链表的后面补0为了达到前面说的在数字前面补 0 保证数字对齐
7——>8——>9
3——>2——>0

  1. 对每一位计算的同时需要考虑上一位的进位问题,并当前位计算结束后同样需要更新进位值
  2. 如果两个链表遍历完毕后进位值为 1,则在新链表最前方添加结点 1

python题解代码

class Solution:
    def addTwoNumbers(self, l1: Optional[ListNode], l2: Optional[ListNode]) -> Optional[ListNode]:
        dummy = ListNode()
        cur_node = dummy
        carry = 0       # 进位
        while l1 or l2:
            x = 0 if not l1 else l1.val   # 如果一个链表为空另一个不为空则需要在后面补0
            y = 0 if not l2 else l2.val

            total = x + y + carry	# 从两个数字的末尾(两个链表的头)开始计算

            carry = total / 10		# 记录当前两个数之和的进位
            total %= 10				# 取个位
            cur_node.next = ListNode(total)	# 将实例化的结点放在头结点dummy之后
            cur_node = cur_node.next		# cur_node指针右移准备计算下一位
            if l1:
                l1 = l1.next	# l1不为空则l1右移
            if l2:
                l2 = l2.next   # l2不为空则l2右移
        if carry == 1:   # 如果两个链表遍历完毕后进位值为 1,则在新链表最末尾(即数的最高位)添加结点 1
            cur_node.next = ListNode(carry)

        return dummy.next

第二题

Leetcode146:LRU缓存:中等题 详情请点击链接看原题

请你设计并实现一个满足 LRU (最近最少使用) 缓存 约束的数据结构

博主水平有限,算法设计思路来源于力扣大佬题解,博主只在这里做个总结,方便大家和本人理解
要让putget方法的时间复杂度为O(1)cache这个数据结构的必要条件

1.cache中的元素必须有时序以区分最近使用和久未使用的数据,容量满了以后要删除最久未使用的那个元素
2.我们要在cache中快速找某个 key 是否已存在并得到对应的 val
3.每次访问cache中的某个key,需要将这个元素变为最近使用的,也就是说 cache要支持在任意位置快速插入和删除元素

这个数据接口应具备如下节点:
1.支持快速查找
2.保存访问的先后顺序,在末尾加入一项,去除最前端一项
3.将队列中的某一项移到末尾

哈希表查找快,但数据无固定顺序,链表有顺序之分但是查找慢,所以二者结合一下形成一种新的数据结构,这就是大名鼎鼎的哈希链表 LinkedHashMap

图片素材来源于网络
Leetcode刷题笔记—双指针在链表中的面试高频考题_第4张图片

1.默认put操作从链表尾部添加元素,那么显然越靠近尾部的元素就是最近使用的,越靠头部的元素就是最久未使用的(因为是双链表,你也可以在链表头部添加元素,把头部元素当成是最近使用的元素)
2.对于某一个key,我们可以通过哈希表快速定位到链表中的结点从而取得对应的val
3.链表虽然支持通过修改指针在任意位置插入和删除,只不过传统的链表无法按照索引快速访问某一个位置的元素,而这里借助于哈希表,通过key快速映射到任意一个链表结点,然后进行插入和删除

这里为啥用双链表而不是单链表?
从前几题的分析我们可以知道删除一个节点不光要得到该节点本身的指针,也需要操作其前驱结点的指针,而我们用哈希表只能定位到需删除的节点,无法定位到其前驱结点,而双向链表就支持直接查找前驱,保证操作的时间复杂度为O(1)

python题解代码

class ListNode(object):		# 定义双链表的结点类(注意这里为啥要使用双向链表)
    def __init__(self, key=None, value=None):
        self.key = key
        self.value = value
        self.prev = None
        self.next = None


class LRUCache:
    def __init__(self, capacity: int):
        self.capacity = capacity
        self.hashmap = {}
        # 初始化一个虚拟头结点和虚拟尾结点用作表头和表尾,其余节点放在中间
        self.head = ListNode()	
        self.tail = ListNode()
        self.head.next = self.tail
        self.tail.prev = self.head

    def move_node_to_tail(self, key):
        # 1.先根据 key 在哈希表中找到对应的双链表中的节点
        node = self.hashmap[key]
        # 2.将该节点的前后节点相连
        node.prev.next = node.next
        node.next.prev = node.prev

        # 3.之后将node插入到尾结点前
        node.prev = self.tail.prev
        node.next = self.tail
        self.tail.prev.next = node
        self.tail.prev = node

    def get(self, key: int) -> int:
        if key in self.hashmap:
            # 如果已经在链表中了就把它移动到末尾(变成最新访问的)
            self.move_node_to_tail(key)
        res = self.hashmap.get(key, -1)	# 在哈希表中查找对应的key
        if res == -1:
            return res
        else:
            return res.value	# 返回链表中对应的value

    def put(self, key: int, value: int) -> None:
        # 1.如果key本身已经在哈希表中就不需要在链表中加入新的结点,但是需要更新字典对应结点的value
        if key in self.hashmap:
            self.hashmap[key].value = value
            # 2.之后将刚刚访问的该结点移到链表末尾
            self.move_node_to_tail(key)
        else:
        	# 1.如果容量已满则去掉最久未使用的节点
            if len(self.hashmap) == self.capacity:
                # 1.1 去掉哈希表对应项
                self.hashmap.pop(self.head.next.key)
                # 1.2 去掉最久没有被访问过的结点,即头结点之后的结点
                self.head.next = self.head.next.next
                self.head.next.prev = self.head

            # 如果不在且容量未满的情况下就插入到尾结点之前
            new = ListNode(key, value)
            self.hashmap[key] = new
            new.prev = self.tail.prev
            new.next = self.tail
            self.tail.prev.next = new
            self.tail.prev = new

其实对于本题来说核心考察点就是双链表的插入和删除操作,需要注意细节和前后顺序

总结

本文给大家总结了大厂面试中喜欢考察的关于链表的相关题型,对于链表题,双指针的快慢指针通常是最常用的,注意一下链表的插入和删除操作中的顺序要求,顺序乱了整个链表就断裂了,主要帮助大家总结了考点以及清晰明了的题解方便大家理解,如果你觉得本文对你有帮助的话,还请点赞收藏转发,最后,祝大家都能找到心仪的工作呀~

你可能感兴趣的:(Leetcode刷题笔记,leetcode,笔记,链表,python)