Leetcode148. Sort List

Leetcode148. Sort List

Sort a linked list in O ( n l o g n ) O(nlogn) O(nlogn) time using constant space complexity.

Example 1:

Input: 4->2->1->3
Output: 1->2->3->4

Example 2:

Input: -1->5->3->4->0
Output: -1->0->3->4->5
解法一 归并排序 递归 自顶向下

要求时间复杂度是 O ( n l o g n ) O(nlogn) O(nlogn),可以使用归并排序。

归并排序需要一个辅助方法,也就是对两个有序链表进行合并,具体见Leetcode21. Merge Two Sorted Lists

归并排序是从中间分一半,因此我们需要用到快慢指针来定位链表中点

ListNode dummy = new ListNode(0);
dummy.next = head;
ListNode fast = dummy;
ListNode slow = dummy;
while (fast != null && fast.next != null) {
    slow = slow.next;
    fast = fast.next.next;
}

dummy指针作用是当节点个数是偶数的时候,让 slow 刚好指向前边一半节点的最后一个节点,也就是下边的状态。

1    2    3    4
     ^         ^
    slow      fast

或者可以添加一个pre指针,让它一直指向 slow 的前一个即可。

ListNode prev = null, slow = head, fast = head;
while (fast != null && fast.next != null) {
    prev = slow;
    slow = slow.next;
    fast = fast.next.next;
}
  • 时间复杂度:O(nlogn)
  • 空间复杂度:O(logn)

通过递归实现链表归并排序:

  • 分割 cut 环节: 找到当前链表中点,并从中点将链表断开;
    • 快慢双指针法,奇数个节点找到中点,偶数个节点找到中心左边的节点。
    • 找到中点 slow 后,执行 slow.next = None 将链表切断。
    • 递归分割时,输入当前链表左端点 head 和中心节点 slow 的下一个节点(因为链表是从 slow 切断的)。
    • cut递归终止条件: 当head.next == None时,说明只有一个节点了,直接返回此节点。
  • 合并 merge 环节: 将两个排序链表合并,转化为一个排序链表。
    • 双指针法合并,建立 dummyhead 作为头部。
    • 设置两指针 left, right 分别指向两链表头部,比较两指针处节点值大小,由小到大加入合并链表头部,指针交替前进,直至添加完两个链表。
    • 返回 dummy.next(它指向链表头部)。
    • 时间复杂度 O(l + r)l, r 分别代表两个链表长度。
  • 当题目输入的 head == None 时,直接返回None。

在这里插入图片描述

public ListNode sortList(ListNode head) {
    return mergeSort(head);
}

private ListNode mergeSort(ListNode head) {
    if (head == null || head.next == null) {
        return head;
    }
    ListNode dummy = new ListNode(0);
    dummy.next = head;
    ListNode fast = dummy;
    ListNode slow = dummy;
    //快慢指针找中点
    while (fast != null && fast.next != null) {
        slow = slow.next;
        fast = fast.next.next;
    }
    ListNode head2 = slow.next;
    slow.next = null;
    head = mergeSort(head);
    head2 = mergeSort(head2);
    return merge(head, head2);

}

private ListNode merge(ListNode head1, ListNode head2) {
    ListNode dummy = new ListNode(0);
    ListNode tail = dummy;
    while (head1 != null && head2 != null) {
        if (head1.val < head2.val) {
            tail.next = head1;
            tail = tail.next;
            head1 = head1.next;
        } else {
            tail.next = head2;
            tail = tail.next;
            head2 = head2.next;
        }
    }
    if (head1 != null) {
        tail.next = head1;
    }
    if (head2 != null) {
        tail.next = head2;
    }
    return dummy.next;
}
解法二 归并排序 迭代 自底向上

