【LeetCode】21. 合并两个有序链表
文章目录
- 21. 合并两个有序链表
- 题目描述
- 示例 1:
- 示例 2:
- 示例 3:
- 提示:
- 解题思路
- 思路流程(迭代-哨兵)
- 解法1:迭代(哨兵节点)- 推荐
- 解法2:递归
- 解法3:原地指针连接
- 复杂度分析
- 示例演示
- 边界与正确性
- 运行方式
- 测试用例
- 完整题解代码
21. 合并两个有序链表
题目描述
将两个升序链表合并为一个新的 升序 链表并返回。新链表是通过拼接给定的两个链表的所有节点组成的。
示例 1:
输入:l1 = [1,2,4], l2 = [1,3,4]
输出:[1,1,2,3,4,4]
示例 2:
输入:l1 = [], l2 = []
输出:[]
示例 3:
输入:l1 = [], l2 = [0]
输出:[0]
提示:
- 两个链表的节点数目范围是 [0, 50]
- -100 <= Node.val <= 100
- l1 和 l2 均按 非递减顺序 排列
解题思路
本题要求将两个已按非递减顺序排列的链表合并为一个新的有序链表。关键在于:每次从两个链表当前头部较小的节点中取出一个接到结果链表后面,直至某一方为空,再把剩余部分整体接上。
- 核心操作:双指针比较
l1.Val
与l2.Val
- 有序性保证:每次选择较小值,整体仍保持升序
思路流程(迭代-哨兵)
graph TDA[开始: l1, l2 均为已排序链表] --> B[创建哨兵节点 dummy, cur 指向 dummy]B --> C{l1 与 l2 是否都非空?}C -- 否 --> H[将非空的剩余链表整体接到 cur.Next]C -- 是 --> D{l1.Val <= l2.Val?}D -- 是 --> E[cur.Next = l1; l1 = l1.Next]D -- 否 --> F[cur.Next = l2; l2 = l2.Next]E --> G[cur = cur.Next]F --> GG --> CH --> I[返回 dummy.Next 作为新链表头]
解法1:迭代(哨兵节点)- 推荐
- 思路:使用一个虚拟头
dummy
,指针cur
逐步拼接较小节点 - 优势:实现简单、指针移动清晰、无需额外空间
- 时间复杂度:O(m+n)
- 空间复杂度:O(1)
解法2:递归
- 思路:较小节点作为头,递归合并其余部分
- 适用:代码更简洁,但递归层数受链表总长度限制
- 时间复杂度:O(m+n)
- 空间复杂度:O(m+n)(递归栈)
解法3:原地指针连接
- 思路:不新建节点,仅移动指针连接已有节点,等价于迭代
- 时间复杂度:O(m+n)
- 空间复杂度:O(1)
复杂度分析
解法 | 时间复杂度 | 空间复杂度 | 说明 |
---|---|---|---|
迭代-哨兵 | O(m+n) | O(1) | 推荐,简单高效 |
递归 | O(m+n) | O(m+n) | 代码短,栈深最多 m+n |
原地指针 | O(m+n) | O(1) | 与迭代等价,书写风格不同 |
示例演示
输入:l1 = [1,2,4]
,l2 = [1,3,4]
合并过程(迭代-哨兵):
- 比较 1 与 1 → 取
l1
的 1;结果[1]
- 比较 2 与 1 → 取
l2
的 1;结果[1,1]
- 比较 2 与 3 → 取 2;结果
[1,1,2]
- 比较 4 与 3 → 取 3;结果
[1,1,2,3]
- 比较 4 与 4 → 取
l1
的 4;结果[1,1,2,3,4]
l1
空,将l2
剩余 4 接上 →[1,1,2,3,4,4]
边界与正确性
- 任一为空:直接返回另一条链表
- 全为空:返回空
- 负数、重复值、不同长度:均能保持全局非递减
- 稳定性:相等值时优先取
l1
,保持原相对顺序
运行方式
cd 21
go run main.go
测试用例
// 基本
l1=[1,2,4], l2=[1,3,4] → [1,1,2,3,4,4]
l1=[], l2=[] → []
l1=[], l2=[0] → [0]// 混合、负数
l1=[-10,-3,0,5], l2=[-5,-3,2] → [-10,-5,-3,-3,0,2,5]
完整题解代码
package mainimport ("fmt"
)// 单链表节点定义
type ListNode struct {Val intNext *ListNode
}// 解法1:迭代-哨兵节点(推荐):时间 O(m+n),空间 O(1)
func mergeTwoListsIterative(l1 *ListNode, l2 *ListNode) *ListNode {dummy := &ListNode{}cur := dummyfor l1 != nil && l2 != nil {if l1.Val <= l2.Val {cur.Next = l1l1 = l1.Next} else {cur.Next = l2l2 = l2.Next}cur = cur.Next}if l1 != nil {cur.Next = l1} else {cur.Next = l2}return dummy.Next
}// 解法2:递归:时间 O(m+n),空间 O(m+n)(递归栈)
func mergeTwoListsRecursive(l1 *ListNode, l2 *ListNode) *ListNode {if l1 == nil {return l2}if l2 == nil {return l1}if l1.Val <= l2.Val {l1.Next = mergeTwoListsRecursive(l1.Next, l2)return l1}l2.Next = mergeTwoListsRecursive(l1, l2.Next)return l2
}// 解法3:原地指针连接(本质与迭代等价,演示另一种写法)
func mergeTwoListsInPlace(l1 *ListNode, l2 *ListNode) *ListNode {if l1 == nil {return l2}if l2 == nil {return l1}var head, tail *ListNodeif l1.Val <= l2.Val {head, tail, l1 = l1, l1, l1.Next} else {head, tail, l2 = l2, l2, l2.Next}for l1 != nil && l2 != nil {if l1.Val <= l2.Val {tail.Next = l1l1 = l1.Next} else {tail.Next = l2l2 = l2.Next}tail = tail.Next}if l1 != nil {tail.Next = l1} else if l2 != nil {tail.Next = l2}return head
}// 工具函数:由切片构建链表
func buildList(nums []int) *ListNode {var head, cur *ListNodefor _, v := range nums {node := &ListNode{Val: v}if head == nil {head = nodecur = node} else {cur.Next = nodecur = node}}return head
}// 工具函数:链表转切片
func listToSlice(head *ListNode) []int {res := []int{}for head != nil {res = append(res, head.Val)head = head.Next}return res
}func runTests() {fmt.Println("=== 21. 合并两个有序链表 测试 ===")cases := []struct {l1 []intl2 []intwant []int}{{[]int{1, 2, 4}, []int{1, 3, 4}, []int{1, 1, 2, 3, 4, 4}},{[]int{}, []int{}, []int{}},{[]int{}, []int{0}, []int{0}},{[]int{-10, -3, 0, 5}, []int{-5, -3, 2}, []int{-10, -5, -3, -3, 0, 2, 5}},}for i, c := range cases {l1 := buildList(c.l1)l2 := buildList(c.l2)got1 := listToSlice(mergeTwoListsIterative(l1, l2))fmt.Printf("用例#%d 迭代: got=%v, want=%v\n", i+1, got1, c.want)l1 = buildList(c.l1)l2 = buildList(c.l2)got2 := listToSlice(mergeTwoListsRecursive(l1, l2))fmt.Printf("用例#%d 递归: got=%v, want=%v\n", i+1, got2, c.want)l1 = buildList(c.l1)l2 = buildList(c.l2)got3 := listToSlice(mergeTwoListsInPlace(l1, l2))fmt.Printf("用例#%d 原地: got=%v, want=%v\n", i+1, got3, c.want)}
}func main() {runTests()
}
// end