leetcode148. Sort List神剖析

  1. Sort List
    Given the head of a linked list, return the list after sorting it in ascending order.

Follow up: Can you sort the linked list in O(n logn) time and O(1) memory (i.e. constant space)?
这里给出了三个测试用例
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
初一看这个题可能觉得没什么,也就是个单链表排序问题,于是我就傻乎乎的写出了这样的解法(C++实现的常规暴力解法)

ListNode* sortList(ListNode* head) {
    
    
        //头插法排序
        if(head == NULL) return NULL;
        ListNode *root = new ListNode();
        ListNode *s = head->next,*pre,*q;
        head -> next = NULL;
        root->next = head;
        pre = root;
        while(s != NULL){
    
    
            ListNode *tmp = root -> next;
            while(tmp != NULL && s != NULL && s->val > tmp->val){
    
    
                pre = tmp;
                tmp = tmp -> next;
            }
            // 直到找到List中大于s->val的
            pre -> next = s;
            q = s -> next;
            s -> next = tmp;
            s = q;
            pre = root;
            
        }
        return root->next;
    }
};

对上述解法的简单总结就是,建立一个空链表root,然后每次从head链表上拿走一个结点插入到root中,至于插入到root中的哪个位置,则是根据内层while循环进行搜索,直到找到第一个比它大的结点然后停止。就可以三部曲插入结点啦!然鹅,Medium的题并没有那么简单,程序运行超时。而且我留意到起初的那句话,时间复杂度O(nlogn),空间复杂度O(1)。我是一个都没有做到呀,惭愧惭愧,立马跑去Discuss区剽窃学习大佬的思路。

经过耐心的分析和理解,终于把没有注释的神仙级代码看懂了,必须在此做个记录,方便回顾学习!

原文在这里,用到了递归和分治,for循环中的while循环一共执行logn次(因为每次都是跳着移动长度为n的列表),因此整体复杂度为O(nlogn)。核心思想就是每次把相邻的两个子列表长度一样,依次为2,4,8等等)进行按序合并,最后直到整个列表都有序。

  • 例如第一轮,先把长度为n的list划分为多个长度为2的小列表,暂时标记为l1,l2,l3,l4…,然后将l1与l2顺序合并,l3与l4顺序合并,以此类推。
  • 第二轮每次将长度为4的小列表合并,同理最后将两个有序的长为length/2的列表合并(同归并排序的思路一样!!!

其他的细节我就把写到注释里,大家看看哈,如果还有不明白的地方请评论,我经常在CSDN的哈哈哈

ListNode* sortList(ListNode* head) {
    
    
        // 解决双层while循环的超时问题
        int length = 0; // 记录链表的长度
        ListNode *tmp = head; //临时指针,辅助计算链表长度
        while(tmp!=NULL){
    
    
            length++;
            tmp = tmp->next;
        }
        // 得到链表长度
        ListNode dummy(0); // 还是构建一个新的列表
        ListNode *left, *right,*tail;
        dummy.next = head;
        for(int step = 1;step < length;step<<=1){
    
     //i安装2,4,8,16的方式扩大
            ListNode* cur = dummy.next;
            tail = &dummy; //取dummy的地址
            while(cur != NULL){
    
    
                left = cur;
                right = split(left, step);//从left结点的位置开始,向右移step个位置
                cur = split(right, step); //从right结点的位置开始,向右移step个位置
                //经过上面的两次移动,共从head移动了两个step的距离,这就是循环指针每次*2的原因
                //下面合并两个子列表,并且返回列表尾指针用于连接下一层
                tail = merge(left, right, tail);
            }
        }
        return dummy.next;
    }
    // 列表后移n下
    ListNode* split(ListNode* head, int n){
    
    
        for(int i = 1; head && i < n;i++) head = head -> next;
        if(head == NULL) return NULL;
        ListNode* second = head -> next;
        head -> next = NULL;
        return second;
    }
    // 合并两个子列表,分别表示前半个列表和后半个列表的首个结点
    ListNode* merge(ListNode* l1, ListNode* l2, ListNode* head){
    
    
        ListNode *tmp = head;
        while(l1&&l2){
    
    
            if(l1->val < l2->val){
    
    
                tmp->next = l1;
                tmp = tmp -> next;
                l1 = l1->next;
            }else{
    
    
                tmp->next = l2;
                tmp = tmp -> next;
                l2 = l2 -> next;
            }
        }
        tmp -> next = l1? l1:l2;
        while(tmp->next) tmp = tmp->next;
        return tmp;
    }

猜你喜欢

转载自blog.csdn.net/weixin_42474261/article/details/109479801
今日推荐