Leetcode 链表 1

题号直达: 160 相交链表 | 206 反转链表 | 234 回文链表 | 141 环形链表

LeetCode 160. 相交链表 ↑

题目

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

题目保证整个链式结构中不出现环。

示意图如下:

相交链表示意图

提示

  • listA 中节点数目为 m
  • listB 中节点数目为 n
  • 1 ≤ m, n ≤ 3 × 10⁴
  • 1 ≤ Node.val ≤ 10⁵
  • 0 ≤ skipA ≤ m
  • 0 ≤ skipB ≤ n
  • 如果 listA 和 listB 没有交点,intersectVal 为 0
  • 如果 listA 和 listB 有交点,intersectVal == listA[skipA] == listB[skipB]

进阶:你能否设计一个时间复杂度 O(m + n)、仅用 O(1) 内存的方案?

代码(Python)解法一:哈希集合记录访问过的节点

class Solution(object):
    def getIntersectionNode(self, headA, headB):
        """
        :type headA: ListNode
        :type headB: ListNode
        :rtype: ListNode
        """
        visited = set()

        temp = headA
        while temp:
            visited.add(temp)
            temp = temp.next

        temp = headB
        while temp:
            if temp in visited:
                return temp
            temp = temp.next

        return None

思路解析(解法一)

  • 核心想法:先遍历链表 A,把遇到的每个节点指针都丢进集合 visited;再遍历链表 B,一边走一边判断当前节点是否已经在集合中,若在则说明这是两个链表第一个相交的节点。
  • 正确性:相交意味着从某个节点开始,接下来的 next 指针完全共享,因此一旦在 B 的遍历过程中遇到集合中已有的节点,这个节点必然是相交起点。
  • 复杂度:时间复杂度 O(m + n),空间复杂度 O(m)(集合存储链表 A 的节点)。满足时间复杂度要求,但没有做到进阶中的 O(1) 额外空间(可以作为后续双指针解法的对比基线)。

代码(Python)解法二:双指针「对齐长度」法(O(1) 空间)

class Solution(object):
    def getIntersectionNode(self, headA, headB):
        """
        :type headA: ListNode
        :type headB: ListNode
        :rtype: ListNode
        """
        # 如果任一链表为空,不可能相交
        if not headA or not headB:
            return None
        
        # 双指针初始化
        pA, pB = headA, headB
        
        # 当两个指针不相遇时继续移动
        while pA != pB:
            # pA 移动:如果到达末尾,转到 headB;否则继续向前
            if pA:
                pA = pA.next
            else:
                pA = headB
            
            # pB 移动:如果到达末尾,转到 headA;否则继续向前
            if pB:
                pB = pB.next
            else:
                pB = headA
        
        # 返回相遇点(可能是交点,也可能是 None)
        return pA

思路解析(解法二)

  • 长度对齐的巧妙写法:指针 pA 先走完链表 A 再接链表 B,pB 先走完链表 B 再接链表 A,它们总共都走了 lenA + lenB 步。如果有交点,走到第二轮时会在交点处相遇;如果没有交点,则最终都变为 None,也在同一位置相遇。
  • 为什么一定会相遇:第一轮结束时,pA 比 pB 多走了 lenA - lenB 或反之,通过第二轮互换起点,这部分差距被刚好抵消,因此两者会在同一时刻到达交点(或同时为 None)。
  • 复杂度:时间复杂度 O(m + n),空间复杂度 O(1),完全满足进阶要求,也是本题最常用、最优雅的写法之一。

LeetCode 206. 反转链表 ↑

题目

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

提示

  • 链表中节点数目范围是 ([0, 5000])
  • (-5000 ≤ Node.val ≤ 5000)

进阶:链表可以使用迭代或递归方式完成反转,你能否用两种方式都写一写?

代码(Python)解法一:用数组记录节点,再倒序重连

class Solution(object):
    def reverseList(self, head):
        """
        :type head: Optional[ListNode]
        :rtype: Optional[ListNode]
        """
        if not head:
            return None

        record = []

        p = head
        while p:
            record.append(p)
            p = p.next

        ans = record[-1]
        temp = ans

        for i in range(1, len(record)):
            temp.next = record[len(record) - i - 1]
            temp = temp.next

        temp.next = None

        return ans

