【数据结构与算法】21.合并两个有序链表(LeetCode)

【数据结构与算法】21.合并两个有序链表(LeetCode)

文章目录

合并两个有序链表:高效算法解析与实现

链表合并是数据结构中的经典问题,在算法面试和实际开发中经常出现。本文将深入解析如何高效合并两个有序链表,并展示C语言的实现方案。

问题描述

在这里插入图片描述

给定两个升序排列的链表list1list2,要求将它们合并为一个新的升序链表并返回。新链表应该通过拼接给定链表的节点来完成。

示例:

输入:list1 = [1,2,4], list2 = [1,3,4] 输出:[1,1,2,3,4,4] 

核心思路:双指针尾插法

基本思想:

  1. 创建一个新的空链表作为结果
  2. 使用两个指针分别遍历两个输入链表
  3. 比较当前节点的值,将较小值的节点插入新链表的尾部
  4. 当任一链表遍历完后,将剩余链表直接接到新链表尾部

时间复杂度: O(n+m),其中n和m分别是两个链表的长度
空间复杂度: O(1),不需要额外空间,直接在原节点上操作

完整代码实现

/** * Definition for singly-linked list. * struct ListNode { * int val; * struct ListNode *next; * }; */structListNode*mergeTwoLists(structListNode* list1,structListNode* list2){structListNode* l1 = list1;structListNode* l2 = list2;structListNode* NewHead =NULL;structListNode* NewTail =NULL;// 处理空链表的情况if(l1 ==NULL)return l2;if(l2 ==NULL)return l1;// 遍历两个链表while(l1 && l2){if(l1->val < l2->val){// 处理新链表的头节点if(NewHead ==NULL){ NewHead = NewTail = l1;}else{ NewTail->next = l1; NewTail = NewTail->next;} l1 = l1->next;}else{// 处理新链表的头节点if(NewHead ==NULL){ NewHead = NewTail = l2;}else{ NewTail->next = l2; NewTail = NewTail->next;} l2 = l2->next;}}// 连接剩余链表if(l1 ==NULL){ NewTail->next = l2;}else{ NewTail->next = l1;}return NewHead;}

关键点解析

1. 边界条件处理

if (l1 == NULL) return l2; if (l2 == NULL) return l1; 

这两行代码处理了空链表的边界情况,提高了代码的健壮性。

2. 头节点初始化

if (NewHead == NULL) { NewHead = NewTail = l1; // 或l2 } 

这里使用NewHeadNewTail两个指针分别记录新链表的头和尾:

  • NewHead:始终指向新链表的头节点
  • NewTail:始终指向新链表的尾节点,便于尾插操作

3. 节点比较与插入

if (l1->val < l2->val) { // 插入l1节点 } else { // 插入l2节点 } 

通过比较两个链表当前节点的值,决定哪个节点应该优先插入新链表,确保结果保持升序。

4. 剩余节点处理

if (l1 == NULL) { NewTail->next = l2; } else { NewTail->next = l1; } 

当任一链表遍历完后,直接将另一链表的剩余部分连接到新链表尾部,避免了不必要的循环。

常见错误与修正

在原始代码中,存在一个典型错误:

// 错误写法(赋值而非比较)if(NewHead=NULL)// 正确写法(比较操作)if(NewHead ==NULL)

这个错误会导致:

  1. NewHead设置为NULL
  2. 条件判断结果永远为假(NULL相当于0)
  3. 永远不会进入头节点初始化分支

开发建议: 在条件判断中使用常量在前的方式避免此类错误:

if(NULL== NewHead)// 如果误写为赋值,编译器会报错

优化方案:哨兵节点

使用哨兵节点可以进一步简化代码逻辑:

structListNode*mergeTwoLists(structListNode* list1,structListNode* list2){structListNode dummy;// 哨兵节点structListNode* tail =&dummy; dummy.next =NULL;while(list1 && list2){if(list1->val < list2->val){ tail->next = list1; list1 = list1->next;}else{ tail->next = list2; list2 = list2->next;} tail = tail->next;}// 连接剩余部分 tail->next = list1 ? list1 : list2;return dummy.next;// 哨兵的下一个节点即真实头节点}

哨兵节点方案的优点:

  1. 消除头节点特殊判断
  2. 减少代码分支(从4个分支减少到2个)
  3. 提高代码可读性和健壮性
  4. 避免头节点指针的初始化问题

算法应用场景

  1. 归并排序:链表归并排序的核心操作
  2. 多路归并:多个有序流的合并(如K个有序链表)
  3. 数据库系统:合并多个有序结果集
  4. 消息队列:合并多个有序消息流

总结

合并两个有序链表是链表操作中的基础但重要的算法:

  • 核心思想:双指针遍历+尾插法
  • 关键技巧:头尾指针维护新链表
  • 常见陷阱:头节点初始化、指针操作顺序
  • 优化方向:哨兵节点简化边界处理

多路归并*:多个有序流的合并(如K个有序链表)
3. 数据库系统:合并多个有序结果集
4. 消息队列:合并多个有序消息流

总结

合并两个有序链表是链表操作中的基础但重要的算法:

  • 核心思想:双指针遍历+尾插法
  • 关键技巧:头尾指针维护新链表
  • 常见陷阱:头节点初始化、指针操作顺序
  • 优化方向:哨兵节点简化边界处理

Read more

【算法详解】理解KMP,真的那么难吗?—— 一篇讲透它的核心思想

【算法详解】理解KMP,真的那么难吗?—— 一篇讲透它的核心思想

🫧 励志不掉头发的内向程序员:个人主页  ✨️ 个人专栏: 《C++语言》《Linux学习》 🌅偶尔悲伤,偶尔被幸福所完善 👓️博主简介: 文章目录 * 前言 * 一、相关概念 * 二、前缀函数 * 三、计算前缀函数 * 四、用前缀函数解决字符串匹配 * 五、kmp 算法模板 * 六、next 数组版本 * 七、周期和循环节 * 总结 前言 本文用尽量详细的语言来讲解说明 kmp 算法内容,学习之前需要知道一点点动态规划的基础,如果不知道最好去了解了解。我们一起来看看算法吧。 一、相关概念 在学习 kmp 算法之前,我们得先提前了解最基本的 “ 动态规划 ” 的知识,否则可能学习的时候会有一些困难,因为它的原理类似于动态规划。 字符串: * 用字符构成的的序列就是字符串。 这个概念很简单,但是我们这里有个小技巧:就和动态规划那样,

By Ne0inhk

【学习记录】使用 John the Ripper 和 Hashcat破解 RAR、ZIP 与 7z 文件密码(Windows教程)

文章目录 * 📌 引言 * 📦 支持类型 * 🛠️ 所需工具下载地址(Windows 官方编译版) * 额外工具 * 🔐 一、破解RAR 压缩文件密码 * 步骤 1:获取RAR 文件的加密哈希值 * 步骤 2:将正确的哈希值复制到 `hash.txt` 文件 * 步骤 3:在 Hashcat 官方 wiki 查找 `-m` 哈希模式 ID * 步骤 4: 使用 Hashcat 破解哈希值 * 📁 二、破解ZIP 压缩文件密码 * 步骤 1:提取 ZIP 文件的加密哈希值 * 步骤 2:将正确的哈希值复制到 `hash.txt` 文件 * 步骤

By Ne0inhk
Flutter 三方库 collection — 鸿蒙应用全方位集合操作与算法增强利器,实现鸿蒙深度适配下的高效容器过滤与优先级队列实战全解析(适配鸿蒙 HarmonyOS Next ohos)

Flutter 三方库 collection — 鸿蒙应用全方位集合操作与算法增强利器,实现鸿蒙深度适配下的高效容器过滤与优先级队列实战全解析(适配鸿蒙 HarmonyOS Next ohos)

欢迎加入开源鸿蒙跨平台社区:https://openharmonycrossplatform.ZEEKLOG.net。 Flutter 三方库 collection — 鸿蒙应用全方位集合操作与算法增强利器,实现鸿蒙深度适配下的高效容器过滤与优先级队列实战全解析 前言 在鸿蒙(OpenHarmony)应用开发中,数据结构的选择往往决定了逻辑的成败。当标准的 List、Set、Map 无法满足更高级的需求(例如:需要一个自动按优先级排序的任务队列,或者需要判断两个深度嵌套的 Map 是否完全一致)时,开发者就需要引入更强大的集合支持。 collection 是 Dart 官方维护的最核心基础库之一。它不仅补充了大量缺失的容器类型(如 PriorityQueue、Heap),还为原生集合提供了极其丰富的扩展工具类(如 ListEquality、CanonicalizedMap)。在 Flutter for OpenHarmony 的底层架构实践中,它是处理复杂业务逻辑、优化检索效率的必备“基石”。 一、原理解析 / 概念介绍

By Ne0inhk
【数据结构】常见时间复杂度以及空间复杂度

【数据结构】常见时间复杂度以及空间复杂度

时间复杂度与空间复杂度 * 一、复杂度的概念 * 二、时间复杂度 * 1、大O的渐进表示法 * 2、函数clock计算运算时间 * 3、常见复杂度对比 * 3.1常数项复杂度 * 3.2线性时间复杂度 * 案例1 * 案例2 * 3.3平方阶复杂度 * 3.4对数复杂度 * 3.5递归函数 * 单递归 * 双递归 * 三、空间复杂度 * 冒泡排序O(1) * 三个反置O(N) 一、复杂度的概念 * 一个算法的好坏,主要是对比两者的时间和空间两个维度,也就是时间和空间复杂度。 * 时间复杂度主要衡量一个算法运行的快慢,空间复杂度主要衡量一个算法运行需要的额外空间 二、时间复杂度 * 算法的时间复杂度是一个函数式T(N),算法中的基本操作的执行次数,为算法的时间复杂度。 * 注:编译器的不同,编译所需要的时间也不同。越新的编译器,编译的时间往往比旧的编译器快 * 当一个算法函数式为T(

By Ne0inhk