Hot100

12 阅读7分钟

46. 全排列

class Solution:
    def permute(self, nums: List[int]) -> List[List[int]]:
        res = []    # 存放最终所有全排列结果
        path = []   # 记录当前递归的路径(临时排列)
        n = len(nums)
        used = [False] * n  # 标记对应下标数字是否已使用

        def backtrack():
            # 终止条件:路径长度 = 数组长度,说明一组排列完成
            if len(path) == n:
                res.append(path.copy())
                return
            
            # 遍历所有数字,逐个尝试选择
            for i in range(n):
                if used[i]:
                    continue # 当前数字已过时,跳过
                # 1. 做出选择
                used[i] = True
                path.append(nums[i])
                # 2. 递归深入,继续下一个数
                backtrack()
                # 3. 回溯撤销选择
                path.pop()
                used[i] = False
        
        backtrack()
        return res

208. 实现 Trie (前缀树)


class Trie:

    def __init__(self):
        # 跟节点,用字典存储子节点,is_end 标记是否是单词结尾
        self.root = {}
        self.end = '#'

    def insert(self, word: str) -> None:
        # 从根节点开始
        node = self.root
        for c in word:
            # 字符不存在就新建子字典
            if c not in node:
                node[c] = {}
            # 往下走
            node = node[c]
        # 标记单词结束
        node[self.end] = True

    def search(self, word: str) -> bool:
        node = self.root
        for c in word:
            if c not in node:
                return False
            node = node[c]
        # 必须走到单词结尾标记才算存在
        return self.end in node

    def startsWith(self, prefix: str) -> bool:
        node = self.root
        for c in prefix:
            if c not in node:
                return False
            node = node[c]
        # 只要能走完所有字符,就说明前缀存在
        return True


# Your Trie object will be instantiated and called as such:
# obj = Trie()
# obj.insert(word)
# param_2 = obj.search(word)
# param_3 = obj.startsWith(prefix)

207. 课程表

class Solution:
    def canFinish(self, numCourses: int, prerequisites: List[List[int]]) -> bool:
        # 1. 构建有向图:graph[i] 存储课程 i 学完后,可以接着学的所有课程
        graph = [[] for _ in range(numCourses)]
        # 2. 入度数组:in_degree[i] 表示课程 i 还有几门先修课没修
        in_degree = [0] * numCourses
        # 3. 遍历先修关系,填充图和入度数组
        for a, b in prerequisites: 
            graph[b].append(a)      # 建立边 b->a:学完b才能学a
            in_degree[a] += 1       # 课程 a 多了一门先修课,入度 +1
        
        # 4. 初始化队列:把所有【没有先修课、可以直接学】的课程入对
        q = deque()
        for i in range(numCourses):
            if in_degree[i] == 0:   # 入度为0表示无先修要求
                q.append(i)         # 加入队列
        
        count = 0   # 统计已经成功修完的课程数量
        # 5. BFS 核心循环:不断处理队列中可以学的课
        while q:    # 只要队列不为空,就还有课能学
            u = q.popleft() # 从队列头部取出一门课 u,开始学习
            count += 1  # 学完一门课,计数 +1

            # 6. 遍历 u 的所有后续课程 v (v学完u才能学的课)
            for v in graph[u]:
                in_degree[v] -= 1

                # 7. 如果 v 的入度变为0,说明 v 所有先修课都完成了
                if in_degree[v] == 0:
                    q.append(v) # 把v加入队列,可以开始学了
        
        # 8. 最终判断:修完的课程数 == 总课程数 -> 无环,可以修完
        return count == numCourses

146. LRU 缓存


# 定义双向链表节点类
# 每个节点需要存储:key、value、前驱指针、后继指针
class DLinkedNode:
    def __init__(self, key=0, value=0):
        self.key = key      # 必须存key!删除链表尾节点时,要靠它去哈希表删数据
        self.value = value  # 缓存存储的值
        self.prev = None    # 前驱节点指针
        self.next = None    # 后继节点指针

