IT数码 购物 网址 头条 软件 日历 阅读 图书馆
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
图片批量下载器
↓批量下载图片,美女图库↓
图片自动播放器
↓图片自动播放器↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁
 
   -> 数据结构与算法 -> 每日写题分享--排序链表 |归并排序 -> 正文阅读

[数据结构与算法]每日写题分享--排序链表 |归并排序

题目描述:

题目链接戳此

解题思路:

题目的进阶问题要求达到 O(nlogn) 的时间复杂度和O(1) 的空间复杂度,时间复杂度是 O(nlogn) 的排序算法包括归并排序、堆排序和快速排序(快速排序的最差时间复杂度是 O(n^2),其中最适合链表的排序算法是归并排序。

先回忆一下自顶向下归并:

归并排序排序采用分治算法,将已有序的子序列合并得到完全有序的序列。

?

在本题中,先找到链表的中间节点(可以用快慢指针),将链表分为两个部分,再分别二分左右两边的链表,直到节点为空或者只剩一个节点时结束。这里可以采用递归方式分割。分割完成后就可以合并各个子链表。

代码实现如下:

class Solution {

    public ListNode sortList(ListNode head) {
         return sortList(head,null);
    }

    public ListNode sortList(ListNode head,ListNode tail) {//每次递归都有记录好的头结点和为节点,递归传入的中间值保留在下一个子序列当中,所以尾结点置为空。先向后看
        //递归结束条件
        if (head == null) return head;
        if (head.next == tail) {
            head.next = null;//只保留head到mid的前一个节点
            return head;
        }
        ListNode slow = head,fast = head;//快慢指针,找到中间节点
        while (fast != tail) {
            fast = fast.next;
            slow = slow.next;
            if (fast != tail) {
                fast = fast.next;
            }
        }
        ListNode mid = slow;
        ListNode head1 = sortList(head,mid);//开始递归,由上面的代码铺垫,这里的mid会被置为空
        ListNode head2 = sortList(mid,tail);//这里的mid相当于头结点
        ListNode new_head = merge(head1,head2);合并子序列
        return new_head;
    }
    public ListNode merge(ListNode head1,ListNode head2) {
        ListNode dummyHead = new ListNode(-1);//傀儡节点
        ListNode tmp = dummyHead,tmp1 = head1,tmp2 = head2;
        while (tmp1 != null && tmp2 != null) {
            if (tmp1.val <= tmp2.val) {
                tmp.next = tmp1;
                tmp1 = tmp1.next;
            } else {
                tmp.next = tmp2;
                tmp2 = tmp2.next;
            }
            tmp = tmp.next;
        }
        if (tmp1 == null) tmp.next = tmp2;
        else if (tmp2 == null) tmp.next = tmp1;
        return dummyHead.next;
    }
}

由于自顶向下归并用到了递归方法,空间复杂度达到O(logn),不符合题目要求的O(1),所以这里要采取自底向上的非递归排序方法。我们可以采用迭代的方法从最小的单元开始合并,合并后的新的子序列继续合并,直到子序列长度达到链表原始长度。所以第一步我们要求的是链表的长度,然后根据长度迭代时间复杂度O(N)。每次长度都会缩小两倍,时间复杂度O(logN)。完成以上步骤后时间复杂度为O(nlogn),空间复杂度只有定义的变量,是常数复杂度O(1)。

代码实现如下:

class Solution {
    public ListNode sortList(ListNode head) {
        if (head == null) return head;
        int length = 0;
        ListNode node = head;
        while (node != null) {//求出总长度
            length++;
            node = node.next;
        }
        
        //引入傀儡节点
        ListNode dummyHead = new ListNode(0,head);
        
        //拆分链表成若干个子链表,子链表长度从1开始,后面是2、4、8、16...直到达到length。
        for (int subLen = 1;subLen < length;subLen <<= 1) {
            ListNode prev = dummyHead;        //prev负责连接每个有序子链表
            ListNode curr = dummyHead.next;    //拆分链表开始的位置

            while (curr != null) {//如果没有拆分结束
                ListNode head1 = curr;        //记录第一个子链表
                for (int i = 1;i < subLen && curr != null && curr.next != null;i++) {
                    curr = curr.next;        
                }
                ListNode head2 = curr.next;    //第一个子链表长度达到subLen,下一个节点是第二个链表的起始位置
                curr.next = null;            //断开第一个和第二个链表的连接
                curr = head2;

                for (int i = 1;i < subLen && curr != null && curr.next != null;i++) {
                    curr = curr.next;        //开始拆分第二个长度为subLen的链表
                }
                ListNode next = null;        
                if (curr != null) {
                    next = curr.next;        //记录后面剩下的链表的位置
                    curr.next = null;        //断开第二个链表与后面的连接,这样head1、head2都是两个独立的链表
                }

                ListNode merge = mergeTwoLists(head1,head2);//排序后合并成有序链表
                prev.next = merge;            //连接上新的子链表的头结点
                while (prev.next != null) {
                    prev = prev.next;        //走到目前已经排好序的链表的末尾,准备下一次连接新的子链表
                }

                curr = next;                //curr从第三个第三个链表的头开始走,准备和第四个链表排序后合并
            }
        }
        return dummyHead.next;
    }

    public ListNode mergeTwoLists(ListNode head1,ListNode head2) {//合并子链表
        ListNode dummy = new ListNode(-1);
        ListNode cur = dummy;
        while (head1 != null && head2 != null) {    //只要有一个链表为空则退出循环
            if (head1.val < head2.val) {
                cur.next = head1;
                head1 = head1.next;
            } else {
                cur.next = head2;
                head2 = head2.next;
            }
            cur = cur.next;
        }
        if (head1 == null) cur.next = head2;    //把没走完的链表的剩下部分连接上
        else if (head2 == null) cur.next = head1;
        return dummy.next; 
    }
}

  数据结构与算法 最新文章
【力扣106】 从中序与后续遍历序列构造二叉
leetcode 322 零钱兑换
哈希的应用:海量数据处理
动态规划|最短Hamilton路径
华为机试_HJ41 称砝码【中等】【menset】【
【C与数据结构】——寒假提高每日练习Day1
基础算法——堆排序
2023王道数据结构线性表--单链表课后习题部
LeetCode 之 反转链表的一部分
【题解】lintcode必刷50题<有效的括号序列
上一篇文章      下一篇文章      查看所有文章
加:2021-12-16 17:56:08  更:2021-12-16 17:56:20 
 
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁

360图书馆 购物 三丰科技 阅读网 日历 万年历 2024年10日历 -2024/10/30 18:19:04-

图片自动播放器
↓图片自动播放器↓
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
图片批量下载器
↓批量下载图片,美女图库↓
  网站联系: qq:121756557 email:121756557@qq.com  IT数码