力扣刷题记录&整理——(六)Linked List


前言

整理力扣刷题思路。

  • 语言:python
  • 题库:来自neetcode: link

一、预备知识

1.链表

链表(Linked list)是一种常见的基础数据结构,用于存储一系列数据元素。与数组不同,链表中的数据元素不是按顺序存储,而是通过指针相互链接在一起。

链表的基本思想

  • 链表通过不连续的存储方式,自适应内存大小,以及指针的灵活使用,巧妙地简化了数据的插入、删除和移动操作。
  • 链表的基本思想是利用结构体的设置,额外开辟出一份内存空间作为指针,它总是指向下一个节点。
  • 一个个节点通过 NEXT 指针相互串联,形成了链表。

单链表的定义

  • 单链表是一种链式存取的数据结构,由一系列节点组成。
  • 每个节点包含两部分:
    • DATA:自定义的数据类型,可以是整数、字符、结构体等。
    • NEXT:指向下一个链表节点的指针。
# Definition for singly-linked list.
# class ListNode:
#     def __init__(self, val=0, next=None):
#         self.val = val
#         self.next = next

参考python代码的定义,单链表就是给定一个头节点(head),可以访问到节点值(head.val)和下一个节点(head.next)。

链表的优点

  • 插入和删除操作只需要修改指针所指向的区域,不需要进行大量的数据移动操作。
  • 链表适用于动态内存分配,不需要预先分配固定大小的空间。

链表的类型

  1. 单链表:节点之间只有一个指针,指向下一个节点。
  2. 双链表:每个节点有两个指针,分别指向前一个节点和后一个节点。
  3. 循环链表:尾节点的 NEXT 指针指向头节点,形成一个闭环。

二、解题思路

1.迭代

206.reverse-linked-list

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

# 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]:
        #如果链表为空或者只有一个值,那么反转还是自身
        if not head or not head.next:
            return head

        newhead = None
        while head:
            #切断原节点与其下一个节点的联系,建立新联系
            tmp = head.next
            head.next = newhead
            newhead = head
            #处理下一个节点
            head = tmp
        return newhead

25.reverse-nodes-in-k-group

给你链表的头节点 head ,每 k 个节点一组进行翻转,请你返回修改后的链表。

k 是一个正整数,它的值小于或等于链表的长度。如果节点总数不是 k 的整数倍,那么请将最后剩余的节点保持原有顺序。

你不能只是单纯的改变节点内部的值,而是需要实际进行节点交换。
link

class Solution:
    def reverseKGroup(self, head: Optional[ListNode], k: int) -> Optional[ListNode]:
        if not head or k==1:
            return head

        vec = []
        tmp = dummy = ListNode(0,head)
        while tmp:
            vec.append(tmp)
            tmp = tmp.next
        
        m = (len(vec)-1)//k
        ans = dummy
        for i in range(m):
            #反转第i组的k个节点
            cur = vec[i*k+1]
            vec[(i+1)*k].next = None
            ans.next = self.reverseList(cur)
            #将ans更新为第i组节点反转前的第一个节点
            #也就是反转后的最后一个节点
            ans = cur
        
        #连接最后剩余的少于k的节点
        if (len(vec)-1)%k != 0:
            ans.next = vec[m*k+1]
        return dummy.next
            
    
    def reverseList(self, head):
        newhead = None
        while head:
            tmp = head.next
            head.next = newhead
            newhead = head
            head = tmp
        return newhead

这一题是上面反转链表的进阶

21.merge-two-sorted-lists

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

class Solution:
    def mergeTwoLists(self, list1: Optional[ListNode], list2: Optional[ListNode]) -> Optional[ListNode]:
        if not list1:
            return list2
        if not list2:
            return list1
        
        newlist = ListNode(0)
        tmp = newlist

        while list1 and list2:
            if list1.val <= list2.val:
                tmp.next = list1
                list1 = list1.next
            else:
                tmp.next = list2
                list2 = list2.next
            tmp = tmp.next
        
        tmp.next = list1 if list1 else list2
        return newlist.next

2.递归

206

class Solution:
    def reverseList(self, head: Optional[ListNode]) -> Optional[ListNode]:
        if head==None or head.next==None:
            return head
		# 递归到最后这里的cur就是最后一个节点
        cur = self.reverseList(head.next)
		# 如果链表是 1->2->3->4->5,那么此时的cur就是5
		# 此时的head是4,head的下一个是5,下下一个是空
		# 所以head.next.next 就是5->4
        head.next.next = head
		# 防止链表循环,需要将head.next设置为空
        head.next = None
		# 每层递归函数都返回cur,也就是最后一个节点
        return cur

21

class Solution:
    def mergeTwoLists(self, list1: Optional[ListNode], list2: Optional[ListNode]) -> Optional[ListNode]:
        if not list1:
            return list2
        if not list2:
            return list1
        
        if list1.val <= list2.val:
            list1.next = self.mergeTwoLists(list1.next, list2)
            return list1
        else:
            list2.next = self.mergeTwoLists(list1, list2.next)
            return list2

23.merge-k-sorted-lists

给你一个链表数组,每个链表都已经按升序排列。

