【LeetCode_21】合并两个有序链表

刷爆LeetCode系列

LeetCode第21题:

github地址

有梦想的电信狗

前言

  • 本文用C++解答LeetCode21

题目描述

题目链接https://leetcode-cn.com/problems/merge-two-sorted-lists/description/


题目与思路分析

目标分析

  1. 两个升序链表 合并为一个升序链表
  2. 返回新链表的头指针
  3. 新链表中的结点由已有两链表中的节点组成
  4. 提高要求 :时间复杂度为O(n),空间复杂度为O(1)

思路一:尾插

思路 :创建一个新的空链表newHead同时逐个遍历 两个链表的结点,将val较小的节点尾插到新链表中

操作

  • 注意对空链表的处理,两个链表可能都为空,也可能任意一个为空

  • 遍历 :循环继续的条件为curNode1 && curNode2,只要有一个链表结束,结束即可,将未遍历完的链表直接整体尾插到新链表中

    • curNode1curNode2:分别用于遍历链表一和链表二
    • tailNode:尾结点,方便尾插时找尾
  • curNode->val <= curNode2->val时,说明:curNode1需要被尾插到新链表

    • 第一次尾插时(if(newHead == nullptr)需要特殊处理
      • tailNode = newHead = curNode;
      • curNode = curNode->next;
    • 其余结点的尾插,常规化处理
      • tailNode->next = curNode;
      • tailNode = tailNode->next;
    • 插入完后:curNode = curNode->next;
  • curNode->val > curNode2->val时和上面是一样的逻辑

  • 循环结束后,检查哪个链表未遍历完全直接整体尾插到新链表中

    cpp 复制代码
      // 循环结束后,可能链表还有剩余元素
      if(curNode1)
          tailNode->next = curNode1;
      if(curNode2)
          tailNode->next = curNode2;
  • 最终返回return newHead;

思路二

思路 :使用带哨兵位的头结点优化尾插 ,在带哨兵位的链表中进行尾插时,无需特殊处理第一次尾插时的情况

操作

  • 注意对空链表的处理,两个链表可能都为空,也可能任意一个为空

  • 遍历 :循环继续的条件为curNode1 && curNode2,只要有一个链表结束,结束即可,将未遍历完的链表直接整体尾插到新链表中

    • curNode1curNode2:分别用于遍历链表一和链表二
    • tailNode:尾结点,方便尾插时找尾
  • curNode->val <= curNode2->val时,说明:curNode1需要被尾插到新链表

    • 有了哨兵位的头结点,结点的尾插,都能常规化处理
      • tailNode->next = curNode;
      • tailNode = tailNode->next;
    • 插入完后:curNode = curNode->next;
  • curNode->val > curNode2->val时和上面是一样的逻辑

  • 循环结束后,检查哪个链表未遍历完全直接整体尾插到新链表中

  • 保存新链表的头结点ListNode* newHead = guardNode->next,为guardNodenext结点

    cpp 复制代码
      guardNode->next = nullptr;
      delete guardNode
    • 释放guardNode防止内存泄露
  • 最终返回新的头结点return newHead;

代码实现

思路一

cpp 复制代码
class Solution {
public:
    ListNode* mergeTwoLists(ListNode* list1, ListNode* list2) {
        // 空链表判断
        if(list1 == nullptr && list2 == nullptr)
             return nullptr;
        if(list1 == nullptr || list2 == nullptr){
             if(list1)
                 return list1;
             if(list2)
                 return list2;
        }
        // 以下 两个链表都不为空
        ListNode* curNode1 = list1;
        ListNode* curNode2 = list2;
        ListNode* newHead = nullptr, *tailNode = nullptr;
        while(curNode1 && curNode2){
            // 更小的元素尾插到新结点
            if(curNode1->val <= curNode2->val){
                // 第一个节点直接尾插
                if(newHead == nullptr){
                    newHead = tailNode = curNode1;
                }
                // 其他节点直接 尾插
                else{
                    tailNode->next = curNode1;
                    tailNode = tailNode->next;
                }
                curNode1 = curNode1->next;
            }
            else{
                // 第一个节点直接尾插
                if(newHead == nullptr){
                    newHead = curNode2;
                    tailNode = curNode2;
                }
                // 其他节点直接 尾插
                else{
                    tailNode->next = curNode2;
                    tailNode = tailNode->next;
                }
                curNode2 = curNode2->next;
            }
        }
        // 循环结束后,可能链表还有剩余元素
        if(curNode1)
            tailNode->next = curNode1;
        if(curNode2)
            tailNode->next = curNode2;
        
        return newHead;
    }
};

思路二

cpp 复制代码
// 使用带哨兵位的头结点 优化算法
class Solution {
public:
    ListNode* mergeTwoLists(ListNode* list1, ListNode* list2) {
        if(list1 == nullptr)
            return list2;
        if(list2 == nullptr)
            return list1;

        ListNode* curNode1 = list1, *curNode2 = list2;	// 遍历链表,用于迭代
         // 尾插需要找尾,提前保存,避免重复找
        ListNode* guardNode = new ListNode();
        ListNode* tailNode = guardNode;
       
        while(curNode1 && curNode2){
            // 将小的那个结点,尾插到 guardNode 后面
            if(curNode1->val <= curNode2->val){
                tailNode->next = curNode1;
                tailNode = tailNode->next;
                curNode1 = curNode1->next;
            }
            else{
                tailNode->next = curNode2;
                tailNode = tailNode->next;
                curNode2 = curNode2->next;
            }
        }
        // 有一个链表结束后,将另一个链表再链接上
        if(curNode1)
            tailNode->next = curNode1;
        if(curNode2)
            tailNode->next = curNode2;
        // 保存新的头结点,并释放内存,防止内存泄露
        ListNode* newHead = guardNode->next;
        guardNode->next = nullptr;
        delete guardNode;
        // 返回新结点
        return newHead;
    }
};

算法代码优化

优化思路一

  • 优化了空链表返回的逻辑
  • 其中一个链表为空,就返回另一个链表
cpp 复制代码
class Solution {
public:
    ListNode* mergeTwoLists(ListNode* list1, ListNode* list2) {
        if(list1 == nullptr)
            return list2;
        if(list2 == nullptr)
            return list1;
        // 以下 两个链表都不为空
        ListNode* curNode1 = list1;
        ListNode* curNode2 = list2;
        ListNode* newHead = nullptr, *tailNode = nullptr;
        while(curNode1 && curNode2){
            // 更小的元素尾插到新结点
            if(curNode1->val <= curNode2->val){
                // 第一个节点直接尾插
                if(newHead == nullptr){
                    newHead = tailNode = curNode1;
                }
                // 其他节点直接 尾插
                else{
                    tailNode->next = curNode1;
                    tailNode = tailNode->next;
                }
                curNode1 = curNode1->next;
            }
            else{
                // 第一个节点直接尾插
                if(newHead == nullptr){
                    newHead = curNode2;
                    tailNode = curNode2;
                }
                // 其他节点直接 尾插
                else{
                    tailNode->next = curNode2;
                    tailNode = tailNode->next;
                }
                curNode2 = curNode2->next;
            }
        }
        // 循环结束后,可能链表还有剩余元素
        if(curNode1)
            tailNode->next = curNode1;
        if(curNode2)
            tailNode->next = curNode2;
        return newHead;
    }
};

优化思路二

  • 优化了初始链表判空的处理
  • 这里无需对空链表进行处理 :通过分析得知
    • list1list2nullptr时,不会进入while循环。由于guardNode非空, ListNode* newHead = guardNode->next,因此保存的newHead时一定合法。
    • list1list2全为nullptr时,newHead即为nullptr
    • list1list2其中一个为nullptr时,newHead即另一个链表的头结点
cpp 复制代码
// 使用带哨兵位的头结点 优化算法
class Solution {
public:
    ListNode* mergeTwoLists(ListNode* list1, ListNode* list2) {
        // 遍历链表的结点
        ListNode* curNode1 = list1, *curNode2 = list2;
         // 尾插需要找尾,提前保存,避免重复找
        ListNode* guardNode = new ListNode();
        ListNode* tailNode = guardNode;
       
        while(curNode1 && curNode2){
            // 将小的那个结点,尾插到 guardNode 后面
            if(curNode1->val <= curNode2->val){
                tailNode->next = curNode1;
                tailNode = tailNode->next;
                curNode1 = curNode1->next;
            }
            else{
                tailNode->next = curNode2;
                tailNode = tailNode->next;
                curNode2 = curNode2->next;
            }
        }
        // 有一个链表结束后,将另一个链表再链接上
        if(curNode1)
            tailNode->next = curNode1;
        if(curNode2)
            tailNode->next = curNode2;
        // 保存新的头结点,并释放内存,防止内存泄露
        ListNode* newHead = guardNode->next;
        guardNode->next = nullptr;
        delete guardNode;
        // 返回新结点
        return newHead;
    }
};

以上就是本文的所有内容了,如果觉得文章对你有帮助,欢迎 点赞⭐收藏 支持!如有疑问或建议,请在评论区留言交流,我们一起进步

分享到此结束啦
一键三连,好运连连!

你的每一次互动,都是对作者最大的鼓励!


征程尚未结束,让我们在广阔的世界里继续前行! 🚀

相关推荐
dongf20193 分钟前
R语言KNN算法
算法·数据分析·r语言
原来是猿11 分钟前
深入理解 C++ unordered_map 与 unordered_set
开发语言·c++
满天星830357712 分钟前
【Qt】信号和槽 (一)(概述和基本使用)
开发语言·c++·qt
努力的章鱼bro15 分钟前
CUDA编程模型
c++·cuda
l1t21 分钟前
DeepSeek总结的 waddler,一个 Go 语言编写的从 YAML 文件运行的 ETL 管道
开发语言·golang·etl
小O的算法实验室24 分钟前
2025年IEEE TASE,基于双层耦合平均场博弈的大规模智能体集成任务分配与轨迹规划
人工智能·算法·机器学习
8Qi830 分钟前
LeetCode 337:打家劫舍 III(House Robber III)—— 题解 ✅
算法·leetcode·二叉树·动态规划
地平线开发者30 分钟前
从 INT64 Div 算子约束到 Cast 修复全流程
算法
2601_9611940230 分钟前
教资科三美术考什么|初中高中美术题型考点和模板资料
leetcode·elasticsearch·职场和发展·蓝桥杯·pat考试·lucene
AI科技星33 分钟前
基于奇合数边界的离散解析数论与双螺旋宇宙本体大统一体系论文全部数学公式汇总表
人工智能·算法·机器学习·架构·学习方法