剑指Offer - 面试题25:合并俩个排序的链表

简介: 剑指Offer - 面试题25:合并俩个排序的链表

题目

输入俩个递增排序的链表,合并这俩个链表并使新链表中的节点仍然是递增序列。例如下图链表1和链表2,合并后的升序链表为链表3,链表节点定义如下:


typedef int TElemType;//链表节点值的数据类型
struct ListNode
{
    TElemType m_nValue;
    ListNode* m_pNext;
};


分析

我们可以构造出来一个链表。表头不存储数据,定义俩指针p1和p2分别指向链表1和链表2表头。再创建一个记录新的链表尾部的指针previously。然后对比俩指针所指节点的值,谁小取谁的值,然后对应的指针后移。将新的节点连接到previously指向的节点后面,然后更新previously。直到俩个链表有一个为nullptr或者俩个均为nullptr。

当有一个链表为空退出循环时,直接将previously的下一个节点连接到另一个链表指针的节点。若均为nullptr时,不进行任何操作。(但是如果题目要求不允许修改原链表,你就只能继续遍历链表,然后创建新节点,连接。)


下面代码是不允许修改的原链表代码,且未优化


C++

#include <iostream>
using namespace std;
typedef int TElemType;//链表节点值的数据类型
struct ListNode
{
    TElemType m_nValue;
    ListNode* m_pNext;
};
ListNode* CreateNode(TElemType val)//创建节点
{
    ListNode* Node = new ListNode();
    Node->m_nValue = val;
    Node->m_pNext = nullptr;
    return Node;
}
void connect(ListNode* L1, ListNode* L2)//链接
{
    L1->m_pNext = L2;
}
void PrintList(ListNode* head)
{
    while (head != nullptr)
    {
        cout << head->m_nValue;
        if (head->m_pNext != nullptr)
        {
            cout << "->";
        }
        else
        {
            cout << endl;
        }
        head = head->m_pNext;
    }
}
ListNode* Merge(ListNode* pHead1, ListNode* pHead2)//合并俩链表
{
    if (nullptr == pHead1 && nullptr == pHead2)
    {
       return nullptr;
    }
    if (nullptr == pHead1)
    {
        return pHead2;
    }
    if (nullptr == pHead2)
    {
        return pHead1;
    }
    ListNode* p1 = pHead1;
    ListNode* p2 = pHead2;
    ListNode* NewNode = nullptr;//新创建的节点指针
    ListNode* head = new ListNode();//新链表的表头指针
    head->m_nValue = 0;
    head->m_pNext = nullptr;
    ListNode* previously = head;//表尾指针
    while (p1 != nullptr && p2 != nullptr)
    {
        int min = 0;//用于保存小值的节点
        //找最小值
        if (p1->m_nValue < p2->m_nValue)
        {
            min = p1->m_nValue;
            p1 = p1->m_pNext;
        }
        else
        {
            min = p2->m_nValue;
            p2 = p2->m_pNext;
        }
        //创建新节点
        NewNode = new ListNode();
        NewNode->m_nValue = min;
        NewNode->m_pNext = nullptr;
        //连接
        previously->m_pNext = NewNode;
        //更新链表尾指针
        previously = NewNode;
    }
    /*不可修改原链表的代码*/
    while (p1 != nullptr)
    {
        int min = p1->m_nValue;
        p1 = p1->m_pNext;
        //创建新节点
        NewNode = new ListNode();
        NewNode->m_nValue = min;
        NewNode->m_pNext = nullptr;
        //连接
        previously->m_pNext = NewNode;
        //更新链表尾指针
        previously = NewNode;
    }
    /*不可修改原链表的代码*/
    while (p2 != nullptr)
    {
        int min = p2->m_nValue;
        p2 = p2->m_pNext;
        //创建新节点
        NewNode = new ListNode();
        NewNode->m_nValue = min;
        NewNode->m_pNext = nullptr;
        //连接
        previously->m_pNext = NewNode;
        //更新链表尾指针
        previously = NewNode;
    }
    return head->m_pNext;
}
int main()
{
    /*测试用例1*/
    // 1 3 5 7       2 4 6 8
    ListNode* Node1 = CreateNode(1);
    ListNode* Node3 = CreateNode(3);
    ListNode* Node5 = CreateNode(5);
    ListNode* Node7 = CreateNode(7);
    ListNode* Node2 = CreateNode(2); 
    ListNode* Node4 = CreateNode(4); 
    ListNode* Node6 = CreateNode(6);
    ListNode* Node8 = CreateNode(8);
    connect(Node1, Node3);
    connect(Node3, Node5);
    connect(Node5, Node7);
    connect(Node2, Node4);
    connect(Node4, Node6);
    connect(Node6, Node8);
    cout << "链表1:";
    PrintList(Node1);
    cout << "链表2:";
    PrintList(Node2);
    ListNode* head = Merge(Node1, Node2);
    cout << "合并后的链表:";
    PrintList(head);
    cout << "--------------------------------------" << endl;
    /*测试用例2*/
    // 1 2 4      3 5 8  
    Node1 = CreateNode(1);
    Node2 = CreateNode(2);
    Node4 = CreateNode(4);
    Node3 = CreateNode(3);
    Node5 = CreateNode(5);
    Node8 = CreateNode(8);
    connect(Node1, Node2);
    connect(Node2, Node4);
    connect(Node3, Node5);
    connect(Node5, Node8);
    cout << "链表1:";
    PrintList(Node1);
    cout << "链表2:";
    PrintList(Node3);
    head = Merge(Node1, Node3);
    cout << "合并后的链表:";
    PrintList(head);
    return 0;
}


