笔试题36. LeetCode OJ (23)

合并K个排序链表,没错。我的思路是分别从 K 个链表的中找出最小的一个值,然后依次插入链表,最后遍历完所有链表就好了,也没想中的那么难呀,循环遍历的思路如下: (先说明一下,它是不合格的)

主要思路是:

1.首先从所有排序链表里找一个最小val节点作为头结点

2.依次循环在各个链表里面找到最小节点摘下来尾插,循环结束的条件是当链表的数目为0或者为1的时候结束(可以通过lists[i]==NULL来判断lists[i]代表的链表是否结束了),还编写了一个函数用来找最小元素下标的,每次返回的lists[i]的节点的val的值是"最小的"

3.但是这种做法有很多问题,当每个链表的长度都为1,但是总的数目很多的时候,效率太低,会有很多不必要的访问的(摘掉节点后的 lists 里面会有很多存放的是 NULL 指针),所以下面这种做法是无法达到时间限制的。

class Solution {
public:
    ListNode* mergeKLists(vector<ListNode*>& lists)
    {
        if(lists.size() == 0)
        {
            return NULL;
        }
        int pos = findmin(lists);
        if(pos == -1)
        {
            return NULL;
        }

        ListNode *newHead=lists[pos];
        lists[pos] = lists[pos]->next;
        newHead->next = NULL;
        ListNode* cur = newHead;
        while(numOfList(lists)>1)
        {
            int pos = findmin(lists);
            if(pos == -1)
            {
                break;
            }
            cur->next = lists[pos];
            lists[pos] = lists[pos]->next;
            cur = cur->next;
            cur->next=NULL;
            EraseList(lists);
        }

        if( numOfList(lists) == 1)
        {
            int lastpos = findmin(lists);
            cur->next = lists[lastpos];
        }
        return newHead;
    }

    int numOfList(vector<ListNode*>&lists)
    {
        int num = 0;
        int len = lists.size();
        while(--len)
        {
            if(lists[len])
            {
                ++num;
            }
        }
        return num;
    }

    int findmin(vector<ListNode*>& lists)
    {
        int minval = 0;
        int minpos = -1;
        int j=0;
        for( ;j<lists.size();++j)
        {
            if(lists[j])
            {
                minval=lists[j]->val;
                minpos=j;
                break;
            }
        }
        for(int i=j+1;i<lists.size();++i)
        {
            if(lists[i] == NULL)
            {
                continue;
            }
            if(lists[i]->val < minval)
            {
                minval=lists[i]->val;
                minpos = i;
            }
        }
        return minpos;
    }

    void EraseList(vector<ListNode*>& lists)
    {
        int pos = 0;
        while(lists[pos] == NULL)
        {
            ++pos;
        }
        lists.erase(lists.begin(),lists.begin()+pos);
    }

};

-------------------写了好几个函数才实现的,居然效率不达标,那么这时候必须换种想法了--------------分治

对,我们可以通过分治的想法来实现,我们把大任务分为子任务,最后再汇总起来不就行了。所以我通过归并的思路去解题的,思路如下:

1.先把 k 个链表无限二等分并且递归进去

2.当区间的长度为1时,直接返回上一级,说明已经完成了这一趟的合并

3.当区间的长度为2时,合并这两个链表,在把结果返回上一级

4........最后,在把第一次分开的两个分支合并-------------------这就转化为了 k/2 次合并两个链表的思想(合并次数大于K/2)

代码如下:

/**
 * Definition for singly-linked list.
 * struct ListNode {
 *     int val;
 *     ListNode *next;
 *     ListNode(int x) : val(x), next(NULL) {}
 * };
 */
class Solution {
public:
    ListNode* mergeKLists(vector<ListNode*>& lists)
    {
        /*
            合并k个排序链表
            极端情况是n个只有一个节点的链表
            所以不能循环的去做,我想到了归并排序的方法
        */

        ListNode *list1;
        ListNode *list2;
        //归并排序
        int len = lists.size();
        if(len == 0)
        {
            return NULL;
        }
        if(len == 1)
        {
            return lists[0];
        }

        int mid=len/2;
        domerge(list1,lists,0,mid);
        domerge(list2,lists,mid+1,len-1);
        ListNode *newHead = merge(list1,list2);
        return newHead;
    }   

