首页 > 编程语言 >算法题——合并两个已排序链表(不使用额外空间)

算法题——合并两个已排序链表(不使用额外空间)

时间:2024-10-14 16:47:12浏览次数:8  
标签:递归 合并 链表 算法 l2 l1 排序 节点

题目如下:

        输入两个递增的链表,单个链表的长度为n,合并这两个链表并使新链表中的节点仍然是递增排序的。

        数据范围: 0≤n≤10000≤n≤1000,−1000≤节点值≤1000−1000≤节点值≤1000
要求:空间复杂度 O(1)O(1),时间复杂度 O(n)O(n)

        如输入{1,3,5},{2,4,6}时,合并后的链表为{1,2,3,4,5,6},所以对应的输出为{1,2,3,4,5,6},转换过程如下图所示:

        

或输入{-1,2,4},{1,3,4}时,合并后的链表为{-1,1,2,3,4,4},所以对应的输出为{-1,1,2,3,4,4},转换过程如下图所示:


一、解决方案

(一)迭代法

        迭代法是一种通过逐步比较和连接节点来合并两个已排序链表的方法。首先,创建一个虚拟头节点,用于存储合并后的链表。这个虚拟头节点的作用是方便我们在迭代过程中始终有一个固定的起始点来连接节点。

        然后,使用两个指针分别指向两个待合并的链表的头节点。在迭代过程中,比较这两个指针所指向的节点的值,将较小值的节点连接到虚拟头节点后面。接着,移动指向较小值节点的指针到下一个节点,并更新用于连接节点的指针,使其始终指向合并后链表的最后一个节点。

        例如,假设有两个已排序链表:链表 A 为 2->4->6,链表 B 为 1->3->5。首先,比较链表 A 的头节点值 2 和链表 B 的头节点值 1,将值为 1 的节点连接到虚拟头节点后面,然后移动指向链表 B 的指针到下一个节点。接着比较链表 A 的头节点值 2 和链表 B 的新头节点值 3,将值为 2 的节点连接到合并后的链表中,再移动指向链表 A 的指针。依此类推,直到两个链表中的所有节点都被处理完毕。

        迭代法的优点是直观易懂,实现起来相对简单。时间复杂度为 O (n),其中 n 是两个链表的总长度,因为需要遍历两个链表的所有节点。空间复杂度为 O (1),因为只使用了常量级别的额外空间,即虚拟头节点和几个指针。

(二)递归法

        递归法是另一种实现合并两个已排序链表的方法。递归的基本思想是将问题分解为更小的子问题,直到子问题可以直接解决,然后再将子问题的解组合起来得到原问题的解。

        在合并两个已排序链表的问题中,递归的实现方式如下:首先,比较两个链表的头节点值。如果一个链表的头节点值较小,那么将这个节点作为合并后链表的头节点,并递归地调用函数来合并这个链表的下一个节点和另一个链表。如果一个链表为空,直接返回另一个链表。

        例如,对于链表 A 为 2->4->6 和链表 B 为 1->3->5,首先比较链表 A 的头节点值 2 和链表 B 的头节点值 1,由于 1 较小,所以将值为 1 的节点作为合并后链表的头节点,然后递归地合并链表 A 和链表 B 的下一个节点,即合并 2->4->6 和 3->5。

        递归法的优点是代码简洁,易于理解。时间复杂度也是 O (n),因为需要遍历两个链表的所有节点。然而,递归法可能会导致栈溢出的问题,特别是当链表很长时,因为递归调用会占用大量的栈空间。空间复杂度在理论上是 O (n),但由于实际实现中编译器的优化等因素,可能会小于 O (n)。不过,相比迭代法,递归法通常会占用更多的空间。

二、C语言实现

(一)迭代法实现

#include <stdio.h>
#include <stdlib.h>

// 链表节点结构
struct ListNode {
    int val;
    struct ListNode *next;
};

// 创建新节点
struct ListNode* createNode(int val) {
    // 为新节点分配内存
    struct ListNode* newNode = (struct ListNode*)malloc(sizeof(struct ListNode));
    // 初始化新节点的值
    newNode->val = val;
    // 新节点的下一个指针初始化为 NULL
    newNode->next = NULL;
    // 返回新创建的节点指针
    return newNode;
}

// 迭代法合并两个已排序链表
struct ListNode* mergeTwoListsIterative(struct ListNode* l1, struct ListNode* l2) {
    // 创建一个虚拟头节点,这个节点不存储实际数据,只是为了方便操作结果链表
    struct ListNode dummy;
    // tail 指针用于指向结果链表的尾节点
    struct ListNode* tail = &dummy;

