夯实算法-排序链表

180 阅读2分钟

题目:排序链表

给你链表的头结点 head ,请将其按 升序 排列并返回 排序后的链表 。

示例 1:

输入: head = [4,2,1,3]
输出: [1,2,3,4]

示例 2:

输入: head = [-1,5,3,4,0]
输出: [-1,0,3,4,5]

示例 3:

输入: head = []
输出: []

提示:

  • 链表中节点的数目在范围 [0,5104][0, 5 * 10^4] 内
  • 105 <=Node.val<=105-10^5 <= Node.val <= 10^5

解题思路

归并的核心思想就是把一个大集合拆分为两个小集合,再分别对两个小集合进行拆分,直到不能拆分为止,然后对拆分后的小集合进行排序,排序完成后再向上合并,最终合并为一个有序的集合。

首先要先确立链表的长度,然后通过for循环对链表元素进行倍速遍历(每次将上一步的两部分合为一部分)

定义first和second指针指向每一部分的头结点,每次事先保存每一部分的后继结点再将其断链送入Merge进行排序

将排序好的链表用pre指针连接起来,再将剩下的remain部分链表挂到尾部进入下一轮翻倍循环

等到最后一轮for循环结束,如有剩余元素就挂在已排序链表尾部,如果恰好等分排好整条链表,就返回dummy.next即可。

迭代版的归并排序,实现递归中的归的过程。

i表示当前层中每两个需要归并的同序链表的一个链表长度;

j表示第一个链表的开头位置,并判断第二个链表的开头位置是否合法;

k表示第二个链表的开头位置。

代码实现

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

private ListNode sortList(ListNode head, ListNode tail) {
    //无法继续拆分的情况
    if (head == null) {
        return null;
    }
    //无法继续拆分的情况
    if (head.next == tail) {
        head.next = null;
        return head;
    }

    //快慢指针找到中间节点
    ListNode slow = head, fast = head;
    while (fast != tail && fast.next != tail) {
        slow = slow.next;
        fast = fast.next.next;
    }

    ListNode mid = slow;
    //左边继续拆分
    ListNode left = sortList(head, mid);
    //右边继续拆分
    ListNode right = sortList(mid, tail);
    //有序链表合并
    return merge(left, right);
}

private ListNode merge(ListNode left, ListNode right) {
    ListNode mergeNode = new ListNode();
    ListNode help = mergeNode;
    //比较两个链表当前的值,值小的链表就把引用赋给mergeNode,并向后移动一位重新赋值给自己,同时help指向值小的那个节点
    while (left != null && right != null) {
        if (left.val < right.val) {
            help.next = left;
            left = left.next;
        } else {
            help.next = right;
            right = right.next;
        }
        help = help.next;
    }
    //最后如果有剩余的节点,就一次性链上去 
    help.next = left == null ? right : left;
    return mergeNode.next;
}

运行结果

Snipaste_2023-05-01_23-11-34.png

复杂度分析

  • 空间复杂度:O(n)
  • 时间复杂度:O(n)

掘金(JUEJIN) 一起分享知识, Keep Learning!