请你将所有链表合并到一个升序链表中,返回合并后的链表。

link

class Solution:
    def mergeKLists(self, lists: List[Optional[ListNode]]) -> Optional[ListNode]:
        if not lists:
            return None
        n = len(lists)
        if n==1:
            return lists[0]
        
        left = self.mergeKLists(lists[:n//2])
        right = self.mergeKLists(lists[n//2:])
        return self.mergeTwoLists(left,right)

    def mergeTwoLists(self, list1, list2):
        if not list1:
            return list2
        if not list2:
            return list1

        if list1.val <= list2.val:
            list1.next = self.mergeTwoLists(list1.next, list2)
            return list1
        else:
            list2.next = self.mergeTwoLists(list1, list2.next)
            return list2

借用21题的合并两个链表,为了减少不必要的计算,使用二分法合并

3.快慢指针

141.linked-list-cycle

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

如果链表中有某个节点,可以通过连续跟踪 next 指针再次到达,则链表中存在环。 为了表示给定链表中的环,评测系统内部使用整数 pos 来表示链表尾连接到链表中的位置(索引从 0 开始)。注意:pos 不作为参数进行传递 。仅仅是为了标识链表的实际情况。

如果链表中存在环 ,则返回 true 。 否则,返回 false 。
link

class Solution:
    def hasCycle(self, head: Optional[ListNode]) -> bool:
        if not head or not head.next:
            return False
        
        #快指针的速度比慢指针大1
        #若存在环,一定会追及
        fast,slow = head.next,head
        while fast and fast.next:
            if fast == slow:
                return True
            fast = fast.next.next
            slow = slow.next
        #若不存在环,快指针会先走出链表
        return False

142.linked-list-cycle-ii

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

如果链表中有某个节点,可以通过连续跟踪 next 指针再次到达,则链表中存在环。 为了表示给定链表中的环,评测系统内部使用整数 pos 来表示链表尾连接到链表中的位置(索引从 0 开始)。如果 pos 是 -1,则在该链表中没有环。注意:pos 不作为参数进行传递,仅仅是为了标识链表的实际情况。

不允许修改 链表。

link

# Definition for singly-linked list.
# class ListNode:
#     def __init__(self, x):
#         self.val = x
#         self.next = None

class Solution:
    def detectCycle(self, head: Optional[ListNode]) -> Optional[ListNode]:
        fast,slow = head,head
        while True:
            if not fast or not fast.next: 
                return None
            fast = fast.next.next
            slow = slow.next
            if fast == slow:
                break

        cur = head
        while cur != slow:
            cur = cur.next
            slow = slow.next
        return slow

两次指针,参考:link

287.find-the-duplicate-number

给定一个包含 n + 1 个整数的数组 nums ,其数字都在 [1, n] 范围内(包括 1 和 n),可知至少存在一个重复的整数。

假设 nums 只有 一个重复的整数 ,返回 这个重复的数 。

你设计的解决方案必须 不修改 数组 nums 且只用常量级 O(1) 的额外空间。
link

class Solution:
    def findDuplicate(self, nums: List[int]) -> int:
        def next(i):
            return nums[i]
        fast,slow = 0,0
        while True:
            fast = next(next(fast))
            slow = next(slow)
            if fast == slow:
                break
        
        cur = 0
        while cur != slow:
            cur = next(cur)
            slow = next(slow)
        return slow

类似环形链表,参考:link

19.remove-nth-node-from-end-of-list

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

class Solution:
    def removeNthFromEnd(self, head: Optional[ListNode], n: int) -> Optional[ListNode]:
        if not head or not head.next:
            return None
        
        fast = slow = dummy = ListNode(0,head)
        #快指针先走n+1步
        for _ in range(n+1):
            fast = fast.next
        
        #快指针走出链表时,慢指针刚好在要删除的节点前一位
        while fast:
            fast = fast.next
            slow = slow.next
        slow.next = slow.next.next
        return dummy.next

143.reorder-list

给定一个单链表 L 的头节点 head ,单链表 L 表示为:

L0 → L1 → … → Ln - 1 → Ln
请将其重新排列后变为:

L0 → Ln → L1 → Ln - 1 → L2 → Ln - 2 → …
不能只是单纯的改变节点内部的值,而是需要实际的进行节点交换。
link

class Solution:
    def reorderList(self, head: Optional[ListNode]) -> None:
        """
        Do not return anything, modify head in-place instead.
        """
        if not head or not head.next or not head.next.next:
            return
        
        middle = self.findMiddle(head)
        fir = head
        sec = self.reverseList(middle.next)
        middle.next = None
        self.mergeTwo(fir,sec)
        
    def reverseList(self, head):
        if not head or not head.next:
            return head
        
        newhead = None
        while head:
            tmp = head.next
            head.next = newhead
            newhead = head
            head = tmp
        return newhead
    
    def mergeTwo(self, l1, l2):
        while l1 and l2:
            tmp = l1.next
            l1.next = l2
            l1 = tmp

            tmp_ = l2.next
            l2.next = l1
            l2 = tmp_
    
    def findMiddle(self, head):
        fast = slow = head
        while fast.next and fast.next.next:
            fast = fast.next.next
            slow = slow.next
        return slow

这一题可以整合前面几个题目的做法,也可以做前后两指针

class Solution:
    def reorderList(self, head: Optional[ListNode]) -> None:
        """
        Do not return anything, modify head in-place instead.
        """
        if not head or not head.next or not head.next.next:
            return
        
        vec = []
        tmp = head
        while tmp:
            vec.append(tmp)
            tmp = tmp.next
        
        l,r = 0,len(vec)-1
        while l<r:
            vec[l].next = vec[r]
            l += 1
            vec[r].next = vec[l]
            r -= 1
        vec[l].next = None

4.双向链表

146.lru-cache

请你设计并实现一个满足 LRU (最近最少使用) 缓存 约束的数据结构。
实现 LRUCache 类:
LRUCache(int capacity) 以 正整数 作为容量 capacity 初始化 LRU 缓存
int get(int key) 如果关键字 key 存在于缓存中,则返回关键字的值,否则返回 -1 。
void put(int key, int value) 如果关键字 key 已经存在,则变更其数据值 value ;如果不存在,则向缓存中插入该组 key-value 。如果插入操作导致关键字数量超过 capacity ,则应该 逐出 最久未使用的关键字。
函数 get 和 put 必须以 O(1) 的平均时间复杂度运行。
link

class Node:
    __slots__ = 'prev','next','key','val'

    def __init__(self, key=0, val=0):
        self.key = key #节点存储key值是为了删除时可以在字典中定位
        self.val = val


class LRUCache:

    def __init__(self, capacity: int):
        self.capacity = capacity
        self.dummy = Node()
        self.dummy.prev = self.dummy
        self.dummy.next = self.dummy
        self.map = {}

    def removeNode(self, node):
        #删除一个节点/抽出一本书
        node.prev.next = node.next
        node.next.prev = node.prev
    
    def pushTop(self, node):
        #添加头节点/把书放到最上面
        #这里每个节点的prev是它上面的书,next是下面的书
        #对于哨兵节点dummy,它的prev是最底部,next是最上面
        node.prev = self.dummy
        node.next = self.dummy.next
        node.prev.next = node
        node.next.prev = node
    
    def getNode(self, key):
        #没有这本书
        if key not in self.map:
            return None
        #有这本书,则先抽出来再放到最上面/删除节点再把节点添加为头节点
        self.removeNode(self.map[key])
        self.pushTop(self.map[key])
        return self.map[key]

    def get(self, key: int) -> int:
        node = self.getNode(key)
        return node.val if node else -1

    def put(self, key: int, value: int) -> None:
        node = self.getNode(key)
        if node:
            #有这本书,则只需要更新value
            node.val = value
            return
        
        #没有这本书,则把这本新书放到最上面/添加为头节点
        self.map[key] = newnode = Node(key,value)
        self.pushTop(newnode)

        #字典容量超出capacity,抽出最底下的书/删除底部节点
        if len(self.map)>self.capacity:
            bottom = self.dummy.prev
            self.removeNode(bottom)
            del self.map[bottom.key]


# Your LRUCache object will be instantiated and called as such:
# obj = LRUCache(capacity)
# param_1 = obj.get(key)
# obj.put(key,value)

这道题的参考写的很清楚,用堆书比喻:link

5.哈希

138.copy-list-with-random-pointer

给你一个长度为 n 的链表,每个节点包含一个额外增加的随机指针 random ,该指针可以指向链表中的任何节点或空节点。

构造这个链表的 深拷贝。 深拷贝应该正好由 n 个 全新 节点组成,其中每个新节点的值都设为其对应的原节点的值。新节点的 next 指针和 random 指针也都应指向复制链表中的新节点,并使原链表和复制链表中的这些指针能够表示相同的链表状态。复制链表中的指针都不应指向原链表中的节点 。

例如,如果原链表中有 X 和 Y 两个节点,其中 X.random --> Y 。那么在复制链表中对应的两个节点 x 和 y ,同样有 x.random --> y 。

返回复制链表的头节点。

用一个由 n 个节点组成的链表来表示输入/输出中的链表。每个节点用一个 [val, random_index] 表示:

val:一个表示 Node.val 的整数。
random_index:随机指针指向的节点索引(范围从 0 到 n-1);如果不指向任何节点,则为 null 。
你的代码 只 接受原链表的头节点 head 作为传入参数。
link

"""
# Definition for a Node.
class Node:
    def __init__(self, x: int, next: 'Node' = None, random: 'Node' = None):
        self.val = int(x)
        self.next = next
        self.random = random
"""

class Solution:
    def copyRandomList(self, head: 'Optional[Node]') -> 'Optional[Node]':
        if not head:
            return None

        hashdic = {}
        cur = head
        while cur:
            hashdic[cur] = Node(cur.val)
            cur = cur.next
        
        cur = head
        while cur:
            hashdic[cur].next = hashdic.get(cur.next,None)
            hashdic[cur].random = hashdic.get(cur.random, None)
            cur = cur.next
        return hashdic[head]

参考:link

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值