【Leetcode034】排序链表

148、排序链表

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

进阶:

  • 你可以在 O(n log n) 时间复杂度和常数级空间复杂度下,对链表进行排序吗?

在这里插入图片描述

前言

在这里插入图片描述

方法一:自顶向下的归并排序

1.1 思路分析

要求用O(nlogn)的时间复杂度,排序算法中的归并排序、快速排序和堆排序时间复杂度是O(nlogn)。这道题适用归并排序,可以将链表切分成子序列,递归方式进行排序和合并。

递归的结束条件是head节点为空,或只有一个节点。将两个有序链表合并可以用【21、合并两个有序链表】。

在这里插入图片描述

1.2 代码实现

/**
 * Definition for singly-linked list.
 * type ListNode struct {
 *     Val int
 *     Next *ListNode
 * }
 */
func sortList(head *ListNode) *ListNode {
    return sort(head, nil)
}

// 合并两个有序链表
func merge(head1, head2 *ListNode) *ListNode{
    res := &ListNode{}
    ans := res
    for head1 != nil && head2 !=nil{
        if head1.Val <= head2.Val{
            ans.Next = head1
            head1 = head1.Next
        }else{
            ans.Next = head2
            head2 = head2.Next
        }
        ans = ans.Next
    }
    if head1==nil{
        ans.Next = head2
    }
    if head2==nil{
        ans.Next = head1
    }
    return res.Next
}


// 排序
func sort(head *ListNode, tail *ListNode) *ListNode{
    if head == nil{
        return head
    }
    if head.Next == tail{   // 把中间节点变为尾节点
        head.Next = nil
        return head
    }

    l, r := head, head
    for r != tail{
        l = l.Next
        r = r.Next
        if r != tail{
            r = r.Next
        }
    }
    mid := l
    return merge(sort(head, mid), sort(mid, tail))

}

1.3 测试结果

在这里插入图片描述

1.4 复杂度

  • 时间复杂度:O(nlogn),其中 n 是链表的长度。
  • 空间复杂度:O(logn),其中 n 是链表的长度。空间复杂度主要取决于递归调用的栈空间。

方法二:自底向上的归并排序

2.1 思路分析

在这里插入图片描述

2.2 代码实现

/**
 * Definition for singly-linked list.
 * type ListNode struct {
 *     Val int
 *     Next *ListNode
 * }
 */
func sortList(head *ListNode) *ListNode {
    //  空链表
    if head == nil{
        return head
    }
    // 1. 获取链表长度
    length := 0
    for node:=head; node!=nil; node=node.Next{
        length++
    }
    // 2. 初始化,引入dummyNode
    dummyNode := &ListNode{Next:head}
    // 3. 每次将链表拆分成若干个长度为subLen的子链表 , 并按照每两个子链表一组进行合并
    for subLength:=1; subLength<length; subLength <<=1{ 
        // subLen每次左移一位(即sublen = sublen*2) PS:位运算对CPU来说效率更高
        prev, cur := dummyNode, dummyNode.Next  // curr用于记录拆分链表的位置
        for cur != nil{     // 如果链表没有被拆完
            // 3.1 拆分subLen长度的链表1
            head1 := cur    // 第一个链表的头 即 curr初始的位置
            for i:=1; i<subLength && cur!=nil && cur.Next!=nil; i++{ // 拆分出长度为subLen的链表1
                cur = cur.Next
            }

            // 3.2 拆分subLen长度的链表2
            head2 := cur.Next   // 第二个链表的头  即 链表1尾部的下一个位置
            cur.Next = nil      // 断开第一个链表和第二个链表的链接
            cur = head2         // 第二个链表头 重新赋值给curr

            for i:=1; i<subLength && cur!=nil && cur.Next!=nil; i++{ // 再拆分出长度为subLen的链表2
                cur = cur.Next
            }
            // 3.3 再次断开 第二个链表最后的next的链接
            var next *ListNode
            if cur != nil{
                next = cur.Next    // next用于记录 拆分完两个链表的结束位置
                cur.Next = nil      // 断开连接
            }

            // 3.4 合并两个subLen长度的有序链表
            merged := merge(head1, head2)
            prev.Next = merged      // prev.next 指向排好序链表的头
            for prev.Next != nil{
                prev = prev.Next
            }

            cur = next  // next用于记录 拆分完两个链表的结束位置
        }
    }
    // 返回新排好序的链表
    return dummyNode.Next
}

// 合并两个有序链表
func merge(head1, head2 *ListNode) *ListNode{
    res := &ListNode{}
    ans := res
    for head1 != nil && head2 !=nil{
        if head1.Val <= head2.Val{
            ans.Next = head1
            head1 = head1.Next
        }else{
            ans.Next = head2
            head2 = head2.Next
        }
        ans = ans.Next
    }
    if head1==nil{
        ans.Next = head2
    }
    if head2==nil{
        ans.Next = head1
    }
    return res.Next
}

2.3 测试结果

在这里插入图片描述

2.4 复杂度

  • 时间复杂度:O(nlogn),其中 n 是链表的长度。
  • 空间复杂度:O(1)。
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值