    void domerge(ListNode*& list,vector<ListNode*>&lists,int begin,int end)
    {
        if(begin > end)
        {
            list=NULL;
        }
        if(begin == end)
        {//返回
            list=lists[begin];
        }

        if(end-begin == 1)
        {//合并
            list=merge(lists[begin],lists[end]);
        }

        if(end-begin >1)
        {//继续二分
            int mid = begin+(end-begin)/2;
            ListNode *l1;
            ListNode *l2;
            domerge(l1,lists,begin,mid);
            domerge(l2,lists,mid+1,end);
            list=merge(l1,l2);
        }
    }

    ListNode *merge(ListNode *l1,ListNode *l2)
    {
        if(l1 == NULL && l2==NULL)
        {
            return NULL;
        }
        if(l1 == NULL)
        {
            return l2;
        }
        if(l2 == NULL)
        {
            return l1;
        }
        ListNode *ret = NULL;
        if(l1->val <= l2->val)
        {
            ret = l1;
            l1 = l1->next;
        }
        else
        {
            ret = l2;
            l2 = l2->next;
        }
        ret->next = NULL;
        ListNode* cur = ret;
        while(l1 && l2)
        {
            if(l1->val <= l2->val)
            {
                cur->next = l1;
                l1 = l1->next;
                cur=cur->next;
            }
            else
            {
                cur->next = l2;
                l2 = l2->next;
                cur = cur->next;
            }
            cur->next = NULL;
        }
        if(l1)
        {
            cur->next = l1;
        }
        if(l2)
        {
            cur->next = l2;
        }
        return ret;
    }
};

结果是同过所有测试用例,包括第一种解法遇到的问题

时间: 2024-10-12 16:15:52

笔试题36. LeetCode OJ (23)的相关文章

笔试题49. LeetCode OJ (36)

这个题目是一个"数独"问题的求解,数独就是由9个九宫格组成的,规则是每行没列包含数字1~9,且每个九宫格内也是有1~9组成的,现在让我们判断数独是否合法. 这个问题比较常规了,我们按规矩行事就行了,既然数独的规则是:每行,每列,每宫,都是数字1~9,(此题中并没有全部给出数字,没给出的地方用' . '代替),所以我们只要判断每行,每列,每宫的数字字符是否合格即可 这种题目只是考察对规则的掌握就可解题,不是很难,所以我并没有细细说明,如果要我们给出数独问题的解,那就麻烦了,记得算法里面学

笔试题28. LeetCode OJ (15)

这个题确实比较复杂,我刚刚开始的思路是先将数组排序,然后从左向右遍历,然后用两个变量lpos,rpos分别指向left+1 和 nums.size()-1,然后求三者的和,若和sum < 0 则让lpos加1,若sum>0则让rpos减1.想法不错,可是现实很残酷.这样 的解很容易错过真实解,我测试了很多遍,总有测试用例无法通过.其中还有一个时间复杂度太高了也没通过测试.这个题大家 可以自己去实现试试,真的错误点太多了,最后还是采用了比较老实的办法,再一步一步分析,求的解如下: class S

笔试题52. LeetCode OJ (39)

如果还记的话或者写过LeetCode的人会知道,这是nsum,已经不再是之前的twosum,threesum,foursum了.之前是都是使用 i 层循环解题的,那么现在n层循环理论上是可以解题的(其实应该不行),但是n是不确定了,而且是可变的,其变化范围是[1,n] 说道这里其实想说明的是,我们要换种思路去解题了.我在看到这个题的时候想到的思路是: 我们从小到大一个一个将最小的数字重复不断的加入到 vector 中,若vector现有的值的和比 target 大则将 vector 集合中的元素