    // 当两个输入链表都不为空时循环
    while (l1 && l2) {
        // 如果 l1 节点的值小于 l2 节点的值
        if (l1->val < l2->val) {
            // 将 l1 节点连接到结果链表的尾部
            tail->next = l1;
            // 移动 l1 指针指向下一个节点
            l1 = l1->next;
        } else {
            // 将 l2 节点连接到结果链表的尾部
            tail->next = l2;
            // 移动 l2 指针指向下一个节点
            l2 = l2->next;
        }
        // 更新 tail 指针指向结果链表的新尾节点
        tail = tail->next;
    }

    // 如果 l1 不为空,将 l1 连接到结果链表尾部;如果 l1 为空,l2 必然不为空,将 l2 连接到结果链表尾部
    tail->next = l1? l1 : l2;

    // 返回虚拟头节点的下一个节点,即合并后的链表的头节点
    return dummy.next;
}

(二)递归法实现

// 递归法合并两个已排序链表
struct ListNode* mergeTwoListsRecursive(struct ListNode* l1, struct ListNode* l2) {
    // 如果 l1 为空,直接返回 l2
    if (!l1) return l2;
    // 如果 l2 为空,直接返回 l1
    if (!l2) return l1;

    // 如果 l1 的值小于 l2 的值
    if (l1->val < l2->val) {
        // 将 l1 的下一个节点设置为递归调用 mergeTwoListsRecursive 处理 l1 的下一个节点和 l2 的结果
        l1->next = mergeTwoListsRecursive(l1->next, l2);
        // 返回 l1,因为它是当前合并后的链表的头部
        return l1;
    } else {
        // 将 l2 的下一个节点设置为递归调用 mergeTwoListsRecursive 处理 l1 和 l2 的下一个节点的结果
        l2->next = mergeTwoListsRecursive(l1, l2->next);
        // 返回 l2,因为它是当前合并后的链表的头部
        return l2;
    }
}

        以上两段代码实现了两种方法来合并两个已排序的链表,并且不使用额外的空间。

        迭代法通过比较两个链表的当前节点值,将较小值的节点连接到结果链表中,直到其中一个链表遍历完,然后将剩余的链表连接到结果链表的末尾。

        递归法通过比较两个链表的当前节点值,将较小值的节点连接到结果链表中,然后递归地处理剩余的链表部分。

三、应用场景

合并两个已排序链表的算法在实际中有广泛的应用场景,充分体现了其强大的实用性。

(一)合并有序用户列表

        在许多软件系统中,用户信息通常以链表的形式存储。例如,一个社交平台可能有两个已按照用户注册时间排序的用户链表,一个是新注册用户列表,另一个是老用户列表。当需要对所有用户进行统一管理或进行特定的用户数据分析时,就可以使用合并两个已排序链表的算法来将这两个用户列表合并为一个有序的用户列表。这样可以方便地进行用户信息的查询、排序和处理,提高系统的性能和用户体验。

(二)合并有序日志文件

        在软件开发和系统运维中,日志文件是非常重要的信息来源。假设一个系统有两个分别记录不同类型事件的日志文件,并且这两个日志文件都是按照时间顺序排序的。为了进行综合的日志分析,就可以使用合并两个已排序链表的算法来将这两个日志文件合并为一个有序的日志文件。这样可以更方便地追踪系统的运行状态,发现问题并进行故障排除。

        除了上述场景,该算法还可以应用于数据库管理系统中的索引合并、文件系统中的文件合并等场景。在数据库管理系统中,合并有序的索引链表可以优化查询性能,提高数据检索的速度。在文件系统中,将多个有序的小文件合并为一个大的有序文件可以减少文件碎片,提高文件读写的效率。

        总之,合并两个已排序链表的算法在各种实际应用场景中都具有重要的价值,能够帮助我们更高效地处理数据,提高系统的性能和稳定性。

四、总结展望

        合并两个已排序链表的算法具有诸多优势。首先,它简单高效,无论是迭代法还是递归法,都能在不使用额外空间的情况下实现链表的合并,时间复杂度为 ,其中 是两个链表的总长度。这使得在内存资源有限的环境下,如嵌入式系统开发中,能够有效地节省内存开销,提高程序的性能和效率。

        其次,该算法具有广泛的应用前景。在实际的软件开发中,无论是合并有序用户列表、合并有序日志文件,还是在数据库管理系统中的索引合并、文件系统中的文件合并等场景,都能发挥重要作用。它可以方便地进行数据的整合和排序,提高系统的性能和稳定性。

        然而,该算法也可能面临一些挑战。例如,递归法可能会导致栈溢出的问题,特别是当链表很长时。在实际应用中,需要根据具体情况选择合适的方法。如果对内存使用有严格要求,迭代法可能是更好的选择;如果追求代码简洁性,递归法可能更合适。

        总之,合并两个已排序链表的算法虽然存在一些挑战,但凭借其简单高效和广泛的应用前景,在计算机科学领域中具有重要的地位。随着技术的不断发展,相信该算法将在更多的领域得到应用和优化。


