leetcode hot100【链表】

0 阅读8分钟

链表

1. 相交链表

原题链接:160. 相交链表

image.png

image.png

题解:

image.png

class Solution(object):
    def getIntersectionNode(self, headA, headB):
        p = headA
        q = headB
        while p is not q:
            p = p.next if p else headB
            q = q.next if q else headA
        return p

2. 反转链表

原题链接:206. 反转链表 1. 方法一:迭代(双指针)

class Solution(object):
    def reverseList(self, head):
        cur = head
        pre = None
        while cur:
            tmp = cur.next # 暂存后继节点 cur.next
            cur.next = pre # 修改 next 引用指向
            pre = cur      # pre 暂存 cur
            cur = tmp      # cur 访问下一节点
        return pre

2. 方法二:递归

image.png

        def recur(cur,pre):
            if not cur: return pre
            res = recur(cur.next,cur)
            cur.next = pre
            return res
        return recur(head,None)

3. 回文链表

原题链接:234. 回文链表

image.png

# Definition for singly-linked list.
# class ListNode(object):
#     def __init__(self, val=0, next=None):
#         self.val = val
#         self.next = next
class Solution(object):
    def isPalindrome(self,head):
        # 链表的中间结点
        def midNode(head):
            slow = fast = head
            while fast.next and fast.next.next:
                fast = fast.next.next
                slow = slow.next
            return slow
        # 反转链表,得到反转后的头结点
        def reverseList(head):
            def recur(cur,pre):
                if not cur:return pre
                res = recur(cur.next,cur)
                cur.next = pre
                return res
            return recur(head,None)
        # 左链表与右链表一一对比
        mid = midNode(head)
        right = reverseList(mid.next)
        while right:
            if head.val != right.val:
                return False
            head = head.next
            right = right.next
        return True

4. 环形链表

原题链接:141. 环形链表

image.png

class Solution(object):
    def hasCycle(self, head):
        slow = fast = head
        while fast and fast.next:          # 同时检查 fast 和 fast.next
            slow = slow.next               # 慢指针走一步
            fast = fast.next.next          # 快指针走两步
            if slow == fast:               # 相遇则说明有环
                return True
        return False

5. 环形链表 II

原题链接:142. 环形链表 II

image.png

class Solution(object):
    def detectCycle(self, head):
        """
        检测链表中是否存在环,并返回环的入口节点。
        若不存在环,返回 None。
        使用快慢指针(Floyd判圈算法):
        1. 快指针每次走两步,慢指针每次走一步,若存在环,它们必然在环内相遇。
        2. 相遇后,将其中一个指针重置到头节点,然后两个指针都每次走一步,
           再次相遇的位置即为环的入口。
        """
        # 空链表或只有一个节点,不可能有环
        if not head or not head.next:
            return None

        # 初始化快慢指针,都指向头节点
        slow = fast = head

        # 快指针每次走两步,慢指针走一步,循环条件确保快指针不会越界
        # 当 fast 或 fast.next 为 None 时,说明链表无环,循环自然结束
        while fast and fast.next:
            fast = fast.next.next   # 快指针走两步
            slow = slow.next        # 慢指针走一步

            # 如果快慢指针相遇,说明链表中存在环
            if fast == slow:
                # 将快指针(或慢指针)重置到头节点
                fast = head
                # 然后两个指针同时以相同速度(每次一步)前进
                # 根据数学推导,它们再次相遇的节点就是环的入口
                while fast != slow:
                    fast = fast.next
                    slow = slow.next
                # 返回入口节点
                return fast

        # 循环正常结束(未触发 return),说明没有环
        return None

计算环长:

class Solution(object):
    def detectCycle(self, head):
        if not head or not head.next:
            return None
        slow = fast = head
        while fast and fast.next:
            fast = fast.next.next
            slow = slow.next
            if fast == slow:
                # 有环,记录相遇点
                meet = slow
                break
        else:
            return None   # 无环

        # 计算环的长度:从相遇点出发,走一圈回到原点
        length = 1
        slow = meet.next
        while slow != meet:
            slow = slow.next
            length += 1
        return length   # 返回环长

6.合并两个有序链表

原题链接:21. 合并两个有序链表

image.png

思路:比较两个链表当前头节点的值,将较小的节点作为合并后的头,然后将其 next 指向剩余部分合并的结果。

