当前位置: 首页 > news >正文

【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.Vall2.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 与 1 → 取 l1 的 1;结果 [1]
  2. 比较 2 与 1 → 取 l2 的 1;结果 [1,1]
  3. 比较 2 与 3 → 取 2;结果 [1,1,2]
  4. 比较 4 与 3 → 取 3;结果 [1,1,2,3]
  5. 比较 4 与 4 → 取 l1 的 4;结果 [1,1,2,3,4]
  6. 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
http://www.dtcms.com/a/344769.html

相关文章:

  • 开发二手车小程序时,如何确保信息的真实性和可靠性?
  • Prometheus+Grafana监控redis
  • 【连接器专题】连接器接触界面的理解
  • Elasticsearch Rails 集成(elasticsearch-model / ActiveRecord)
  • 高速互联技术——NVLink
  • SpringBoot3集成Oauth2.1——8自定义认证模式(密码模式)
  • 第九届86358贾家庄短片周在山西汾阳贾家庄举办
  • 将博客网站完整迁移至本地虚拟机
  • 爬虫基础学习-授权认证,cookie认证,异常处理
  • 最短路径问题(图论)
  • 中国SM系列密码算法的入门教程
  • 网络实践——Socket编程UDP
  • Seaborn数据可视化实战:Seaborn颜色与样式定制教程
  • elasticsearch的使用
  • odoo-065 两个视图中的action类型的button互相引用,造成死循环
  • ubuntu使用fstab挂载USB设备(移动硬盘)
  • Claude Code接入Serena mcp
  • ESP32C5,使用espidf框架配置wifi扫描时报错,为什么会提示,ghz_5_channels的参数无效呢
  • 开发避坑指南(32):FastJSON异常JSONArray cannot be cast to JSONObject解决方案
  • 什么是数据分类分级?数据分类分级技术实现路径及产品推荐
  • ​Kubernetes 详解:云原生时代的容器编排与管理
  • 08.21总结
  • 【yocto】BitBake指令汇总解析
  • 基于springboot的农产品社区配送系统
  • 线性回归的学习
  • C++ unistd.h库文件介绍(文件与目录操作, 进程管理, 系统环境访问, 底层I/O操作, 系统休眠/执行控制)
  • golang 非error错误分类
  • 【如何生成专业级 API 接口文档:从规范到实战】
  • 指针实现数组的逆序存放并输出
  • IKE 与 ISAKMP 核心笔记