1. 链表算法基础与解题框架
链表作为数据结构中的经典类型,在算法面试中出现的频率极高。与数组不同,链表通过指针连接各个节点,这种非连续存储的特性带来了独特的操作方式和解题思路。掌握链表问题的核心在于理解指针操作和递归思维,下面我将从基础开始,逐步拆解LeetCode中常见的链表问题解法。
1.1 链表数据结构特点
链表由节点(Node)组成,每个节点包含两个部分:数据域(val)和指针域(next)。单链表的基本结构如下:
java复制class ListNode {
int val;
ListNode next;
ListNode(int x) { val = x; }
}
链表操作有几个关键特性需要特别注意:
- 头节点处理:很多操作需要特殊处理头节点,使用虚拟头节点(dummy node)可以简化逻辑
- 指针丢失:在修改next指针前,必须提前保存后续节点的引用
- 边界条件:空链表、单节点链表、操作头节点/尾节点等情况需要单独考虑
1.2 链表问题通用解题框架
解决链表问题时,我通常会遵循以下思考框架:
-
确定解法模式:判断问题适合迭代还是递归解法。迭代通常需要维护多个指针,递归则需要明确定义递归函数的作用和终止条件。
-
处理边界条件:考虑空链表、单节点链表、操作头尾节点等特殊情况。
-
指针操作规划:在纸上画出指针变化前后的链表状态,确保不会出现指针丢失或循环引用。
-
复杂度分析:明确时间复杂度和空间复杂度要求,选择最优解法。
-
测试验证:用简单测试用例验证代码,特别注意边界情况。
需要模型API调用? 免费领10W Token,多模型网关一键接入 Claude、DeepSeek 等主流模型。
2. 合并两个有序链表
合并两个有序链表是链表操作的基础问题,它展示了如何同时遍历两个链表并进行指针操作。这个问题有两种经典解法:递归和迭代。
2.1 递归解法详解
递归解法的核心思想是:比较两个链表当前节点的值,选择较小的节点作为合并后的当前节点,然后递归处理剩余部分。
java复制public ListNode mergeTwoLists(ListNode l1, ListNode l2) {
// 递归终止条件:任一链表为空,直接返回另一个
if (l1 == null) return l2;
if (l2 == null) return l1;
// 选择较小节点作为当前节点,并递归合并剩余部分
if (l1.val <= l2.val) {
l1.next = mergeTwoLists(l1.next, l2);
return l1;
} else {
l2.next = mergeTwoLists(l1, l2.next);
return l2;
}
}
注意事项:
- 递归终止条件必须放在最前面,防止空指针异常
- 每次递归调用都会消耗栈空间,链表过长可能导致栈溢出
- 时间复杂度O(n+m),空间复杂度O(n+m)(递归栈深度)
2.2 迭代解法详解
迭代解法使用双指针遍历两个链表,通过比较节点值决定连接顺序,并使用虚拟头节点简化操作。
java复制public ListNode mergeTwoLists(ListNode l1, ListNode l2) {
ListNode dummy = new ListNode(-1); // 虚拟头节点
ListNode curr = dummy; // 当前指针
while (l1 != null && l2 != null) {
if (l1.val <= l2.val) {
curr.next = l1;
l1 = l1.next;
} else {
curr.next = l2;
l2 = l2.nex