笔试题42. LeetCode OJ (29)

题目意思清晰明了:求两个数的商,不能使用乘法,除法或者求模运算等等.看似很简单的一道题,可是在排行榜上的正确率却是最低的一道,原因是情况很复杂,边界很难控制.需要考虑到的细节特别多,如:正负号,除数和被除数的取值,还有就是越界情况.其中越界情况最难考虑到,我也给拉低这道题的正确率增加了一份"功劳",真的测试了好几遍才将条件考虑全面,我的代码中写有很多注释(大部分以测试用例形式给出)可以帮助大家分析特定情况,这类型的题目没有很强的技巧,唯一需要注意的就是"细心".对了

笔试题73. LeetCode OJ (60)

Permutation Sequence 这个题是求1~n (n[1~9]) 的数字的全排列的第K个序列. 一般思路是:使用一个计数器,递归去找全排列序列,找到一个计数器加一,一直到第k个. 但是加若 n = 9 我要找的是第 (9! -1 )个数,那么上述办法的时间是多少,多半会超时的(没试过,但是我敢保证一定会超时的,因为这样的思路不可取),想一想我们只需要一个序列,并不必要把全部的序列都找出来吧.下面我给出一种解题方案,我个人感觉是可取的. 我们是学过数学的人,要我们求全排列的第 k 个序

笔试题85. LeetCode OJ (71)

              Simplify Path     这个题是给出一个字符串的相对路径,让我们返回绝对路径,让我们在Linux下自己去动手操作确实不难,可是用程序做起来的话确实比较麻烦. 我的解题思路是找好'/'和'.'之间的关系,找好他们之间的组合关系后这个题就容易一些.在路径中'.'和'/'的组合正常的可能只有'.'和'..'两种.'/'是起修饰作用的,说明当前目录下可能还存在目录. 既然我们要写这个题的话,那么肯定会出现各种组合的情况,其实我们只要处理好两种正常的情况就差不多了,

笔试题30. LeetCode OJ (17)

这个题目有意思,和生活接近,题目的意思一看就知道,应该是字符串的全排列吧.但是需要注意的是有几数字字符是没有对应的字符串的,比如'0'和'1',所以我们的输入字符串中若有他们,则将它们过滤掉.这个题我的思路是递归,因为这种类型的题目递归思路清晰明了,若要使用循环,则会形成n层循环,所以循环的思想应该被淘汰掉.我在做这个题的时候喜欢先把"多余"的东西先给它解决掉,比如'0'和'1'这两个数字,我先遍历一边digits数组,将'0'和'1'过滤掉,这样在后面的递归中可以少考虑一些东西,自认

笔试题74. LeetCode OJ (61)

    Rotate List    这个题的意思旋转链表,更具体点的意思右移链表,移出去的节点放到头部前面,结合着题目给出的例子还是很好理解的. 这个题的主要思路是:摘取从末尾到头的k个节点,然后将他们放到头部. 需要注意的是,上面说的k并不一定等于传入的k的值,因为这个k很可能比链表的长度还大.所以我主要思路是:遍历一遍链表,找到链表的长度n,然后k%=n(这时候k<n,我们更喜欢的是此时k=0),这样就可以找出实际需要移动的节点的个数,然后将链表的最后k个节点放到链表的前面就行了.大概方法

笔试题72. LeetCode OJ (59)

Spiral Matrix II 看上图就能知道这个提示要干什么的,给定一个 n 值,按照螺旋数组的存储特点将 1~N^2 存放到螺旋数组中. 思路:使用一个计数器(引用的方式使用),然后按照螺旋数组的方式去遍历的特点依次将该计数器的值赋给数组相应的位置,遍历完成后就是上述的样子了了,需要注意一下几点. 1.我们需要实现分配空间(讲vector的大小给定),否则肯定会崩溃的... 2.还是需要注意螺旋数组中一下常见问题,如重复问题,越界问题等等 代码如下: class Solution { pu