# LRU 缓存实现类
# 核心:哈希表 O(1) 查找 + 双向链表 O(1) 增删
class LRUCache:

    # 初始化构造方法
    def __init__(self, capacity: int):
        self.capacity = capacity  # 缓存最大容量(上限)
        self.size = 0             # 当前缓存里实际存了多少数据
        self.cache = {}           # 哈希表:key -> 双向链表节点,实现 O(1) 查找

        # 创建【虚拟头节点】和【虚拟尾节点】
        # 作用:不用判断空链表、头插、尾删的边界情况
        self.head = DLinkedNode()
        self.tail = DLinkedNode()
        
        # 初始化双向链表:head <-> tail
        self.head.next = self.tail
        self.tail.prev = self.head

    # 查询操作:根据 key 获取 value
    def get(self, key: int) -> int:
        # 如果 key 不在缓存里,返回 -1
        if key not in self.cache:
            return -1
        
        # key 存在:通过哈希表找到对应的节点
        node = self.cache[key]
        # 访问过了,把节点移到链表头部(标记为【最近使用】)
        self.moveToHead(node)
        # 返回节点的值
        return node.value

    # 插入/更新操作
    def put(self, key: int, value: int) -> None:
        if key in self.cache:
            # 情况1:key 已存在
            # 找到节点,更新 value
            node = self.cache[key]
            node.value = value
            # 访问过,移到头部
            self.moveToHead(node)
        else:
            # 情况2:key 不存在,新建节点
            new_node = DLinkedNode(key, value)
            # 加入哈希表
            self.cache[key] = new_node
            # 缓存数量 +1
            self.size += 1

            # 如果缓存满了,需要删除【最久未使用】的节点(链表尾部)
            if self.size > self.capacity:
                # 删除尾部节点
                tail_node = self.removeTail()
                # 同时在哈希表里删除这个 key
                del self.cache[tail_node.key]
                # 缓存数量 -1
                self.size -= 1
            
            # 把新节点加到链表头部
            self.addToHead(new_node)
    
    # 工具方法:把一个节点【添加到链表头部】
    def addToHead(self, node: DLinkedNode):
        # 新节点的前驱指向 head
        node.prev = self.head
        # 新节点的后继指向 head 原来的 next
        node.next = self.head.next
        
        # 原来的头节点的前驱,指向新节点
        self.head.next.prev = node
        # head 的 next 指向新节点
        self.head.next = node

    # 工具方法:从双向链表中【删除指定节点】
    def removeNode(self, node: DLinkedNode):
        # 拿到要删除节点的 前驱 和 后继
        prev_node = node.prev
        next_node = node.next

        # 直接跳过当前节点,让前后节点互相连接
        prev_node.next = next_node
        next_node.prev = prev_node
    
    # 工具方法:把节点【移动到头部】
    # 两步:先删除,再插入头部
    def moveToHead(self, node: DLinkedNode):
        self.removeNode(node)
        self.addToHead(node)
    
    # 工具方法:【删除尾部节点】(最久未使用)
    def removeTail(self) -> DLinkedNode:
        # 尾节点的前驱,就是真正的最后一个有效节点
        tail_node = self.tail.prev
        # 从链表中删掉它
        self.removeNode(tail_node)
        # 返回被删掉的节点(用于去哈希表删 key)
        return tail_node

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

23. 合并 K 个升序链表

