LeetCode148 排序链表

94 阅读4分钟

leetcode.cn/problems/so…

image.png

解法一:排序法

利用数组存储所有节点的val,排好序后生成一条新链表即可

/**
 * Definition for singly-linked list.
 * type ListNode struct {
 *     Val int
 *     Next *ListNode
 * }
 */
func sortList(head *ListNode) *ListNode {
    if head == nil || head.Next == nil{
        return head
    }
    // 将元素加入到数组中排好序
    nums := make([]int, 0)
    cur := head
    for cur != nil{
        nums = append(nums, cur.Val)
        cur = cur.Next
    }
    sort.Slice(nums, func(i, j int) bool{
        return nums[i] < nums[j]
    })
    // 生成结果链表
    res := &ListNode{Val:nums[0]}
    cur = res
    for _, v := range nums[1:]{
        cur.Next = &ListNode{Val:v}
        cur = cur.Next
    }
    return res
}

但是这个方法,申请了额外另一整个链表还有对应长度的数组的空间,空间复杂度太高了。

解法二:归并排序

归并的关键就是分治思想,需要寻找子问题结构,然后递归。

对原链表一分为二,分开为两段更短的链表,然后对这两个更短的链表进行排序,然后再合并两个有序子链表就得到答案了。

这时候,我们发现,对这两个更短链表的排序,就又回到原问题了,说明这就是子问题结构,可以用递归!

可以对子链表再切分,直到最后只剩下一个节点就无需排序直接返回排序结果,这就是递归退出条件。

/**
 * Definition for singly-linked list.
 * type ListNode struct {
 *     Val int
 *     Next *ListNode
 * }
 */
func sortList(head *ListNode) *ListNode {
    if head == nil || head.Next == nil{
        return head
    }
    l1, l2 := splitList(head)
    sortedL1 := sortList(l1)
    sortedL2 := sortList(l2)
    res := mergeTwoList(sortedL1, sortedL2)
    return res
}

func splitList(head *ListNode) (*ListNode, *ListNode){
    // 找到链表中点,并断开其与下一节点的连接
    pre, slow, fast := head, head, head
    for fast != nil && fast.Next != nil { // 链表长度为奇数或偶数的两种退出条件
        pre = slow
        slow = slow.Next
        fast = fast.Next.Next
    }
    pre.Next = nil // 断开 slow 的前一个节点和 slow 的连接
    return head, slow
}

func mergeTwoList(l1 *ListNode, l2 *ListNode) *ListNode{
    dummy := &ListNode{}
    cur := dummy
    for l1 != nil && l2 != nil{
        if l1.Val < l2.Val{
            cur.Next = l1
            l1 = l1.Next
        }else{
            cur.Next = l2
            l2 = l2.Next
        }
        cur = cur.Next
    }
    if l1 == nil{
        cur.Next = l2
    }
    if l2 == nil{
        cur.Next = l1
    }
    return dummy.Next
}
  • 时间复杂度:O(n*logn)
  • 空间复杂度:O(logn)

实际上,用了递归需要O(logn)的栈开销

好在所有的递归基本上都可以转为迭代,进一步降低空间复杂度

前面递归思想是自顶向下,改成自底向上,就可将空间复杂度优化成O(1)

具体步骤:

  1. 遍历链表,获取链表长度length。
  2. 初始化步长step=1。
  3. 循环直到step≥length。
  4. 每轮循环,从链表头节点开始。
  5. 分割出两段长为step的链表,合并,把合并后的链表插到新链表的末尾。重复该步骤,直到链表遍历完毕。
  6. 把step扩大一倍。回到第4步。
/**
 * Definition for singly-linked list.
 * type ListNode struct {
 *     Val int
 *     Next *ListNode
 * }
 */
func sortList(head *ListNode) *ListNode {
    if head == nil || head.Next == nil{
        return head
    }
    length := getListLength(head) // 获取链表长度
    dummy := ListNode{Next: head} // 用哨兵节点简化代码逻辑
    // step 为步长,即参与合并的链表长度
    for step := 1; step < length; step *= 2 { 
        newListTail := &dummy // 新链表的末尾
        cur := dummy.Next // 每轮循环的起始节点
        for cur != nil {
            // 从 cur 开始,分割出两段长为 step 的链表,头节点分别为 head1 和 head2
            head1 := cur
            head2 := splitList(head1, step)
            cur = splitList(head2, step) // 下一轮循环的起始节点
            // 合并两段长为 step 的链表
            head, tail := mergeTwoLists(head1, head2)
            // 合并后的头节点 head,插到 newListTail 的后面
            newListTail.Next = head
            newListTail = tail // tail 现在是新链表的末尾
        }
    }
    return dummy.Next
}

// 获取链表长度
func getListLength(head *ListNode) (length int) {
    for head != nil {
        length++
        head = head.Next
    }
    return
}

// 分割链表
// 如果链表长度 <= size,不做任何操作,返回空节点
// 如果链表长度 > size,把链表的前 size 个节点分割出来(断开连接),并返回剩余链表的头节点
func splitList(head *ListNode, size int) *ListNode {
    // 先找到 nextHead 的前一个节点
    cur := head
    for i := 0; i < size-1 && cur != nil; i++ {
        cur = cur.Next
    }

    // 如果链表长度 <= size
    if cur == nil || cur.Next == nil {
        return nil // 不做任何操作,返回空节点
    }

    nextHead := cur.Next
    cur.Next = nil // 断开 nextHead 的前一个节点和 nextHead 的连接
    return nextHead
}

// 返回合并后的链表的头节点和尾节点
func mergeTwoLists(list1, list2 *ListNode) (head, tail *ListNode) {
    dummy := ListNode{} // 用哨兵节点简化代码逻辑
    cur := &dummy // cur 指向新链表的末尾
    for list1 != nil && list2 != nil {
        if list1.Val < list2.Val {
            cur.Next = list1 // 把 list1 加到新链表中
            list1 = list1.Next
        } else { // 注:相等的情况加哪个节点都是可以的
            cur.Next = list2 // 把 list2 加到新链表中
            list2 = list2.Next
        }
        cur = cur.Next
    }
    // 拼接剩余链表
    if list1 != nil {
        cur.Next = list1
    } else {
        cur.Next = list2
    }

    for cur.Next != nil {
        cur = cur.Next
    }
    // 循环结束后,cur 是合并后的链表的尾节点
    return dummy.Next, cur
}