【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;
    }
};

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

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

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


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

相关推荐
码匠许师傅5 小时前
【C++ 面试真题】C++ 的 const 和 constexpr 有什么区别?
c++·面试
坚持编程的菜鸟7 小时前
模拟实现memmove
c语言·算法·模拟实现memmove
BUG研究员_7 小时前
Runnable与LCEL
开发语言·人工智能·python
wabs6668 小时前
关于图论【最短路径之Dijkstra算法(堆优化版)|卡码网47.参加科学大会的思考】
数据结构·算法·图论·优先级队列·邻接表·小顶堆·卡码网
果果燕8 小时前
实习笔记(一):NFS、Samba、VNC、Git、CMake、systemd、内核、交叉编译
linux·arm开发·c++·笔记·git
坚持编程的菜鸟8 小时前
编写判断大小端程序
c语言·算法·判断大小端
papaofdoudou8 小时前
判断排列逆序奇偶性的乘积判别法(范德蒙德符号法)
人工智能·算法
牛艺翔9 小时前
C++基础
开发语言·c++
键盘会跳舞9 小时前
C++ :容器适配器stack源码级拆解
开发语言·c++··stack·先进后出
linux-hzh9 小时前
百日算法修炼 · Day 03
java·算法