# Definition for singly-linked list.
# class ListNode:
#     def __init__(self, val=0, next=None):
#         self.val = val
#         self.next = next
class Solution:
    # 合并两个链表
    def merge(self, h1: Optional[ListNode], h2: Optional[ListNode]) -> Optional[ListNode]:
        cur = dummy = ListNode(0)
        while h1 and h2:
            if h1.val < h2.val:
                cur.next = h1
                h1 = h1.next
            else:
                cur.next = h2
                h2 = h2.next
            cur = cur.next
        cur.next = h1 if h1 else h2
        return dummy.next

    def sortList(self, head: Optional[ListNode]) -> Optional[ListNode]:
        if not head or not head.next:
            return head
        
        # 获取链表总长度
        n = 0
        cur = head
        while cur:
            n += 1
            cur = cur.next
        
        dummy = ListNode(0, head)
        intv = 1
        while intv < n:
            pre, cur = dummy ,dummy.next
            while cur:
                # 截取第一段有序链表
                h1 = cur
                for _ in range(intv - 1):
                    if cur and cur.next:
                        cur = cur.next
                    else:
                        break
                
                # 获取第二段有序链表,并断开第一段链表
                h2 = cur.next
                cur.next = None
                cur = h2
                for _ in range(intv - 1):
                    if cur and cur.next:
                        cur = cur.next
                    else:
                        break
                
                # 获取下一轮开始的指针,并断开第二段链表
                next_node = None
                if cur:
                    next_node = cur.next
                    cur.next = None
                
                # 合并两个链表
                merged = self.merge(h1, h2)
                pre.next = merged
                while pre.next:
                    pre = pre.next
                
                cur = next_node
            intv *= 2
        return dummy.next

138. 随机链表的复制

class Solution:
    def copyRandomList(self, head: 'Optional[Node]') -> 'Optional[Node]':
        # 边界情况:如果原链表为空,直接返回空
        if not head:
            return None
        
        # 创建哈希表,用于存储 原节点 -> 新复制节点 的映射关系
        node_map = {}
        # 定义游标指针,从头节点开始遍历
        cur = head
        
        # 第一次遍历:创建所有新节点,只赋值val,建立原节点和新节点的映射
        while cur:
            # 根据当前原节点的值,创建一个新的复制节点
            new_node = Node(cur.val)
            # 把 原节点:新节点 存入字典,方便后续查找对应关系
            node_map[cur] = new_node
            # 游标向后移动,继续处理下一个原节点
            cur = cur.next
        
        # 第二次遍历:给新节点设置 next 和 random 指针
        cur = head  # 重置游标,回到链表头
        while cur:
            # 从字典中取出当前原节点对应的新节点
            new_node = node_map[cur]
            # 新节点的next = 原节点next对应的新节点(没有则为None)
            new_node.next = node_map.get(cur.next, None)
            # 新节点的random = 原节点random对应的新节点(没有则为None)
            new_node.random = node_map.get(cur.random, None)
            # 游标向后移动
            cur = cur.next
        
        # 返回复制链表的头节点(原头节点对应的新节点)
        return node_map[head]


class Solution:
    def copyRandomList(self, head: 'Optional[Node]') -> 'Optional[Node]':
        # 边界情况:链表为空直接返回
        if not head:
            return None
        
        # 第一步:在每个原节点后面插入对应的复制节点
        cur = head
        while cur:
            # 创建当前节点的复制节点
            copy = Node(cur.val)
            # 复制节点的next指向原节点的next
            copy.next = cur.next
            # 原节点的next指向复制节点,完成插入
            cur.next = copy
            # 游标移动到下一个原节点(跳过刚插入的复制节点)
            cur = copy.next
        
        # 第二步:给所有复制节点设置 random 指针
        cur = head  # 重置游标
        while cur:
            # 如果原节点有random指向,那么复制节点的random = 原节点random的下一个节点(对应复制节点)
            if cur.random:
                cur.next.random = cur.random.next
            # 每次跳两步,只遍历原节点
            cur = cur.next.next
        
        # 第三步:将原链表和复制链表分离,得到最终的深拷贝链表
        cur = head
        copy_head = head.next  # 记录复制链表的头节点,最后要返回
        copy_cur = copy_head   # 复制链表的游标
        
        while cur:
            # 原节点的next恢复为原next(跳过复制节点)
            cur.next = copy_cur.next
            # 原链表游标后移
            cur = cur.next
            # 如果还有后续节点,复制链表游标继续后移
            if cur:
                copy_cur.next = cur.next
                copy_cur = copy_cur.next
        
        # 返回复制好的新链表头
        return copy_head