思路解析(解法一)

  • 整体思路:先顺序遍历链表,把每个节点指针依次存入数组 record;然后从数组最后一个节点开始,按倒序把 next 指针重新连起来,相当于把链表反向“串”回去。
  • 注意点 1:空链表:函数开头先判断 if not head: return None,避免后面对空指针取 next 或访问 record[-1] 时出错。
  • 注意点 2:尾结点要指向 None:重建完倒序链表后,最后一个节点的 next 仍然保留着原链表中的旧指向,必须显式设为 None(temp.next = None),否则会形成错误的链表结构,甚至可能产生环。
  • 复杂度:遍历一次构建数组,再遍历一次数组倒序连边,时间复杂度 (O(n)),需要 (O(n)) 额外空间保存所有节点指针;符合题目时间要求,但没有做到原地 (O(1)) 空间,后续可以用原地迭代指针的写法优化空间。

代码(Python)解法二:递归反转(从尾到头“归”回来)

class Solution(object):
    def reverseList(self, head):
        """
        :type head: Optional[ListNode]
        :rtype: Optional[ListNode]
        """
        # 基本情况:空链表或只有一个节点
        if not head or not head.next:
            return head
        
        # 递归反转后续链表
        newHead = self.reverseList(head.next)
        
        # 将当前节点的下一个节点的 next 指向当前节点
        head.next.next = head
        # 将当前节点的 next 设为 None
        head.next = None
        
        return newHead

思路解析(解法二)

  • 递归含义:reverseList(head) 的语义是「反转从 head 开始的链表,并返回反转后的新头结点」。对当前节点 head,先递归处理 head.next,让 newHead 指向反转后的子链表头。
  • 指针翻转:当递归从最深处往回“归”时,head.next 已经是反转后子链表的尾部之一,此时把 head.next.next = head,相当于把当前节点挂到子链表尾部;再把 head.next = None 断开原来的正向链接,避免成环。
  • 终止条件:当遇到空链表或只有一个节点时直接返回 head,这一层作为最终的 newHead 一路返回到最外层。
  • 复杂度与优缺点:时间复杂度同样是 (O(n)),空间复杂度由于递归栈为 (O(n))。写法简洁、逻辑清晰,但要注意递归深度上限;在 Python 中节点数上限 (5000) 仍在默认递归深度允许范围内。

LeetCode 234. 回文链表 ↑

题目

给你一个单链表的头节点 head,请你判断该链表是否为回文链表。如果是,返回 true;否则,返回 false。

提示

  • 链表中节点数目在范围 ([1, 10^5]) 内
  • (0 ≤ Node.val ≤ 9)

进阶:你能否用 (O(n)) 时间复杂度和 (O(1)) 空间复杂度解决此题?

代码(Python)解法一:把值拷贝到数组里做双指针

class Solution(object):
    def isPalindrome(self, head):
        """
        :type head: Optional[ListNode]
        :rtype: bool
        """
        if not head:
            return True
        
        record = []
        p = head
        while p != None:
            record.append(p.val)
            p = p.next

        N = len(record) // 2
        for i in range(N):
            if record[i] != record[len(record) - 1 - i]:
                return False

        return True

思路解析(解法一)

  • 转化为数组回文判断:顺序遍历链表,把每个节点的值依次压入数组 record。这样链表是否回文就等价于数组 record 是否回文。
  • 双指针比较:只需检查前一半元素是否和后一半对称相等:对每个 i ∈ [0, N-1],判断 record[i] 是否等于 record[len(record) - 1 - i],一旦有一对不相等就返回 False,全部通过则是回文。
  • 边界情况:当链表只有 0 或 1 个节点时,一定是回文,代码中 if not head: return True 覆盖了空链表;长度为 1 时 N = len(record) // 2 为 0,不会进入循环,也会返回 True。
  • 复杂度与不足:时间复杂度 (O(n)),空间复杂度 (O(n))(需要额外数组保存所有节点的值),实现简单直观;但没有达到进阶要求的 (O(1)) 额外空间,后续可以通过「反转后半段链表 + 原地比较」来优化空间。

代码(Python)解法二:递归回溯 + 前后双指针

