力扣148——排序链表

原题

在 O(n log n) 时间复杂度和常数级空间复杂度下,对链表进行排序。

示例 1:

输入: 4->2->1->3
输出: 1->2->3->4

示例 2:

输入: -1->5->3->4->0
输出: -1->0->3->4->5

原题url:https://leetcode-cn.com/problems/sort-list/

解决

题目很明确,排序,对于时间复杂度和空间复杂度有要求,针对O(n log n),让我想到了归并排序快速排序,接下来我们各自来看看。

对了,这里先统一放一下节点类,单向链表中的节点,存储当前节点的值和后一个节点的引用。

Definition for singly-linked list.
public class ListNode {
    int val;
    ListNode next;
    ListNode(int x) { val = x; }
}

归并排序

归并排序,说白了,就是先分解到最小单元,然后逐个进行合并并排序,这样在合并的时候,其实两个链表本身就是有序的,那么当有一个全部取完后,另一个可以直接拼接在最后面。

让我们看一下代码:

public class Solution {
    public ListNode sortList(ListNode head) {
        // 归并排序

        if (head == null || head.next == null) {
            return head;
        }

        // 先分隔,利用快慢指针分隔。
        // 快指针先走,因为只有当空节点或1个节点才是终止条件,2个节点的时候,如果不让快指针先走,而是也指向head,那么2个节点永远不会被分隔,会陷入死循环
        ListNode fast = head.next.next;
        ListNode slow = head;
        while (true) {
            if (fast == null || fast.next == null) {
                break;
            }

            fast = fast.next.next;
            slow = slow.next;
        }
        // 后半部分的开头
        ListNode second = slow.next;
        second = sortList(second);
        // 前半部分的开头
        slow.next = null;
        ListNode first = head;
        first = sortList(first);

        // 合并
        ListNode result = new ListNode(0);
        head = result;
        while (first != null && second != null) {
            if (first.val < second.val) {
                result.next = first;
                first = first.next;
            } else {
                result.next = second;
                second = second.next;
            }
            result = result.next;
        }
        if (first != null) {
            result.next = first;
        } else {
            result.next = second;
        }

        return head.next;
    }
}

提交OK,执行用时:5 ms,内存消耗:39.6 MB,执行用时只战胜了59.07%的 java 提交记录,应该还有优化的空间。

归并排序——优化

针对上面的代码,在分隔的时候,设置fast = head.next.next,这是因为我们设置的递归终止条件是针对null或者单个节点的。其实当只剩下两个节点的时候,就可以进行排序了,这样应该可以节省近一半的时间,当然了,从时间复杂度上来说并没有改变。

我们看一下代码:

public class Solution {
    public ListNode sortList(ListNode head) {
        // 归并排序
        if (head == null || head.next == null) {
            return head;
        }

        // 说明只有两个节点
        if (head.next.next == null) {
            ListNode second = head.next;
            if (head.val > second.val) {
                return head;
            } else {
                second.next = head;
                head.next = null;
                return second;
            }
        }

        // 先分隔,利用快慢指针分隔。
        ListNode fast = head;
        ListNode slow = head;
        while (true) {
            if (fast == null || fast.next == null) {
                break;
            }

            fast = fast.next.next;
            slow = slow.next;
        }
        // 后半部分的开头
        ListNode second = slow.next;
        second = sortList(second);
        // 前半部分的开头
        slow.next = null;
        ListNode first = head;
        first = sortList(first);

        // 合并
        ListNode result = new ListNode(0);
        head = result;
        while (first != null && second != null) {
            if (first.val < second.val) {
                result.next = first;
                first = first.next;
            } else {
                result.next = second;
                second = second.next;
            }
            result = result.next;
        }
        if (first != null) {
            result.next = first;
        } else {
            result.next = second;
        }

        return head.next;
    }
}

执行用时,有的时候是4 ms,有的时候是3 ms,看来归并排序这条路差不多就是这样了。

快速排序

快速排序的思想就是选择一个标准值,将比它大的和比它的小的,做交换。针对链表这种结构,就是将比它大的放在一个链表中,比它小的放在一个链表中,和它一样大的,放在另一个链表中。然后针对小的和大的链表,继续排序。最终将三个链表按照小、相等、大进行连接。

接下来让我们看看代码:

class Solution {
        public ListNode sortList(ListNode head) {
            // 利用快排

            // 单个节点是终止节点
            if (head == null || head.next == null) {
                return head;
            }
            // 比标准值小的节点
            ListNode lowHead = new ListNode(0);
            ListNode low = lowHead;
            // 和标准值一样的节点
            ListNode midHead = new ListNode(0);
            ListNode mid = midHead;
            // 比标准值大的节点
            ListNode highHead = new ListNode(0);
            ListNode high = highHead;

            // 标准值
            int val = head.val;
            ListNode node = head;
            // 遍历
            while (node != null) {
                // 比标准值大的节点
                if (node.val > val) {
                    high.next = node;
                    high = high.next;
                }
                // 比标准值小的节点
                else if (node.val < val) {
                    low.next = node;
                    low = low.next;
                }
                // 和标准值一样的节点
                else {
                    mid.next = node;
                    mid = mid.next;
                }
                node = node.next;
            }
            // 终止,避免造成环
            low.next = null;
            high.next = null;

            lowHead.next = sortList(lowHead.next);
            highHead.next = sortList(highHead.next);

            // 找出小节点链表的末尾
            low = lowHead;
            while (low.next != null) {
                low = low.next;
            }
            // 拼接
            low.next = midHead.next;
            mid.next = highHead.next;

            return lowHead.next;
        }
    }

