leetcode 第23题:关于递归的一些思考与应用

来源:互联网 发布:mac启动磁盘空间不足 编辑:程序博客网 时间:2024/05/07 08:59

题目大意:

给出若干已经排好序链表的头节点指针,将它们合并成一个成序的链表,返回其头结点指针。

示例:

给定链表:1->2->3 和 4->6->8,则应当返回链表 1->2->3->4->6->8的头节点指针。

解题思路:

  一开始看到这道题,我觉得会比较复杂。因为要排成有序链表需要先逐个比较,再将节点插入对应的位置。后来联想到之前使用过的哈希表,变换思路如下:遍历每一个链表,将每个节点的值插入到容器set中,同时建立一个哈希表,存储节点的值和它出现的次数。所有链表遍历结束之后,所有出现过的节点值都保存到了set中,并且按顺序排好。此时,只需要再遍历一遍set,再找到哈希表中对应出现的次数,就可完成合并链表的创建。实现代码如下:
   这里写图片描述
  以上代码在本地通过了测试,但上传到leetcode时,却出现了编译错误。似乎是因为它不允许在类外创建全局变量。回过头看题目,我发现有一个重要条件完全没有派上用场:给出的链表是已经排好序的,这说明算法复杂度肯定不是最优的。并且,实现代码中,我自行创建了集合、映射表,又新建了节点,导致算法的空间复杂度也会很大,进一步降低了其实用性。
  在discuss中,高票的C++解答是基于之前的两链表合并问题的拓展。AC代码如下:
  

/** 1. Definition for singly-linked list. 2. struct ListNode { 3.     int val; 4.     ListNode *next; 5.     ListNode(int x) : val(x), next(NULL) {} 6. }; */class Solution {public:    ListNode* mergeKLists(vector<ListNode*>& lists) {    if(lists.size()==0)        return nullptr;    while(lists.size()>1)    {        lists.push_back(merge2Lists(lists[0],lists[1]));        lists.erase(lists.begin());        lists.erase(lists.begin());    }    return lists.front();          }    ListNode* merge2Lists(ListNode* l1, ListNode* l2){  //连接两个链表    if(l1==nullptr)        return l2;  //如果是空指针,则返回l1    if(l2==nullptr)        return l1;    if(l1->val>l2->val)      {           l2->next=merge2Lists(l1,l2->next);  //确定下下一节点        return l2;  //返回比较结果    }    else    {        l1->next=merge2Lists(l1->next,l2);        return l1;    }    }};

  可以看出,它在时间和空间复杂度上都更加优越。经仔细分析,在合并两个链表时,它是通过递归的方式寻找当前节点的下一节点,直到有一条链表为空为止。
  对我来说,递归一直是一个理解上的难点。因此我试着从几个例子中去理解它的意思。以链表1:1->2->3和链:2:2->3->4为例:
  1. 首先需要寻找合成链表的头结点,换言之,两个链表头结点中值较小的那一个,即1。
  2. 随后寻找下一节点。此时,需要比较当前节点在本链表中的下一节点与另一链表的当前节点值的大小,较小者为下一节点。此处,为2和2,不妨取链表2中的“2”作为下一节点。
  3. 再次寻找下一节点,与步骤2类似,比较2与3,链表1中的“2”成为下一节点
……
  4. 重复以上过程,当需要确定链表1中的3的下一节点时,发现它在本链表中已经是最后一个节点。因此另一链表中的节点自动成为下一节点。由于两个链表本身是成序的,因此也无需进行下一步的搜索,即可得到结果。
  如此,只需要用一个函数merge2Lists即可实现上述过程。不难发现,如果采用顺序的思维,是很容易理解以上的代码的。但轮到自己去实现时,仍然很难完成。归根结底,是因为习惯了先程序中得到结果,再将结果进行进一步的处理的方式,不习惯递归的思维方法——先进行处理,再将结果逐步回带。