//该题来源如下,BM4题https://www.nowcoder.com/practice/d8b6b4358f774294a89de2a6ac4d9337?tpId=295&tags=&title=&difficulty=0&judgeStatus=0&rp=0&sourceUrl=%2Fexam%2Fintelligent%3FquestionJobId%3D10

标签:递归,合并,链表,算法,l2,l1,排序,节点
From: https://blog.csdn.net/m0_71974552/article/details/142756417

相关文章

  • 强化学习性能指标之一:以训练的episodes数和训练所需样本数作为评价算法性能的指标
    在强化学习领域,一般都是限定训练的episodes数和训练所需样本数的,也就是说在进行算法性能对比的时候各个算法都是在相同的训练的episodes数和训练所需样本数的,但是如果我们在算法得分数保持相同的情况下是不是可以将各个算法所用的不同的训练的episodes数和训练所需样本数作为性能......
  • 机器学习领域如何判定算法是否收敛(算法是否稳定)
    最近在看ML的资料的时候看到有关算法收敛的讨论,然后有些资料并没有说明如何判定算法是否收敛,甚至有些资料中会将算法收敛和算法稳定性放在同等位置上来进行讨论,为此本文就这个问题进行一些讨论。几年前参加实验室大师兄小利师兄的博士论文答辩的时候有答辩评委提出了这么一个问......
  • 第2课-枚举、排序、贪心
    前言如果认为自己代码没问题,换行问题,边界问题等都处理了还是不行,可以试试交C++(GCC9)该类型,因为部分题目是UVA上的老题,可能不支持新版本的C++。如果提交UNKNOWNERROR,应该是没绑定UVA账号,洛谷右上角个人设置里去填写注册一下即可。除法Division思路这个题一定要注意输......
  • 机器学习_线性回归_岭回归算法预测波士顿房价代码实现(机器学习全流程)(附带数据集hou
    #1.导入外部数据集HousingDataimportpandasaspdboston_data=pd.read_csv(r"C:\Users\鹰\Desktop\ML_Set\HousingData.csv")#数据基本描述print(boston_data.head())print(boston_data.describe())print(boston_data.shape)#2.数据基本处理-缺失值处理,特征值和......
  • Pytho逻辑回归算法:面向对象的实现与案例详解
    这里写目录标题Python逻辑回归算法:面向对象的实现与案例详解引言一、逻辑回归算法简介1.1损失函数1.2梯度下降二、面向对象的逻辑回归实现2.1类的设计2.2Python代码实现2.3代码详解三、逻辑回归案例分析3.1案例一:简单二分类问题问题描述数据代码实现输出结果3问......
  • Python决策树算法:面向对象的实现与案例详解
    目录Python决策树算法:面向对象的实现与案例详解引言一、决策树算法概述1.1决策树的基本思想1.2分类与回归树1.3决策树的构建过程1.4决策树的优缺点优点缺点二、面向对象的决策树实现2.1类的设计2.2Python代码实现2.3代码详解三、案例分析3.1案例一:鸢尾花分类......
  • 问:JVM的垃圾收集算法你知道哪些,有什么区别?
    GC(垃圾回收器)的概念GC,即垃圾回收(GarbageCollection),是计算机程序中一种自动管理内存的机制。其目的是自动回收不再被使用的对象所占用的内存空间,从而避免内存泄漏和内存溢出,确保程序能够稳定、高效地运行。GC算法的主要特点GC算法有多种,每种算法都有其独特的工作原理和适......
  • (C语言)算法数据结构
    王道数据结构以及本人上课的笔记             ......
  • Python 中快速上手机器学习的基础算法
    机器学习作为一种让计算机从数据中自动学习的技术,在近年来得到了迅猛发展。本文将介绍几种基础的机器学习算法,并通过Python代码示例展示它们的应用。1.什么是机器学习机器学习是一种让计算机学会从数据中自动“学习”并做出预测或决策的技术。不需要显式地编程告诉计算机......
  • 101基于java ssm springboot协同过滤算法高考志愿填报系统(源码+文档+运行视频+讲解视
    项目技术:Springboot+Maven+Vue等等组成,B/S模式+Maven管理等等。环境需要1.运行环境:最好是javajdk1.8,我们在这个平台上运行的。其他版本理论上也可以。2.IDE环境:IDEA,Eclipse,Myeclipse都可以。推荐IDEA;3.tomcat环境:Tomcat7.x,8.x,9.x版本均可4.硬件环境:windows......