上述代码中发现创建新节点多次重复我们可以封装成一个函数

    int min = p2->m_nValue;
        p2 = p2->m_pNext;
        //创建新节点
        NewNode = new ListNode();
        NewNode->m_nValue = min;
        NewNode->m_pNext = nullptr;
        //连接
        previously->m_pNext = NewNode;
        //更新链表尾指针
        previously = NewNode;


优化后的不允许修改原链表代码

C++

#include <iostream>
using namespace std;
typedef int TElemType;//链表节点值的数据类型
struct ListNode
{
  TElemType m_nValue;
  ListNode* m_pNext;
};
ListNode* CreateNode(TElemType val)//创建节点
{
  ListNode* Node = new ListNode();
  Node->m_nValue = val;
  Node->m_pNext = nullptr;
  return Node;
}
void connect(ListNode* L1, ListNode* L2)//链接
{
  L1->m_pNext = L2;
}
void PrintList(ListNode* head)//输出
{
  while (head != nullptr)
  {
    cout << head->m_nValue;
    if (head->m_pNext != nullptr)
    {
      cout << "->";
    }
    else
    {
      cout << endl;
    }
    head = head->m_pNext;
  }
}
//创建并连接新节点
//此处传参必须要二级指针,因为要修改指针
void connNewNode(ListNode** previously, ListNode** p)
{
  //创建新节点
  ListNode* NewNode = new ListNode();
  NewNode->m_nValue = (*p)->m_nValue;
  NewNode->m_pNext = nullptr;
  //连接
  (*previously)->m_pNext = NewNode;
  //更新新链表尾指针
  *previously = NewNode;
  //更新原链表指针
  *p = (*p)->m_pNext;
}
ListNode* Merge(ListNode* pHead1, ListNode* pHead2)//合并俩链表
{
  if (nullptr == pHead1)
  {
    return pHead2;
  }
  if (nullptr == pHead2)
  {
    return pHead1;
  }
  ListNode* p1 = pHead1;
  ListNode* p2 = pHead2;
  ListNode* head = new ListNode();//新链表的表头指针
  head->m_nValue = 0;
  head->m_pNext = nullptr;
  ListNode* previously = head;//表尾指针
  while (p1 != nullptr && p2 != nullptr)
  {
    if (p1->m_nValue < p2->m_nValue)
    {
      connNewNode(&previously, &p1);
    }
    else
    {
      connNewNode(&previously, &p2);
    }
  }
  /*不可修改原链表的代码*/
  while (p1 != nullptr)
  {
    connNewNode(&previously, &p1);
  }
  /*不可修改原链表的代码*/
  while (p2 != nullptr)
  {
    connNewNode(&previously, &p2);
  }
  return head->m_pNext;
}
int main()
{
  /*测试用例1*/
  // 1 3 5 7       2 4 6 8
  ListNode* Node1 = CreateNode(1);
  ListNode* Node3 = CreateNode(3);
  ListNode* Node5 = CreateNode(5);
  ListNode* Node7 = CreateNode(7);
  ListNode* Node2 = CreateNode(2);
  ListNode* Node4 = CreateNode(4);
  ListNode* Node6 = CreateNode(6);
  ListNode* Node8 = CreateNode(8);
  connect(Node1, Node3);
  connect(Node3, Node5);
  connect(Node5, Node7);
  connect(Node2, Node4);
  connect(Node4, Node6);
  connect(Node6, Node8);
  cout << "链表1:";
  PrintList(Node1);
  cout << "链表2:";
  PrintList(Node2);
  ListNode* head = Merge(Node1, Node2);
  cout << "合并后的链表:";
  PrintList(head);
  cout << "--------------------------------------" << endl;
  /*测试用例2*/
  // 1 2 4      3 5 8  
  Node1 = CreateNode(1);
  Node2 = CreateNode(2);
  Node4 = CreateNode(4);
  Node3 = CreateNode(3);
  Node5 = CreateNode(5);
  Node8 = CreateNode(8);
  connect(Node1, Node2);
  connect(Node2, Node4);
  connect(Node3, Node5);
  connect(Node5, Node8);
  cout << "链表1:";
  PrintList(Node1);
  cout << "链表2:";
  PrintList(Node3);
  head = Merge(Node1, Node3);
  cout << "合并后的链表:";
  PrintList(head);
  return 0;
}


