LeetCode 1669 Merge In Between Linked Lists:数组转换、双指针与递归三种解法及多语言实现深度解析
【免费下载链接】leetcodeLeetcode solutions项目地址: https://gitcode.com/GitHub_Trending/leetcode1/leetcode
本篇技术指南以 LeetCode 1669「合并两个链表(Merge In Between Linked Lists)」为核心,系统讲解在单链表中删除区间[a, b]节点并原位插入另一条链表的三种经典思路——转数组(Convert to Array)、双指针(Two Pointers)与递归(Recursion)。本文不仅完整继承 articles/merge-in-between-linked-lists.md 中三种解法的直觉、算法步骤、8 种语言代码与复杂度分析,还结合本仓库实际提交的 Python、Go、Java、Kotlin 源码进行佐证。读完本文,你将掌握单链表指针重连的通用技巧、时空复杂度权衡方法,以及如何避开插入点 off-by-one 等高频坑点。
问题背景与题意理解
mergeInBetween(list1, a, b, list2)要求:给定链表list1与list2,以及两个索引a、b(满足1 <= a <= b < list1.length - 1),从list1中移除下标从a到b的节点,然后将list2整体插入到被移除区间的位置,最后返回list1的头节点。
该题考察的核心是链表的**指针重连(pointer rewiring)**能力:单向链表无法像数组那样按下标随机访问,一切增删改都必须借助next指针的定向修改完成。题目本质上是两个操作的组合:
- 切断:把
list1中a-1号节点(区间前一个节点)的next指向list2的头; - 续接:把
list2的尾节点next指向list1中b+1号节点(区间后一个节点)。
之所以是a - 1和b + 1,是因为题目索引是0-based(a、b是list1中的下标),被删除区间是闭区间[a, b],因此衔接点必须落在区间外的相邻节点上。这一"区间闭开边界"的理解直接决定了后续所有解法的正确性,也是本仓库 articles/merge-in-between-linked-lists.md 开篇强调先掌握以下前置知识的原因。
前置知识(Prerequisites)
在动手实现前,需要具备以下基础,这也是本文关联文档明确列出的要求:
- 链表(Linked Lists):理解单链表节点结构与遍历方式——每个节点包含
val与指向下一个节点的next指针,遍历靠不断前进cur = cur.next; - 指针操纵(Pointer Manipulation):通过修改
next指针来重连节点连接关系,这是链表题的立身之本; - 双指针(Two Pointers):同时在链表中追踪多个位置,常用一个计数器配合指针完成"走到第 N 个节点"的定位任务。
解法一:转数组(Convert To Array)——以空间换直接访问
核心直觉(Intuition)
题目需要删除list1中下标a到b的节点并插入list2,而链表的短板恰恰是无法按下标 O(1) 访问节点。既然这样,索性把list1的所有节点存入一个数组,从而获得任意下标的直接访问能力:连接"a-1号节点 →list2头节点",再连接"list2尾节点 →b+1号节点",两步即可完成拼接。整个思路直白、不易出错,非常适合面试时先讲清楚再做优化。
算法步骤(Algorithm)
- 遍历
list1,将所有节点依次存入一个数组(arr); - 将数组中下标为
a - 1的节点的next指向list2的头节点; - 遍历
list2,找到其最后一个节点(尾节点); - 将
list2尾节点的next指向数组中下标为b + 1的节点; - 返回
list1的头节点。
注意:因为只是重新连线,被删除区间[a, b]中的节点并不需要显式释放——它们只是从主链上"脱钩",后续自然无法从list1头节点出发被访问到(在没有 GC 的语言中,若需要内存管理则要单独处理,见下文"常见陷阱")。
多语言实现
Python
# Definition for singly-linked list. # class ListNode: # def __init__(self, val=0, next=None): # self.val = val # self.next = next class Solution: def mergeInBetween(self, list1: ListNode, a: int, b: int, list2: ListNode) -> ListNode: cur = list1 arr = [] while cur: arr.append(cur) cur = cur.next arr[a - 1].next = list2 cur = list2 while cur.next: cur = cur.next cur.next = arr[b + 1] return list1Java
/** * Definition for singly-linked list. * public class ListNode { * int val; * ListNode next; * ListNode() {} * ListNode(int val) { this.val = val; } * ListNode(int val, ListNode next) { this.val = val; this.next = next; } * } */ public class Solution { public ListNode mergeInBetween(ListNode list1, int a, int b, ListNode list2) { ListNode cur = list1; List<ListNode> arr = new ArrayList<>(); while (cur != null) { arr.add(cur); cur = cur.next; } arr.get(a - 1).next = list2; cur = list2; while (cur.next != null) { cur = cur.next; } cur.next = arr.get(b + 1); return list1; } }C++
/** * Definition for singly-linked list. * struct ListNode { * int val; * ListNode *next; * ListNode() : val(0), next(nullptr) {} * ListNode(int x) : val(x), next(nullptr) {} * ListNode(int x, ListNode *next) : val(x), next(next) {} * }; */ class Solution { public: ListNode* mergeInBetween(ListNode* list1, int a, int b, ListNode* list2) { ListNode* cur = list1; vector<ListNode*> arr; while (cur) { arr.push_back(cur); cur = cur->next; } arr[a - 1]->next = list2; cur = list2; while (cur->next) { cur = cur->next; } cur->next = arr[b + 1]; return list1; } };JavaScript
/** * Definition for singly-linked list. * class ListNode { * constructor(val = 0, next = null) { * this.val = val; * this.next = next; * } * } */ class Solution { /** * @param {ListNode} list1 * @param {number} a * @param {number} b * @param {ListNode} list2 * @return {ListNode} */ mergeInBetween(list1, a, b, list2) { let cur = list1; let arr = []; while (cur) { arr.push(cur); cur = cur.next; } arr[a - 1].next = list2; cur = list2; while (cur.next) { cur = cur.next; } cur.next = arr[b + 1]; return list1; } }Go
/** * Definition for singly-linked list. * type ListNode struct { * Val int * Next *ListNode * } */ func mergeInBetween(list1 *ListNode, a int, b int, list2 *ListNode) *ListNode { cur := list1 arr := []*ListNode{} for cur != nil { arr = append(arr, cur) cur = cur.Next } arr[a-1].Next = list2 cur = list2 for cur.Next != nil { cur = cur.Next } cur.Next = arr[b+1] return list1 }Kotlin
/** * Example: * var li = ListNode(5) * var v = li.`val` * Definition for singly-linked list. * class ListNode(var `val`: Int) { * var next: ListNode? = null * } */ class Solution { fun mergeInBetween(list1: ListNode?, a: Int, b: Int, list2: ListNode?): ListNode? { var cur = list1 val arr = mutableListOf<ListNode>() while (cur != null) { arr.add(cur) cur = cur.next } arr[a - 1].next = list2 cur = list2 while (cur?.next != null) { cur = cur.next } cur?.next = arr[b + 1] return list1 } }Swift
/** * Definition for singly-linked list. * public class ListNode { * public var val: Int * public var next: ListNode? * public init() { self.val = 0; self.next = nil; } * public init(_ val: Int) { self.val = val; self.next = nil; } * public init(_ val: Int, _ next: ListNode?) { self.val = val; self.next = next; } * } */ class Solution { func mergeInBetween(_ list1: ListNode?, _ a: Int, _ b: Int, _ list2: ListNode?) -> ListNode? { var cur = list1 var arr = [ListNode]() while cur != nil { arr.append(cur!) cur = cur?.next } arr[a - 1].next = list2 cur = list2 while cur?.next != nil { cur = cur?.next } cur?.next = arr[b + 1] return list1 } }Rust
// Definition for singly-linked list. // #[derive(PartialEq, Eq, Clone, Debug)] // pub struct ListNode { // pub val: i32, // pub next: Option<Box<ListNode>>, // } impl Solution { pub fn merge_in_between( list1: Option<Box<ListNode>>, a: i32, b: i32, list2: Option<Box<ListNode>>, ) -> Option<Box<ListNode>> { let mut arr: Vec<i32> = Vec::new(); let mut cur = &list1; while let Some(node) = cur { arr.push(node.val); cur = &node.next; } let mut vals2: Vec<i32> = Vec::new(); let mut cur2 = &list2; while let Some(node) = cur2 { vals2.push(node.val); cur2 = &node.next; } let mut result_vals: Vec<i32> = Vec::new(); result_vals.extend_from_slice(&arr[..a as usize]); result_vals.extend_from_slice(&vals2); result_vals.extend_from_slice(&arr[(b + 1) as usize..]); let mut head = None; for &val in result_vals.iter().rev() { let mut node = ListNode::new(val); node.next = head; head = Some(Box::new(node)); } head } }说明:Rust 由于所有权机制,无法像其他语言那样直接持有节点引用数组,因此该版本退化为"收集 val → 重组新链表"的思路,结果一致但语义上更接近值拷贝,可作为 Rust 所有权约束下的参考实现。
时间与空间复杂度
- 时间复杂度:$O(n + m)$——遍历
list1一次、遍历list2一次; - 空间复杂度:$O(n)$——数组额外存储了
list1的全部n个节点。
其中 $n$ 为
list1的长度,$m$ 为list2的长度。
解法二:双指针(Two Pointers)——O(1) 额外空间的原地拼接
核心直觉(Intuition)
转数组法虽然直观,但多花了 $O(n)$ 的空间。实际上我们根本不需要保存所有节点——只需要记住两个关键位置即可:区间前的节点(a - 1号)与区间后的节点(b + 1号)。用一个指针配合计数器在list1上走两段路:先走到a - 1号节点记下它,再继续走到b + 1号节点,最后把这两端分别与list2的头尾接上。这就是本文关联文档推荐的首选解法,也是本仓库 Python、Go、Java、Kotlin 四个已提交实现共同采用的标准写法。
算法步骤(Algorithm)
- 在
list1头部初始化指针curr,计数器i置为0; - 将
curr向前移动,直到i等于a - 1,把当前节点记为head(即区间前节点); - 继续向前移动
curr,直到i超过b,此时curr指向被移除区段之后的第一个节点(即b + 1号节点); - 令
head.next指向list2的头节点; - 遍历
list2找到其尾节点; - 令
list2尾节点的next指向curr; - 返回
list1的头节点。
一个值得注意的细节是:两个循环共用同一个计数器i,第二个循环从i = a - 1的现场继续累加到i > b,因此总共只遍历了 $O(n)$ 个节点,无需二次扫描。
多语言实现
Python
# Definition for singly-linked list. # class ListNode: # def __init__(self, val=0, next=None): # self.val = val # self.next = next class Solution: def mergeInBetween(self, list1: ListNode, a: int, b: int, list2: ListNode) -> ListNode: curr = list1 i = 0 while i < a - 1: curr = curr.next i += 1 head = curr while i <= b: curr = curr.next i += 1 head.next = list2 while list2.next: list2 = list2.next list2.next = curr return list1Java
/** * Definition for singly-linked list. * public class ListNode { * int val; * ListNode next; * ListNode() {} * ListNode(int val) { this.val = val; } * ListNode(int val, ListNode next) { this.val = val; this.next = next; } * } */ public class Solution { public ListNode mergeInBetween(ListNode list1, int a, int b, ListNode list2) { ListNode curr = list1; int i = 0; while (i < a - 1) { curr = curr.next; i++; } ListNode head = curr; while (i <= b) { curr = curr.next; i++; } head.next = list2; while (list2.next != null) { list2 = list2.next; } list2.next = curr; return list1; } }C++
/** * Definition for singly-linked list. * struct ListNode { * int val; * ListNode *next; * ListNode() : val(0), next(nullptr) {} * ListNode(int x) : val(x), next(nullptr) {} * ListNode(int x, ListNode *next) : val(x), next(next) {} * }; */ class Solution { public: ListNode* mergeInBetween(ListNode* list1, int a, int b, ListNode* list2) { ListNode* curr = list1; int i = 0; while (i < a - 1) { curr = curr->next; i++; } ListNode* head = curr; while (i <= b) { curr = curr->next; i++; } head->next = list2; while (list2->next) { list2 = list2->next; } list2->next = curr; return list1; } };JavaScript
/** * Definition for singly-linked list. * class ListNode { * constructor(val = 0, next = null) { * this.val = val; * this.next = next; * } * } */ class Solution { /** * @param {ListNode} list1 * @param {number} a * @param {number} b * @param {ListNode} list2 * @return {ListNode} */ mergeInBetween(list1, a, b, list2) { let curr = list1, i = 0; while (i < a - 1) { curr = curr.next; i++; } let head = curr; while (i <= b) { curr = curr.next; i++; } head.next = list2; while (list2.next) { list2 = list2.next; } list2.next = curr; return list1; } }Go
/** * Definition for singly-linked list. * type ListNode struct { * Val int * Next *ListNode * } */ func mergeInBetween(list1 *ListNode, a int, b int, list2 *ListNode) *ListNode { curr := list1 i := 0 for i < a-1 { curr = curr.Next i++ } head := curr for i <= b { curr = curr.Next i++ } head.Next = list2 for list2.Next != nil { list2 = list2.Next } list2.Next = curr return list1 }Kotlin
/** * Example: * var li = ListNode(5) * var v = li.`val` * Definition for singly-linked list. * class ListNode(var `val`: Int) { * var next: ListNode? = null * } */ class Solution { fun mergeInBetween(list1: ListNode?, a: Int, b: Int, list2: ListNode?): ListNode? { var curr = list1 var i = 0 while (i < a - 1) { curr = curr?.next i++ } val head = curr while (i <= b) { curr = curr?.next i++ } head?.next = list2 var tail = list2 while (tail?.next != null) { tail = tail.next } tail?.next = curr return list1 } }Swift
/** * Definition for singly-linked list. * public class ListNode { * public var val: Int * public var next: ListNode? * public init() { self.val = 0; self.next = nil; } * public init(_ val: Int) { self.val = val; self.next = nil; } * public init(_ val: Int, _ next: ListNode?) { self.val = val; self.next = next; } * } */ class Solution { func mergeInBetween(_ list1: ListNode?, _ a: Int, _ b: Int, _ list2: ListNode?) -> ListNode? { var curr = list1 var i = 0 while i < a - 1 { curr = curr?.next i += 1 } let head = curr while i <= b { curr = curr?.next i += 1 } head?.next = list2 var tail = list2 while tail?.next != nil { tail = tail?.next } tail?.next = curr return list1 } }Rust
impl Solution { pub fn merge_in_between( list1: Option<Box<ListNode>>, a: i32, b: i32, list2: Option<Box<ListNode>>, ) -> Option<Box<ListNode>> { let mut dummy = Some(Box::new(ListNode { val: 0, next: list1 })); let mut cur = &mut dummy; for _ in 0..a { cur = &mut cur.as_mut().unwrap().next; } let mut tail = cur.as_mut().unwrap().next.take(); for _ in 0..=(b - a) { tail = tail.unwrap().next; } cur.as_mut().unwrap().next = list2; let mut cur = cur; while cur.as_ref().unwrap().next.is_some() { cur = &mut cur.as_mut().unwrap().next; } cur.as_mut().unwrap().next = tail; dummy.unwrap().next } }说明:Rust 版本借助哑节点(dummy node)规避头节点特判,并通过
take()从链上摘下待删除区段再续接list2,既体现了双指针思路,也展示了在Option<Box<ListNode>>所有权模型下安全改写链表的惯用法。
时间与空间复杂度
- 时间复杂度:$O(n + m)$——
list1只完整走过一遍,list2额外遍历一遍找尾; - 空间复杂度:$O(1)$ 额外空间——只使用了常数个指针变量。
其中 $n$ 为
list1的长度,$m$ 为list2的长度。
仓库源码佐证
本仓库中已提交的四个语言实现与上文双指针解法完全一致,可作为可直接运行的正确性参照:
- python/1669-merge-in-between-linked-lists.py:
while i < a - 1定位head,while i <= b越过删除区段,随后head.next = list2并遍历list2找到尾节点完成续接; - go/1669-merge-in-between-linked-lists.go:逻辑与 Python 版一一对应,仅将
curr.next替换为curr.Next(Go 导出字段命名); - java/1669-merge-in-between-linked-lists.java:循环条件
list2.next != null显式判空,其余结构与上述实现一致; - kotlin/1669-merge-in-between-linked-lists.kt:在可空类型
ListNode?上使用?.next安全调用,并单独引入tail变量遍历list2尾部。
可见该仓库将双指针法作为该题的标准解答收录,这也印证了它是三种解法中综合最优的选择。
解法三:递归(Recursion)——用调用栈代替显式计数器
核心直觉(Intuition)
递归解法把"指针移动"抽象为"缩小问题规模":每深入一层list1,就把a和b各减 1,直到某个基准条件成立。当a减到 1 时,当前节点正是插入点(即a-1号节点),此时把list2挂上去;随后携带list2的尾节点继续递归,让b继续倒数;当b减到 0 时,说明待删除节点已全部越过,把list2的尾部接到剩余链表上即可。递归深度的上限就是list1的长度,因此空间复杂度为 $O(n)$。
算法步骤(Algorithm)
- 若
a == 1,说明到达插入点:- 保存
list1.next为nxt; - 令
list1.next = list2; - 遍历到
list2末尾找到尾节点; - 以
nxt、a = 0、b - 1、list2的尾节点为参数递归调用自身; - 返回
list1。
- 保存
- 若
b == 0,说明所有待删除节点均已跳过:- 令
list2.next = list1.next,把list2尾部接到list1剩余部分上; - 返回
list1。
- 令
- 其他情况:对
list1.next以a - 1、b - 1递归调用; - 返回
list1。
整个递归过程以递减的a定位插入点、以递减的b控制跳过的节点数,两套计数在同一个调用链上协同工作,是"把迭代式指针移动改写为函数式状态传递"的典型示范。
多语言实现
Python
# Definition for singly-linked list. # class ListNode: # def __init__(self, val=0, next=None): # self.val = val # self.next = next class Solution: def mergeInBetween(self, list1: ListNode, a: int, b: int, list2: ListNode) -> ListNode: if a == 1 : nxt = list1.next list1.next = list2 while list2.next: list2 = list2.next self.mergeInBetween(nxt, 0, b - 1, list2) return list1 if b == 0: list2.next = list1.next return list1 self.mergeInBetween(list1.next, a - 1, b - 1, list2) return list1Java
/** * Definition for singly-linked list. * public class ListNode { * int val; * ListNode next; * ListNode() {} * ListNode(int val) { this.val = val; } * ListNode(int val, ListNode next) { this.val = val; this.next = next; } * } */ public class Solution { public ListNode mergeInBetween(ListNode list1, int a, int b, ListNode list2) { if (a == 1) { ListNode nxt = list1.next; list1.next = list2; while (list2.next != null) { list2 = list2.next; } mergeInBetween(nxt, 0, b - 1, list2); return list1; } if (b == 0) { list2.next = list1.next; return list1; } mergeInBetween(list1.next, a - 1, b - 1, list2); return list1; } }C++
/** * Definition for singly-linked list. * struct ListNode { * int val; * ListNode *next; * ListNode() : val(0), next(nullptr) {} * ListNode(int x) : val(x), next(nullptr) {} * ListNode(int x, ListNode *next) : val(x), next(next) {} * }; */ class Solution { public: ListNode* mergeInBetween(ListNode* list1, int a, int b, ListNode* list2) { if (a == 1) { ListNode* nxt = list1->next; list1->next = list2; while (list2->next) { list2 = list2->next; } mergeInBetween(nxt, 0, b - 1, list2); return list1; } if (b == 0) { list2->next = list1->next; return list1; } mergeInBetween(list1->next, a - 1, b - 1, list2); return list1; } };JavaScript
/** * Definition for singly-linked list. * class ListNode { * constructor(val = 0, next = null) { * this.val = val; * this.next = next; * } * } */ class Solution { /** * @param {ListNode} list1 * @param {number} a * @param {number} b * @param {ListNode} list2 * @return {ListNode} */ mergeInBetween(list1, a, b, list2) { if (a === 1) { let nxt = list1.next; list1.next = list2; while (list2.next) { list2 = list2.next; } this.mergeInBetween(nxt, 0, b - 1, list2); return list1; } if (b === 0) { list2.next = list1.next; return list1; } this.mergeInBetween(list1.next, a - 1, b - 1, list2); return list1; } }Go
/** * Definition for singly-linked list. * type ListNode struct { * Val int * Next *ListNode * } */ func mergeInBetween(list1 *ListNode, a int, b int, list2 *ListNode) *ListNode { if a == 1 { nxt := list1.Next list1.Next = list2 for list2.Next != nil { list2 = list2.Next } mergeInBetween(nxt, 0, b-1, list2) return list1 } if b == 0 { list2.Next = list1.Next return list1 } mergeInBetween(list1.Next, a-1, b-1, list2) return list1 }Kotlin
/** * Example: * var li = ListNode(5) * var v = li.`val` * Definition for singly-linked list. * class ListNode(var `val`: Int) { * var next: ListNode? = null * } */ class Solution { fun mergeInBetween(list1: ListNode?, a: Int, b: Int, list2: ListNode?): ListNode? { if (a == 1) { val nxt = list1?.next list1?.next = list2 var tail = list2 while (tail?.next != null) { tail = tail.next } mergeInBetween(nxt, 0, b - 1, tail) return list1 } if (b == 0) { list2?.next = list1?.next return list1 } mergeInBetween(list1?.next, a - 1, b - 1, list2) return list1 } }Swift
/** * Definition for singly-linked list. * public class ListNode { * public var val: Int * public var next: ListNode? * public init() { self.val = 0; self.next = nil; } * public init(_ val: Int) { self.val = val; self.next = nil; } * public init(_ val: Int, _ next: ListNode?) { self.val = val; self.next = next; } * } */ class Solution { func mergeInBetween(_ list1: ListNode?, _ a: Int, _ b: Int, _ list2: ListNode?) -> ListNode? { if a == 1 { let nxt = list1?.next list1?.next = list2 var tail = list2 while tail?.next != nil { tail = tail?.next } _ = mergeInBetween(nxt, 0, b - 1, tail) return list1 } if b == 0 { list2?.next = list1?.next return list1 } _ = mergeInBetween(list1?.next, a - 1, b - 1, list2) return list1 } }Rust
impl Solution { pub fn merge_in_between( list1: Option<Box<ListNode>>, a: i32, b: i32, list2: Option<Box<ListNode>>, ) -> Option<Box<ListNode>> { fn helper( list1: Option<Box<ListNode>>, a: i32, b: i32, list2: Option<Box<ListNode>>, ) -> Option<Box<ListNode>> { let mut node = list1.unwrap(); if a == 1 { let nxt = node.next.take(); let mut tail = list2; let mut result_vals = vec![node.val]; let mut cur = &tail; let mut l2_vals = Vec::new(); while let Some(n) = cur { l2_vals.push(n.val); cur = &n.next; } let mut remaining = nxt; for _ in 0..b { remaining = remaining.unwrap().next; } let mut vals = result_vals; vals.extend(l2_vals); let mut cur = &remaining; while let Some(n) = cur { vals.push(n.val); cur = &n.next; } let mut head = None; for &val in vals.iter().rev() { let mut n = ListNode::new(val); n.next = head; head = Some(Box::new(n)); } return head; } node.next = helper(node.next, a - 1, b - 1, list2); Some(node) } helper(list1, a, b, list2) } }时间与空间复杂度
- 时间复杂度:$O(n + m)$——每个节点至多被访问常数次;
- 空间复杂度:$O(n)$——递归调用栈深度最多为 $n$。
其中 $n$ 为
list1的长度,$m$ 为list2的长度。
需要提醒的是:递归解法在超长链表上可能触发调用栈溢出,实际工程中优先考虑迭代写法;它更适合作为面试中展示"把循环改写为递归"思维能力的加分项。
三种解法横向对比
| 解法 | 核心思想 | 时间复杂度 | 空间复杂度 | 适用场景 |
|---|---|---|---|---|
| 转数组(Convert to Array) | 用数组换取按下标直接访问 | $O(n + m)$ | $O(n)$ | 思路最直观、最不易写错,适合先讲清楚解法 |
| 双指针(Two Pointers) | 只记录a-1与b+1两个关键节点 | $O(n + m)$ | $O(1)$ | 综合最优,面试与工程首选,本仓库标准实现 |
| 递归(Recursion) | 用调用栈代替计数器,递减a、b定位 | $O(n + m)$ | $O(n)$(递归栈) | 展示递归思维;链表极长时需警惕栈溢出 |
三种解法的时间复杂度相同,差异集中在空间开销与可读性上:转数组法最直观但空间最差,双指针法兼顾简洁与高效,递归法提供了一种函数式视角但受限于栈深度。
常见陷阱(Common Pitfalls)
插入点的 off-by-one 错误
a - 1号节点应指向list2,list2的尾节点应指向b + 1号节点。如果直接使用下标a或b进行连接,拼接结果会错位——要么多删一个节点,要么少删一个节点。这是本类题目出现频率最高的错误,务必牢记区间[a, b]是闭区间。
忘记寻找 list2 的尾节点
将a - 1号节点连到list2头节点之后,必须遍历list2找到尾节点,再把尾节点连到list1的剩余部分。如果跳过这一步,合并后的链表会在list2的末尾断掉,导致结果不完整——这在三份解法(包括仓库标准实现)中都是单独的显式遍历步骤,可见其不可省略。
未处理被删除的节点
下标a到b之间的节点已不再属于结果链表。在没有垃圾回收的语言(如 C/C++)中,若这些节点是动态分配的,需要单独释放以避免内存泄漏;在带 GC 的语言(如 Python、Java、Go、Kotlin)中,这些节点失去引用后会自动被回收,无需额外处理。此外,若被摘除的区段后续还有引用需求,也可考虑将其单独保存复用。
总结
LeetCode 1669 是检验链表基本功的经典题目:它要求你在理解 0-based 闭区间语义的前提下,完成"定位 → 切断 → 插入 → 续接"四个动作。三种解法层层递进——转数组法以空间换直观、双指针法以 O(1) 空间达到最优、递归法以函数式视角换一种实现思路。若要在面试与实战中给出最稳妥的答案,建议掌握并优先采用双指针解法,同时理解转数组与递归两种变体的取舍;完整的多语言实现与讲解可随时回溯 articles/merge-in-between-linked-lists.md,仓库中 python/1669-merge-in-between-linked-lists.py、go/1669-merge-in-between-linked-lists.go、java/1669-merge-in-between-linked-lists.java、kotlin/1669-merge-in-between-linked-lists.kt 则是经过验证的可运行参考实现。
【免费下载链接】leetcodeLeetcode solutions项目地址: https://gitcode.com/GitHub_Trending/leetcode1/leetcode
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考