class Solution(object):
    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

7.两数相加

原题链接:2. 两数相加

image.png

class Solution(object):
    def addTwoNumbers(self, l1, l2):
        # dummy哨兵节点;cur 是当前尾节点指针(游标)
        cur = dummy = ListNode()
        carry = 0  # 进位

        # 只要任一链表未遍历完,或还有进位,就继续
        while l1 or l2 or carry:
            s = carry  # 当前位数值从进位开始

            if l1:                # l1 非空,累加并后移
                s += l1.val
                l1 = l1.next
            if l2:                # l2 非空,累加并后移
                s += l2.val
                l2 = l2.next

            carry = s // 10       # 更新进位(十位)
            cur.next = ListNode(s % 10)  # 创建新节点存放当前位(个位)
            cur = cur.next        # 尾指针后移

        return dummy.next  # 哨兵的下一个节点即为结果链表的头

8.删除链表的倒数第 N 个结点

原题链接:19. 删除链表的倒数第 N 个结点

image.png

class Solution(object):
    def removeNthFromEnd(self, head, n):
        # 创建哑节点,指向头节点,便于统一处理(包括删除头节点)
        dummy = ListNode()
        dummy.next = head
        fast = slow = dummy   # 快慢指针都从哑节点开始

        # 快指针先走 n+1 步(让慢指针最终指向被删节点的前驱)
        for _ in range(n + 1):
            fast = fast.next

        # 快慢指针同步移动,直到快指针到达链表末尾
        while fast:
            fast = fast.next
            slow = slow.next

        # 此时 slow.next 正是倒数第 n 个节点,跳过它
        slow.next = slow.next.next

        # 返回真正的头节点(哑节点的下一个)
        return dummy.next

9.两两交换链表中的节点

原题链接:24. 两两交换链表中的节点

image.png

思路:

image.png

class Solution(object):
    def swapPairs(self, head):
        if not head or not head.next:
            return head
        node0 = dummy = ListNode(0,head)
        node1 = head
        while node1 and node1.next:
            node2 = node1.next
            node3 = node2.next

            node0.next = node2
            node2.next = node1
            node1.next = node3

            node0 = node1
            node1 = node3
        return dummy.next

10.K 个一组翻转链表

原题链接:25. K 个一组翻转链表

image.png

class Solution(object):
    def reverseKGroup(self, head, k):
        # 空链表或只有一个节点,不需要翻转
        if not head or not head.next:
            return head

        # 虚拟头节点,方便处理头节点翻转后的连接
        dummy = ListNode(0, head)
        # pre 指向待翻转组的前一个节点,end 用来查找当前组的尾节点
        pre = end = dummy

        # 只要 end 后面还有节点,就继续处理下一组
        while end.next:
            # 让 end 向后走 k 步,找到当前组的最后一个节点
            # 注意:如果剩余节点不足 k 个,end 会变成 None,此时要跳出循环
            for _ in range(k):
                # 关键:每次移动前检查 end 是否为 None,防止空指针异常
                if end is None:
                    break
                end = end.next
            # 如果 end 是 None,说明剩余节点不够一组,不翻转,直接结束
            if end is None:
                break

            # 保存下一组的头节点(即当前组尾节点的下一个)
            nxt = end.next
            # 将当前组从链表中切断,方便单独翻转
            end.next = None

            # start 是当前组的头节点(翻转前的头)
            start = pre.next
            # 翻转当前组,翻转后 pre.next 指向新的头(原尾节点)
            pre.next = self.reverse(start)
            # 翻转后,start 变成了当前组的尾节点,把它接回剩余链表
            start.next = nxt

            # 移动 pre 和 end 到当前组的尾节点(即 start),准备处理下一组
            pre = start
            end = start

        # 返回虚拟头节点的下一个,即真正的头节点
        return dummy.next

    def reverse(self, head):
        """
        翻转一个单链表,返回新的头节点(原尾节点)
        经典三指针法:
        - pre:已经翻转好的前一个节点
        - cur:当前待翻转的节点
        - nxt:保存 cur 的下一个节点,防止丢失
        """
        if not head or not head.next:
            return head

        pre = None
        cur = head
        # 循环翻转每一个节点
        while cur:
            nxt = cur.next    # 先记住下一个节点
            cur.next = pre    # 当前节点指向前一个,完成翻转
            pre = cur         # pre 后移
            cur = nxt         # cur 后移
        return pre