如果我们可以修改原链表,代码将更加简单。由于只修改了Merge函数,所以展示部分代码

C++

ListNode* Merge(ListNode* pHead1, ListNode* pHead2)//合并俩链表
{
  if (nullptr == pHead1)
  {
    return pHead2;
  }
  if (nullptr == pHead2)
  {
    return pHead1;
  }
  ListNode* p1 = pHead1;
  ListNode* p2 = pHead2;
  ListNode* head = new ListNode();//新链表的表头指针
  head->m_nValue = 0;
  head->m_pNext = nullptr;
  ListNode* previously = head;//表尾指针
  while (p1 != nullptr && p2 != nullptr)
  {
    if (p1->m_nValue < p2->m_nValue)
    {
      connNewNode(&previously, &p1);
    }
    else
    {
      connNewNode(&previously, &p2);
    }
  }
  /*可修改原链表的代码*/
  if (p1 != nullptr)
  {
    previously->m_pNext = p1;
  }
  /*可修改原链表的代码*/
  if (p2 != nullptr)
  {
    previously->m_pNext = p2;
  }
  return head->m_pNext;
}


本章完!

目录
相关文章
|
1月前
|
机器学习/深度学习 算法
24. 两两交换链表中的节点, 19.删除链表的倒数第N个节点 ,面试题 02.07. 链表相交
1. **两两交换链表中的节点**:通过引入虚拟头结点,使所有节点都能采用统一的交换逻辑,避免对头结点单独处理。 2. **删除链表的倒数第N个节点**:利用双指针技巧,让快慢指针保持N个节点的距离,当快指针到达末尾时,慢指针正好指向待删除节点的前一个节点。 3. **链表相交**:先计算两链表长度并调整起点,确保从相同距离末尾的位置开始遍历,从而高效找到相交节点或确定无交点。 以上方法均在时间复杂度和空间复杂度上进行了优化,适合用于理解和掌握链表的基本操作及常见算法设计思路。
|
9月前
(剑指offer)18、删除链表的节点—22、链表中倒数第K个节点—25、合并两个排序的链表—52、两个链表的第一个公共节点(2021.12.07)
(剑指offer)18、删除链表的节点—22、链表中倒数第K个节点—25、合并两个排序的链表—52、两个链表的第一个公共节点(2021.12.07)
105 0
|
9月前
|
存储 算法 安全
HashMap常见面试题(超全面):实现原理、扩容机制、链表何时升级为红黑树、死循环
HashMap常见面试题:红黑树、散列表,HashMap实现原理、扩容机制,HashMap的jd1.7与jdk1.8有什么区别,寻址算法、链表何时升级为红黑树、死循环
|
9月前
|
算法
❤️算法笔记❤️-(每日一刷-83、删除排序链表中的重复项)
❤️算法笔记❤️-(每日一刷-83、删除排序链表中的重复项)
81 0
|
11月前
|
存储 算法 Python
【面试题】合井K个升序链表
【面试题】合井K个升序链表
63 0
|
11月前
|
存储 Java
【Java集合类面试十】、HashMap中的循环链表是如何产生的?
在多线程环境下,HashMap在扩容时如果发生条件竞争,元素的插入顺序可能形成循环链表,导致死循环。
|
11月前
|
存储 Java
【IO面试题 四】、介绍一下Java的序列化与反序列化
Java的序列化与反序列化允许对象通过实现Serializable接口转换成字节序列并存储或传输,之后可以通过ObjectInputStream和ObjectOutputStream的方法将这些字节序列恢复成对象。
|
8月前
|
存储 算法 Java
大厂面试高频:什么是自旋锁?Java 实现自旋锁的原理?
本文详解自旋锁的概念、优缺点、使用场景及Java实现。关注【mikechen的互联网架构】,10年+BAT架构经验倾囊相授。
大厂面试高频:什么是自旋锁?Java 实现自旋锁的原理?
|
8月前
|
存储 缓存 算法
面试官:单核 CPU 支持 Java 多线程吗?为什么?被问懵了!
本文介绍了多线程环境下的几个关键概念,包括时间片、超线程、上下文切换及其影响因素,以及线程调度的两种方式——抢占式调度和协同式调度。文章还讨论了减少上下文切换次数以提高多线程程序效率的方法,如无锁并发编程、使用CAS算法等,并提出了合理的线程数量配置策略,以平衡CPU利用率和线程切换开销。
面试官:单核 CPU 支持 Java 多线程吗?为什么?被问懵了!
|
8月前
|
存储 缓存 Java
大厂面试必看!Java基本数据类型和包装类的那些坑
本文介绍了Java中的基本数据类型和包装类,包括整数类型、浮点数类型、字符类型和布尔类型。详细讲解了每种类型的特性和应用场景,并探讨了包装类的引入原因、装箱与拆箱机制以及缓存机制。最后总结了面试中常见的相关考点,帮助读者更好地理解和应对面试中的问题。
188 4