LeetCode链表经典五题:从相交到环形,双指针的妙用

🔥你好我是fengxin_rou这是我的个人主页 fengxin_rou的主页

❄️欢迎查看我的专栏我的专栏

《Java后端学习》《JAVASE基础》《JUC并发》《redis》《JVM虚拟机》《MYSQL》《黑马点评》《rabbitmq》《JavaWeb+AI的talis学习系统》《苍穹外卖》

目录

引言

一、相交链表:双指针的巧妙应用

[1.1 题目描述](#1.1 题目描述)

[1.2 核心思路](#1.2 核心思路)

[1.3 代码实现](#1.3 代码实现)

[1.4 详细解析](#1.4 详细解析)

[1.5 复杂度分析](#1.5 复杂度分析)

[1.6 边界情况](#1.6 边界情况)

二、反转链表:迭代法详解

[2.1 题目描述](#2.1 题目描述)

[2.2 核心思路](#2.2 核心思路)

[2.3 代码实现](#2.3 代码实现)

[2.4 详细解析](#2.4 详细解析)

[2.5 图解演示](#2.5 图解演示)

[2.6 复杂度分析](#2.6 复杂度分析)

三、回文链表:快慢指针与反转结合

[3.1 题目描述](#3.1 题目描述)

[3.2 核心思路](#3.2 核心思路)

[3.3 代码实现](#3.3 代码实现)

[3.4 详细解析](#3.4 详细解析)

[3.5 边界情况](#3.5 边界情况)

四、环形链表:快慢指针检测

[4.1 题目描述](#4.1 题目描述)

[4.2 核心思路](#4.2 核心思路)

[4.3 代码实现](#4.3 代码实现)

[4.4 详细解析](#4.4 详细解析)

[4.5 数学证明](#4.5 数学证明)

[4.6 复杂度分析](#4.6 复杂度分析)

五、环形链表II:数学推导的精妙

[5.1 题目描述](#5.1 题目描述)

[5.2 核心思路](#5.2 核心思路)

[5.3 代码实现](#5.3 代码实现)

[5.4 数学推导](#5.4 数学推导)

[5.5 为什么这个推导成立?](#5.5 为什么这个推导成立?)

[5.6 复杂度分析](#5.6 复杂度分析)

六、总结与最佳实践

[6.1 技术总结](#6.1 技术总结)

[6.2 最佳实践](#6.2 最佳实践)

[6.3 常见陷阱](#6.3 常见陷阱)

七、常见问题解答

[Q1: 为什么双指针法能解决相交问题?](#Q1: 为什么双指针法能解决相交问题?)

[Q2: 反转链表时为什么要保存下一个节点?](#Q2: 反转链表时为什么要保存下一个节点?)

[Q3: 快慢指针找中点时,奇偶长度有什么区别?](#Q3: 快慢指针找中点时,奇偶长度有什么区别?)

[Q4: 环形链表II的数学推导为什么成立?](#Q4: 环形链表II的数学推导为什么成立?)

[Q5: 这些技巧在实际开发中有什么应用?](#Q5: 这些技巧在实际开发中有什么应用?)


摘要:本文深入剖析LeetCode链表专题的五道经典题目,从相交链表到环形链表II,全面讲解双指针技术的应用。通过详细的代码分析、图解演示和数学推导,帮助读者掌握链表操作的核心技巧。

引言

链表作为数据结构中的基础结构,是算法面试中的高频考点。LeetCode上的链表题目不仅考察对指针操作的理解,更考验逻辑思维和问题抽象能力。本文精选五道经典链表题目,涵盖相交、反转、回文、环检测等核心问题,通过双指针技术这一强大工具,带你深入理解链表操作的精髓。


一、相交链表:双指针的巧妙应用

1.1 题目描述

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

C1 -> C2 -> C3, List B: B1 -> B2 -> B3 -> C1 -> C2 -> C3. Show two pointers starting at headA and headB, moving forward, and meeting at the intersection node C1. Professional engineering style with clear labels. 尺寸:1024x768 风格:自然 质量:高质量 -->

1.2 核心思路

双指针法:让两个指针分别从两个链表的头节点开始遍历,当一个指针到达链表末尾时,跳转到另一个链表的头节点继续遍历。如果两个链表相交,两个指针必然会在交点相遇。

1.3 代码实现

java 复制代码
public class Solution {
    public ListNode getIntersectionNode(ListNode headA, ListNode headB) {
        ListNode A = headA, B = headB;
        while(A != B){
            A = A != null ? A.next : headB;
            B = B != null ? B.next : headA;
        }
        return A;
    }
}

1.4 详细解析

指针移动规则

  • 指针 AheadA 开始遍历,到达末尾后跳转到 headB
  • 指针 BheadB 开始遍历,到达末尾后跳转到 headA
  • A == B 时,要么是交点,要么都是 null(无交点)

为什么这个方法有效? 设:

  • 链表A的长度为 a,公共部分长度为 c
  • 链表B的长度为 b,公共部分长度为 c

则指针A走过的路径:a + c + b(跳转后走B的非公共部分) 指针B走过的路径:b + c + a(跳转后走A的非公共部分)

两者相等,因此必然会在交点相遇。

1.5 复杂度分析

  • 时间复杂度:O(m + n),其中m和n分别是两个链表的长度
  • 空间复杂度:O(1),只使用了两个指针

1.6 边界情况

  1. 两个链表都为空
  2. 一个链表为空,另一个不为空
  3. 两个链表没有交点
  4. 两个链表长度相同且有交点

二、反转链表:迭代法详解

2.1 题目描述

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

2 -> 3 -> 4 -> 5. Intermediate steps showing pointer changes with pre, cur, tmp pointers. Final reversed list: 5 -> 4 -> 3 -> 2 -> 1. Use arrows to show pointer movements. Professional technical illustration style. 尺寸:1024x768 风格:自然 质量:高质量 -->

2.2 核心思路

迭代法 :使用三个指针,逐步调整每个节点的 next 指针方向。

2.3 代码实现

java 复制代码
class Solution {
    public ListNode reverseList(ListNode head) {
        ListNode pre = null;
        ListNode cur = head;
        while( cur != null){
            ListNode tmp = cur.next; // 存储下一个节点
            cur.next = pre;         // 反转当前节点
            pre = cur;              // pre前进一步
            cur = tmp;              // cur前进一步
        }
        return pre; // pre是新的头节点
    }
}

2.4 详细解析

指针角色

  • pre:前一个节点,初始为 null
  • cur:当前节点,初始为 head
  • tmp:临时存储下一个节点,防止链表断裂

操作步骤

  1. 保存 cur.nexttmp
  2. cur.next 指向 pre(反转)
  3. pre 移动到 cur
  4. cur 移动到 tmp

2.5 图解演示

以链表 1 -> 2 -> 3 -> 4 -> 5 为例:

步骤 pre cur tmp 操作
初始 null 1 - -
1 null 1 2 1.next = null
2 1 2 3 2.next = 1
3 2 3 4 3.next = 2
4 3 4 5 4.next = 3
5 4 5 null 5.next = 4
结束 5 null - 返回5

2.6 复杂度分析

  • 时间复杂度:O(n),遍历一次链表
  • 空间复杂度:O(1),只使用了常数个指针

三、回文链表:快慢指针与反转结合

3.1 题目描述

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

3.2 核心思路

三步法

  1. 使用快慢指针找到链表中点
  2. 反转后半部分链表
  3. 比较前半部分和反转后的后半部分

3.3 代码实现

java 复制代码
class Solution {
    public boolean isPalindrome(ListNode head) {
        ListNode mid = middleNode(head);
        ListNode head2 = reverseList(mid);
        while(head2 != null){
            if(head.val != head2.val){
                return false;
            }
            head = head.next;
            head2 = head2.next;
        }
        return true;
    }
    
    private ListNode middleNode(ListNode head) {
        ListNode slow = head;
        ListNode fast = head;
        while(fast != null && fast.next != null){
            slow = slow.next;
            fast = fast.next.next;
        }
        return slow;
    }
    
    private ListNode reverseList(ListNode head){
        ListNode cur = head;
        ListNode pre = null;
        while( cur != null){
            ListNode tmp = cur.next;
            cur.next = pre;
            pre = cur;
            cur = tmp;
        }
        return pre;
    }
}

3.4 详细解析

快慢指针找中点

  • slow 每次移动一步
  • fast 每次移动两步
  • fast 到达末尾时,slow 刚好在中点

为什么这样能找到中点?

  • 奇数长度链表:slow 停在正中间
  • 偶数长度链表:slow 停在中间偏右的位置

3.5 边界情况

  1. 链表为空
  2. 链表只有一个节点
  3. 链表长度为偶数
  4. 链表长度为奇数

四、环形链表:快慢指针检测

4.1 题目描述

给你一个链表的头节点 head,判断链表中是否有环。如果链表中存在环,则返回 true;否则,返回 false

0 -> -4 -> back to 2. Show slow and fast pointers moving, meeting inside the cycle. Professional engineering style with clear labels. 尺寸:1024x768 风格:自然 质量:高质量 -->

4.2 核心思路

快慢指针法:快指针每次移动两步,慢指针每次移动一步。如果存在环,快指针最终会追上慢指针。

4.3 代码实现

java 复制代码
public class Solution {
    public boolean hasCycle(ListNode head) {
        ListNode slow = head;
        ListNode fast = head;
        while(fast != null && fast.next != null){
            slow = slow.next;
            fast = fast.next.next;
            if(slow == fast){
                return true;
            }
        }
        return false;
    }
}

4.4 详细解析

为什么快指针一定能追上慢指针?

  • 如果存在环,快指针和慢指针都会进入环中
  • 快指针速度是慢指针的两倍,相对速度为1
  • 因此快指针每轮都会接近慢指针一个节点的距离
  • 最终必然会在环内某点相遇

4.5 数学证明

设:

  • 环的长度为 L
  • 快指针和慢指针的初始距离为 D
  • 每轮快指针比慢指针多走 1

则相遇所需轮数:D

4.6 复杂度分析

  • 时间复杂度:O(n),最坏情况下需要遍历整个链表
  • 空间复杂度:O(1),只使用了两个指针

五、环形链表II:数学推导的精妙

5.1 题目描述

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

5.2 核心思路

两步法

  1. 使用快慢指针找到环内相遇点
  2. 从相遇点和头节点同时出发,每次一步,最终在环入口相遇

5.3 代码实现

java 复制代码
public class Solution {
    public ListNode detectCycle(ListNode head) {
        ListNode slow = head, fast = head;
        while(true){
            if(fast == null || fast.next == null) return null;
            slow = slow.next;
            fast = fast.next.next;
            if(slow == fast) break;
        }
        fast = head;
        while(slow != fast){
            slow = slow.next;
            fast = fast.next;
        }
        return fast;
    }
}

5.4 数学推导

变量定义

  • a:头节点到环入口的距离
  • b:环入口到相遇点的距离
  • c:相遇点到环入口的距离(环剩余部分)
  • 环的总长度:b + c

第一次相遇时

  • 慢指针走过的距离:a + b
  • 快指针走过的距离:a + b + n(b + c)n是快指针绕环的圈数)

速度关系: 快指针速度是慢指针的两倍,因此:

复制代码
2(a + b) = a + b + n(b + c)

化简得:

复制代码
a = (n-1)(b + c) + c

结论 : 从相遇点和头节点同时出发,每次一步,经过 a 步后必然在环入口相遇。

5.5 为什么这个推导成立?

  1. 慢指针进入环时,快指针已经在环内某处
  2. 快指针每次比慢指针多走一步,最终会追上慢指针
  3. 相遇时,快指针比慢指针多走了整数圈

5.6 复杂度分析

  • 时间复杂度:O(n),需要遍历链表
  • 空间复杂度:O(1),只使用了两个指针

六、总结与最佳实践

6.1 技术总结

题目 核心技术 时间复杂度 空间复杂度
相交链表 双指针 O(m+n) O(1)
反转链表 迭代法 O(n) O(1)
回文链表 快慢指针+反转 O(n) O(1)
环形链表 快慢指针 O(n) O(1)
环形链表II 快慢指针+数学推导 O(n) O(1)

6.2 最佳实践

  1. 指针操作要细心:每次移动指针前,先保存下一个节点
  2. 边界条件要覆盖:空链表、单节点、偶数长度等情况
  3. 复杂度要分析:时间复杂度和空间复杂度都要考虑
  4. 图解辅助理解:画图是理解指针操作的最佳方式
  5. 代码要简洁:避免冗余变量,保持代码清晰

6.3 常见陷阱

  1. 空指针异常 :访问 next 前检查节点是否为 null
  2. 无限循环:确保循环条件正确
  3. 链表断裂:反转时要保存下一个节点
  4. 边界遗漏:测试所有可能的边界情况

七、常见问题解答

Q1: 为什么双指针法能解决相交问题?

A : 因为两个指针走过的总路径长度相同。如果两个链表相交,指针必然会在交点相遇;如果不相交,指针会同时到达两个链表的末尾(都为 null)。

Q2: 反转链表时为什么要保存下一个节点?

A : 因为反转当前节点的 next 指针后,就会丢失对下一个节点的引用。保存 tmp = cur.next 可以防止链表断裂。

Q3: 快慢指针找中点时,奇偶长度有什么区别?

A:

  • 奇数长度:slow 停在正中间
  • 偶数长度:slow 停在中间偏右的位置
  • 两种情况都能正确处理回文判断

Q4: 环形链表II的数学推导为什么成立?

A: 这基于速度差原理。快指针速度是慢指针的两倍,因此快指针会比慢指针多走整数圈。通过数学推导,可以证明从相遇点和头节点同时出发,必然在环入口相遇。

Q5: 这些技巧在实际开发中有什么应用?

A:

  • 链表相交:数据库连接池、多级缓存架构
  • 链表反转:撤销操作、历史记录
  • 回文检测:字符串处理、数据校验
  • 环检测:死锁检测、循环依赖分析
相关推荐
代码中介商1 小时前
数据结构进阶(五):最短路径——Dijkstra 与 Floyd 算法
数据结构·算法
KaMeidebaby1 小时前
卡梅德生物技术快报|抗原如何自己检测?FAdV-4 重组抗原制备与 ELISA 体系技术调试指南
前端·人工智能·物联网·算法·百度
却道天凉_好个秋1 小时前
HEVC(二):如何实现并行处理
人工智能·算法·计算机视觉·hevc·瓦片技术·波前并行处理wpp
wayz111 小时前
Momentum:QQE(定量定性估计)技术指标详解
算法·金融·数据分析·量化交易·特征工程
Dontla1 小时前
聚类找不到簇原因分析(聚类失败)(DBSCAN聚类算法、eps参数、Epsilon参数、最大允许距离)
算法·数据挖掘·聚类
写代码写到手抽筋9 小时前
5G上行DCI字段判定:端口 流数 PMI选择详解
java·算法·5g
xieliyu.10 小时前
Java算法精讲:双指针(二)
java·开发语言·算法
wayz1110 小时前
Momentum:PSL(心理线指标)技术指标详解
算法·金融·数据分析·量化交易·特征工程
8Qi811 小时前
LeetCode 213:打家劫舍 II(House Robber II)—— 题解 ✅
算法·leetcode·职场和发展·动态规划