对于非递归的归并排序,需要使用迭代的方式替换cut环节:

  • 解法一的cut环节本质上是通过二分法得到链表最小节点单元,再通过多轮合并得到排序结果。
  • 每一轮合并merge操作针对的单元都有固定长度intv,例如:
    • 第一轮合并时intv = 1,即将整个链表切分为多个长度为1的单元,两两排序合并后已排序单元长度为2。
    • 第二轮合并时intv = 2,即将整个链表切分为多个长度为2的单元,两两排序合并后已排序单元长度为4。
    • 以此类推,直到单元长度intv >= 链表长度,代表已经排序完成。
  • 根据以上推论,我们可以仅根据intv计算每个单元边界,并完成链表的每轮排序合并,例如:
    • intv = 1时,将链表第1和第2节点排序合并,第3和第4节点排序合并,……。
    • intv = 2时,将链表第1-2和第3-4节点排序合并,第5-6和第7-8节点排序合并,……。
    • intv = 4时,将链表第1-4和第5-8节点排序合并,第9-12和第13-16节点排序合并,……。
  • 时间复杂度O(nlogn),空间复杂度O(1)

在这里插入图片描述

步骤:

  1. 统计链表长度length,用于通过判断intv < length判定是否完成排序;
  2. 额外声明一个节点dummy,作为头部后面接整个链表,用于:
    1. intv *= 2即切换到下一轮合并时,可通过dummy.next找到链表头部;
    2. 执行排序合并时,作为链表头部排序合并时的辅助头部;后面的合并排序可以将上次合并排序的尾部tail用做辅助节点。
  3. 在每轮intv下的合并流程:
    1. 根据intv找到合并单元1和单元2的头部h1, h2。由于链表长度可能不是2^n,需要考虑边界条件:
      1. 在找h2过程中,如果链表剩余元素个数少于intv,则无需合并环节,直接break,执行下一轮合并;
      2. h2存在,但以h2为头部的剩余元素个数少于intv,也执行合并环节,h2单元的长度为c2 = intv - i
    2. 合并长度为c1, c2h1, h2链表,其中:
      1. 合并完后,需要修改新的合并单元的尾部pre指针指向下一个合并单元头部h。(在寻找h1, h2环节中,h指针已经被移动到下一个单元头部)
      2. 合并单元尾部同时也作为下次合并的辅助头部pre
    3. h == None,代表此轮intv合并完成,跳出。
  4. 每轮合并完成后将单元长度×2,切换到下轮合并:intv *= 2