class Solution(object):
    def isPalindrome(self, head):
        self.front = head
        
        def check(node):
            if node:
                if not check(node.next):
                    return False
                if node.val != self.front.val:
                    return False
                self.front = self.front.next
            return True
            
        return check(head)

思路解析(解法二)

  • 递归当作「从右往左」的指针:recursivelyCheck(currentNode) 先一路递归到链表尾部,等递归开始回溯时,currentNode 会从尾结点开始不断往前走,相当于得到一个「从右往左」的遍历顺序。
  • 前后双指针比较:用成员变量 self.frontPointer 作为从左往右的指针,每次在回溯阶段比较 currentNode.val 与 self.frontPointer.val,若不相等直接返回 False;相等则把 self.frontPointer 向前挪一步,继续回溯上一层。
  • 终止与短路:如果某一层已经返回 False,上层递归直接短路返回 False;若所有层都通过比较,则最终返回 True。
  • 复杂度:时间复杂度仍为 (O(n)),需要 (O(n)) 的递归栈空间;相比数组法节省了显式的数组存储,但仍不满足进阶的 (O(1)) 额外空间,更多体现的是「利用递归回溯模拟双向扫描」的思路。

LeetCode 141. 环形链表 ↑

题目

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

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

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

提示

  • 链表中节点数目在范围 ([0, 10^4]) 以内
  • (-10^5 ≤ Node.val ≤ 10^5)
  • pos 为 -1 或者链表中的一个有效索引

进阶:你能否使用 (O(1))(即常量)内存解决此题?

代码(Python)解法一:用列表记录访问过的节点

class Solution(object):
    def hasCycle(self, head):
        """
        :type head: ListNode
        :rtype: bool
        """
        record = []
        p = head
        while p:
            if p in record:
                return True
            record.append(p)
            p = p.next
        return False

思路解析(解法一)

  • 核心想法:顺着链表往后走的过程中,用容器 record 记录已经访问过的节点指针。每访问到一个新节点 p,先判断它是否已经在 record 中:如果是,说明我们又回到了之前访问过的节点,链表存在环;若不是,再把它加入 record 继续向后走,直到指针为 None。
  • 等价表述:本质上是在遍历时「检查是否访问过同一个节点两次」,这和图论里通过标记结点判断是否成环的思路类似,只不过这里结构是单向链表。
  • 边界情况:当 head 为 None 或链表最终走到 p is None 时,说明尾结点指向的是 None,不可能存在环,函数返回 False。
  • 复杂度与不足:当前实现使用列表存指针、用 p in record 做查找,最坏情况下时间复杂度接近 (O(n^2));可以将 record 改为 set() 把查找平均降到 (O(1))。总体空间复杂度为 (O(n)),需要额外存储访问过的节点,未达到进阶要求的 (O(1)) 额外空间;进阶通常使用快慢指针(Floyd 判圈)实现常数空间。

代码(Python)解法二:快慢指针(Floyd 判圈算法)

class Solution(object):
    def hasCycle(self, head):
        """
        :type head: ListNode
        :rtype: bool
        """
        if not head or not head.next:
            return False

        slow = head
        fast = head.next

        while fast is not None and fast.next is not None:
            fast = fast.next.next
            slow = slow.next
            if fast == slow:
                return True
        
        return False

思路解析(解法二)

  • 快慢指针直观理解:同时从头结点出发设置两个指针——slow 每次走一步,fast 每次走两步。如果链表中存在环,那么「快」指针总会在环内追上「慢」指针(类似操场上两人绕圈跑),一旦 fast == slow,即可判定有环。
  • 无环情况:如果链表没有环,fast 或 fast.next 总会先走到 None,循环条件 fast is not None and fast.next is not None 失败,从而跳出循环并返回 False。
  • 边界处理:开头用 if not head or not head.next 过滤掉长度为 0 或 1 的链表,这两种情况不可能成环(因为题意中尾指针要么指向某个节点,要么为 None)。
  • 复杂度与优点:时间复杂度为 (O(n)),空间复杂度为 (O(1)),只使用了常数个指针变量,是满足进阶要求的经典解法;相比解法一不需要额外的数组或集合存储访问记录。
posted @ 2026-10-11 17:37  Phier  阅读(2)  评论(0)    收藏  举报