11.随机链表的复制

原题链接:138. 随机链表的复制

image.png

class Solution(object):
    def copyRandomList(self, head):
        if not head:
            return None

        cur = head
        dic = {}
        # 第一遍:复制每个节点,存映射
        while cur:
            dic[cur] = Node(cur.val)   # 旧节点 -> 新节点
            cur = cur.next

        cur = head
        # 第二遍:连接 next 和 random(用 get 处理 None)
        while cur:
            dic[cur].next = dic.get(cur.next)      # 查映射连 next
            dic[cur].random = dic.get(cur.random)  # 查映射连 random
            cur = cur.next

        return dic[head]  # 返回新链表的头

12.排序链表

原题链接:148. 排序链表

class Solution(object):
    def sortList(self, head):
        # 递归终止条件:空或单节点无需排序
        if not head or not head.next:
            return head

        # 快慢指针找中点,slow 最终停在左半段的尾节点
        slow, fast = head, head.next
        while fast and fast.next:
            fast = fast.next.next
            slow = slow.next

        # 切断链表,nxt 为右半段头节点
        nxt = slow.next
        slow.next = None

        # 递归排序左右两半
        left = self.sortList(head)
        right = self.sortList(nxt)

        # 合并两个有序链表(哑节点简化操作)
        dummy = ListNode()
        cur = dummy
        while left and right:
            if left.val < right.val:
                cur.next = left
                left = left.next
            else:
                cur.next = right
                right = right.next
            cur = cur.next
        # 接上剩余部分
        cur.next = left if left else right

        return dummy.next

13.合并 K 个升序链表

原题链接:23. 合并 K 个升序链表

image.png

class Solution(object):
    def mergeKLists(self, lists):
        # 哨兵节点,简化链表构建
        cur = dummy = ListNode()

        # 最小堆:存 (节点值, 链表编号, 节点本身)
        # 加编号 i 是为了值相等时避免比较 ListNode 对象(会报错)
        h = [(head.val, i, head) for i, head in enumerate(lists) if head]
        heapify(h)  # 将列表转为最小堆

        while h:
            _, i, node = heappop(h)  # 弹出当前最小节点
            if node.next:             # 该链表还有剩余节点,将下一个入堆
                heappush(h, (node.next.val, i, node.next))

            cur.next = node           # 接到新链表末尾
            cur = cur.next            # 移动尾指针

        return dummy.next             # 返回真正的头节点

14.LRU 缓存

原题链接:146. LRU 缓存

image.png

思路:

image.png

视频:LeetCode刷题|python版本|146题|LRU缓存_哔哩哔哩_bilibili

class ListNode:
    # 提高访问属性的速度,并节省内存
    __slots__ = 'prev', 'next', 'key', 'value'

    def __init__(self, key=0, val=0):
        self.key = key
        self.val = val

class LRUCache(object):

    def __init__(self, capacity):

        """
        :type capacity: int
        """
        self.capacity = capacity
        self.hashmap = {}
        self.head = ListNode()
        self.tail = ListNode()
        self.head.next = self.tail
        self.tail.prev = self.head

    def remove_node(self,node):
        node.prev.next = node.next
        node.next.prev = node.prev

    def add_node_to_last(self,node):
        self.tail.prev.next = node
        node.prev = self.tail.prev
        node.next = self.tail
        self.tail.prev = node

    def move_node_to_last(self,node):
        self.remove_node(node)
        self.add_node_to_last(node)

    def get(self, key):
        """
        :type key: int
        :rtype: int
        """
        if key not in self.hashmap:
            return -1
        node = self.hashmap[key]
        self.move_node_to_last(node)
        return node.val

    def put(self, key, value):
        """
        :type key: int
        :type value: int
        :rtype: None
        """
        if key in self.hashmap:
            node = self.hashmap[key]
            node.val = value
            self.move_node_to_last(node)
            return 
        if len(self.hashmap) == self.capacity:
            del self.hashmap[self.head.next.key]
            self.remove_node(self.head.next)
        node = ListNode(key,value)
        self.hashmap[key] = node
        self.add_node_to_last(node)
        


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