【数据结构与算法】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

数据结构—顺序表

数据结构—顺序表

数据结构—顺序表 * 线性表 * 顺序表 * 概念与结构 * 顺序表和数组区别 * 分类 * 静态顺序表 * 动态顺序表 * 动态顺序表模拟实现 * 定义动态顺序表结构 * 顺序表初始化 * 顺序表销毁 * 顺序表打印 * 顺序表动态扩容 * 尾插 * 头插 * 尾删 * 头删 * 查找 * 指定位置之前插入 * 删除pos位置的数据 * 竞赛中的静态顺序表 * 静态申请数组 * 封装静态顺序表 * 动态顺序表--vector * 创建vector * size / empty * begin / end * push_back / pop_back * front / back * resize * clear * insert / erase * 仓库—代码总结 线性表 线性表(linear list)是

By Ne0inhk
【数据结构指南】高频二叉树节点问题

【数据结构指南】高频二叉树节点问题

前言:               在熟练掌握二叉树四种基本遍历方法的基础上,本文将深入探讨以下进阶问题:节点总数统计、叶子节点计算、第k层节点数量确定、节点的查找以及树高测量。         这些内容将帮助读者深化对二叉树结构的理解与应用能力,以及深入理解递归分治思想。            一、前置说明:          本文所描述的二叉树都是链式二叉树,其定义方式如下所示:          typedef char BTDataType; typedef struct BinaryTree { BTDataType data; struct BinaryTree* left; struct BinaryTree* right; }BTNode;          二、二叉树的创建及销毁          通过前序遍历的数组"ABD##E#H##CF##G##"构建二叉树,其中'#'表示该节点为NULL,二叉树如下图所示:                   前序遍历的思想为: 先访问根节点  ->  再访问左子树 -&

By Ne0inhk
libmd 实现详解:仓颉语言中的哈希算法库开发实践

libmd 实现详解:仓颉语言中的哈希算法库开发实践

libmd 实现详解:仓颉语言中的哈希算法库开发实践 前言 密码学哈希函数是现代信息安全的基石,广泛应用于数据完整性验证、数字签名、用户认证和数据安全存储等领域。在仓颉语言生态中,libmd库提供了完整的密码哈希算法实现,支持多种主流哈希算法,包括经典的MD2、MD4、MD5,以及SHA系列(SHA-1、SHA-224、SHA-256、SHA-384、SHA-512、SHA-512/256)和RIPEMD-160等算法。同时,该库还提供了HMAC功能,支持消息认证码的生成,为数据提供了额外的安全保障。 本文将从库的设计思路、核心实现、技术挑战、性能优化等多个维度,深入解析libmd库的开发过程,为仓颉语言开发者提供库开发的实践参考。 一、库概述 1.1 项目背景 在软件开发的众多领域,数据完整性验证和安全性保障是至关重要的需求。哈希算法因其单向性、抗碰撞性和雪崩效应等特性,成为解决这些问题的理想工具。从文件校验到用户认证,从区块链技术到数字签名,哈希算法的应用无处不在。 libmd库旨在为仓颉语言提供一套完整、高效、易用的哈希算法解决方案,支持多种主流哈希算法,

By Ne0inhk
【优选算法必刷100题】第011~012题(同向双指针:滑动窗口算法):最大连续1的个数 III、将 x 减到 0 的最小操作数

【优选算法必刷100题】第011~012题(同向双指针:滑动窗口算法):最大连续1的个数 III、将 x 减到 0 的最小操作数

🔥艾莉丝努力练剑:个人主页 ❄专栏传送门:《C语言》、《数据结构与算法》、C/C++干货分享&学习过程记录、Linux操作系统编程详解、笔试/面试常见算法:从基础到进阶 ⭐️为天地立心,为生民立命,为往圣继绝学,为万世开太平 🎬艾莉丝的简介: 🎬艾莉丝的算法专栏简介: 目录 011  最大连续1的个数 III 1.1  题目详解 1.2  算法原理以及代码实现 1.2.1  解法思路:滑动窗口 1.2.2  算法流程 1.2.3  代码实现 1.3  博主手记 012  将 x 减到

By Ne0inhk