public class Solution {
private class MergeHelper {
        public ListNode newHead;
        public ListNode newTail;
}
public ListNode sortList(ListNode head) {
    if ( head == null || head.next == null) {
        return head;
    }
    
    ListNode dummyHeadOne = new ListNode(0);
    ListNode dummyHeadTwo = new ListNode(0);
    ListNode dummySortedHead = new ListNode(0);
    ListNode dummySortedLast = dummySortedHead;
    ListNode unvisitedNode = head;
    MergeHelper mergeRst = new MergeHelper();
    
    int listLength = 0;
    int level = 0;
    while ( unvisitedNode != null && unvisitedNode.next != null ) {
        unvisitedNode = addNode ( dummyHeadOne, unvisitedNode, 1<<level);
        unvisitedNode = addNode ( dummyHeadTwo, unvisitedNode, 1<<level);
        merge ( dummyHeadOne.next, dummyHeadTwo.next, mergeRst);
        dummySortedLast.next = mergeRst.newHead;
        dummySortedLast = mergeRst.newTail;
        listLength += 2;
    }
    if (unvisitedNode != null) {
        dummySortedLast.next = unvisitedNode;
        listLength ++;
    }
    level ++;
    
    while ( listLength > 1 << level) {
        dummySortedLast = dummySortedHead;
        unvisitedNode = dummySortedHead.next;
        while (unvisitedNode != null) {
            unvisitedNode = addNode ( dummyHeadOne, unvisitedNode, 1<<level);
            unvisitedNode = addNode ( dummyHeadTwo, unvisitedNode, 1<<level);
            merge ( dummyHeadOne.next, dummyHeadTwo.next, mergeRst);
            dummySortedLast.next = mergeRst.newHead;
            dummySortedLast = mergeRst.newTail;
        }
        level ++;
    }
    
    return dummySortedHead.next;
}

/* merge listOne and listTwo. 
Save the sorted list head into rst.newHead
Save the last node of the sorted list into rst.newTail
*/
private void merge (ListNode listOne, ListNode listTwo, MergeHelper rst) {
    ListNode dummyHead = new ListNode (0);
    ListNode lastNode = dummyHead;
    while (listOne != null && listTwo != null) {
        if ( listOne.val < listTwo.val ) {
            lastNode.next = listOne;
            listOne = listOne.next;
        } else {
            lastNode.next = listTwo;
            listTwo = listTwo.next;
        }
        lastNode = lastNode.next;
    }
    
    while (listOne != null) {
        lastNode.next = listOne;
        listOne = listOne.next;
        lastNode = lastNode.next;
    }
    while ( listTwo != null ) {
        lastNode.next = listTwo;
        listTwo = listTwo.next;
        lastNode = lastNode.next;
    }
    rst.newHead = dummyHead.next;
    rst.newTail = lastNode;
}

/*
 add at max #"count" nodes into "head" from "source"
 return the new position of source after adding.
*/
private ListNode addNode ( ListNode head, ListNode source, int count ) {
    while (count > 0 && source != null) {
        head.next = source;
        head = head.next;
        source = source.next;
        count --;
    }
    head.next = null;
    return source;
}}
解法三 快速排序
public ListNode sortList(ListNode head) {
    if (head == null) return null;
    
    ListNode pivot = head;
    head = head.next;
    pivot.next = null;
    if (head == null) return pivot;
    ListNode small = new ListNode(0);
    ListNode large = new ListNode(0);
    ListNode p = pivot;
    ListNode s = small;
    ListNode l = large;
    while (head!=null) {
        if (head.val < pivot.val) {
            s.next = head;
            s = s.next;
        } else if (head.val == pivot.val) {
            p.next = head;
            p = p.next;
        } else {
            l.next = head;
            l = l.next;
        }
        head = head.next;
    }
    l.next = null;
    s.next = null;
    p.next = null;
    ListNode ss = sortList(small.next);
    if (ss == null) {
        ss = pivot;    
    } else {
        ListNode sss = ss;
        while (sss.next!=null) {
            sss = sss.next;
        }
        sss.next = pivot;
    }
    p.next = sortList(large.next);
    return ss;
}

详细解法

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
根据,题目要求在O(n log n)的时间复杂度和常数级空间复杂度下对链表进行排序。根据提供的代码,可以使用归并排序来解决这个问题。代码中的sortList函数是递归函数,首先判断链表是否为空或者只有一个节点,如果是的话直接返回该链表。然后通过快慢指针找到链表的中点,将链表分成两半,再递归地对两个子链表进行排序。最后,使用merge函数将两个有序的子链表合并成一个有序的链表。merge函数通过比较两个链表节点的值来确定节点的顺序,然后将节点逐个连接起来。整个过程可以保证链表最终被排序。因此,可以使用该方法解决leetcode 148的问题。<span class="em">1</span><span class="em">2</span><span class="em">3</span> #### 引用[.reference_title] - *1* [leetcode 148 排序链表 C语言](https://blog.csdn.net/qq_42007287/article/details/104730970)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v92^chatsearchT3_1"}}] [.reference_item style="max-width: 33.333333333333336%"] - *2* [LeetCode148. Sort List 排序链表(C语言)](https://blog.csdn.net/wangqingchuan92/article/details/104037031)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v92^chatsearchT3_1"}}] [.reference_item style="max-width: 33.333333333333336%"] - *3* [LeetCode 148. Sort List](https://blog.csdn.net/smmyy022/article/details/82937283)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v92^chatsearchT3_1"}}] [.reference_item style="max-width: 33.333333333333336%"] [ .reference_list ]

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值