提交OK,执行用时:2 ms,内存消耗:40.01 MB

和归并排序相比,时间更短,至于原因,我确实是没有想明白,因为都需要比较,然后重新构造新链表。我猜测是测试数据离散程度更高,这样归并排序的话,并没有充分利用其特性:

当两个链表合并时,如果一个链表已经全部结束,另一个链表剩余的部分可以直接拼接。

总结

以上就是这道题目我的解答过程了,不知道大家是否理解了。针对它的时间复杂度要求,利用归并排序或者快速排序解决。

有兴趣的话可以访问我的博客或者关注我的公众号、头条号,说不定会有意外的惊喜。

https://death00.github.io/

公众号:健程之道

原文地址:https://www.cnblogs.com/death00/p/12151679.html

时间: 2024-10-04 12:51:26

力扣148——排序链表的相关文章

[Leetcode]148. 排序链表(归并排序)

题目 在?O(n?log?n) 时间复杂度和常数级空间复杂度下,对链表进行排序. 示例 1: 输入: 4->2->1->3 输出: 1->2->3->4 示例 2: 输入: -1->5->3->4->0 输出: -1->0->3->4->5 来源:力扣(LeetCode) 链接:https://leetcode-cn.com/problems/sort-list 著作权归领扣网络所有.商业转载请联系官方授权,非商业转载请注

148.排序链表

题目描述: 在 O(n log n) 时间复杂度和常数级空间复杂度下,对链表进行排序. 示例 1: 输入: 4->2->1->3 输出: 1->2->3->4 示例 2: 输入: -1->5->3->4->0 输出: -1->0->3->4->5 思路: 要求 O(n log n) 时间复杂度和常数级空间复杂度,首先想到的就是快速排序和归并排序. 此处使用归并排序, 归并排序大致思想是: 1.将链表划分左右两部分-->

LeetCode 148. 排序链表

在 O(n log n) 时间复杂度和常数级空间复杂度下,对链表进行排序. 示例 1: 输入: 4->2->1->3输出: 1->2->3->4 示例 2: 输入: -1->5->3->4->0输出: -1->0->3->4->5算法:归并排序(且只能归并排序,因为题目做了要求).我们依据归并排序的思想.将原链表分为两部分进行递归排序即可. /** * Definition for singly-linked list.

leetcode 148. 排序链表(c++)

在 O(n log n) 时间复杂度和常数级空间复杂度下,对链表进行排序. 示例 1: 输入: 4->2->1->3输出: 1->2->3->4示例 2: 输入: -1->5->3->4->0输出: -1->0->3->4->5 /** * Definition for singly-linked list. * struct ListNode { * int val; * ListNode *next; * ListNo

力扣86——分隔链表

原题 给定一个链表和一个特定值 x,对链表进行分隔,使得所有小于 x 的节点都在大于或等于 x 的节点之前. 你应当保留两个分区中每个节点的初始相对位置. 示例: 输入: head = 1->4->3->2->5->2, x = 3 输出: 1->2->2->4->3->5 原题url:https://leetcode-cn.com/problems/partition-list/ 解题 题目很好理解,重点在于区分大于等于和小于目标值的节点,判断

力扣142——环形链表 II

原题 给定一个链表,返回链表开始入环的第一个节点.?如果链表无环,则返回?null. 为了表示给定链表中的环,我们使用整数 pos 来表示链表尾连接到链表中的位置(索引从 0 开始). 如果 pos 是 -1,则在该链表中没有环. 说明:不允许修改给定的链表. ? 示例 1: 输入:head = [3,2,0,-4], pos = 1 输出:tail connects to node index 1 解释:链表中有一个环,其尾部连接到第二个节点. 示例?2: 输入:head = [1,2], p

力扣 删除排序数组中的重复项

1. 我的笨比做法 将整个数组平移 (太笨比了) 2. 实际上 本题的关键只要把不同的元素移到数组的左侧就行 原文地址:https://www.cnblogs.com/TsinghuaComing/p/12254902.html

力扣—Remove Duplicates from Sorted List(删除排序链表中的重复元素)python实现

题目描述: 中文: 给定一个排序链表,删除所有重复的元素,使得每个元素只出现一次. 示例 1: 输入: 1->1->2输出: 1->2 示例 2: 输入: 1->1->2->3->3输出: 1->2->3 英文: Given a sorted linked list, delete all duplicates such that each element appear only once. Example 1: Input: 1->1->

力扣(LeetCode)删除排序链表中的重复元素 个人题解

给定一个排序链表,删除所有重复的元素,使得每个元素只出现一次. 这题思路比较简单,同样是快慢针的思路. 用一个整数类型val对应最新的只出现过一次的那个值, 如果节点的下一个节点的值和这个对应则不做别的操作,快针进入下一个, 如果不对应则接到慢针对应的节点上,同时快针慢针都向前一位,整数val设置为新的值,以此类推. 代码如下: 1 class Solution { 2 public ListNode deleteDuplicates(ListNode head) { 3 if (head ==