【经典算法】LeetCode 148. 排序链表(Java/C/Python3实现含注释说明,中等)

题目描述

  • 标签(题目类型):链表、排序

给你链表的头结点 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, 5 * 104] 内
-105 <= Node.val <= 105

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

原题:148. 排序链表

思路及实现

方式一:归并排序(续)

C语言版本(续)

// 释放链表内存
void freeList(struct ListNode* head) {
    struct ListNode *cur = head;
    while (cur != NULL) {
        struct ListNode *temp = cur;
        cur = cur->next;
        free(temp);
    }
}

struct ListNode* sortList(struct ListNode* head) {
    // ... 省略之前的代码 ...
    
    // 释放dummy节点内存
    free(dummy);
    
    return result;
}

说明:

  • freeList 函数用于释放链表占用的内存。
  • sortList 函数最后,释放了 dummy 节点占用的内存,并返回排序后的链表头节点 result
Python3版本
# Definition for singly-linked list.
class ListNode:
    def __init__(self, val=0, next=None):
        self.val = val
        self.next = next

class Solution:
    def sortList(self, head: ListNode) -> ListNode:
        if not head or not head.next:
            return head
        
        # 使用快慢指针找到链表中间节点
        slow, fast = head, head
        while fast.next and fast.next.next:
            slow = slow.next
            fast = fast.next.next
        
        # 分割链表
        mid = slow.next
        slow.next = None
        
        # 递归对左右两部分链表排序
        left = self.sortList(head)
        right = self.sortList(mid)
        
        # 合并两个已排序链表
        return self.merge(left, right)
    
    def merge(self, l1: ListNode, l2: ListNode) -> ListNode:
        dummy = ListNode(0)
        cur = dummy
        
        while l1 and l2:
            if l1.val < l2.val:
                cur.next = l1
                l1 = l1.next
            else:
                cur.next = l2
                l2 = l2.next
            cur = cur.next
        
        # 处理剩余节点
        cur.next = l1 if l1 is not None else l2
        
        return dummy.next

说明:

  • Python版本的实现与Java版本类似,但语法有所不同。
  • Python中的链表节点定义使用了类,并且链表操作更为简洁。
Golang版本
package main

import "fmt"

type ListNode struct {
    Val  int
    Next *ListNode
}

func sortList(head *ListNode) *ListNode {
    if head == nil || head.Next == nil {
        return head
    }
    
    slow, fast := head, head
    for fast.Next != nil && fast.Next.Next != nil {
        slow = slow.Next
        fast = fast.Next.Next
    }
    
    mid := slow.Next
    slow.Next = nil
    
    left := sortList(head)
    right := sortList(mid)
    
    return merge(left, right)
}

func merge(l1, 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
    }
    
    cur.Next = l1
    if l1 == nil {
        cur.Next = l2
    }
    
    return dummy.Next
}

func main() {
    // 示例链表: 4 -> 2 -> 1 -> 3
    head := &ListNode{Val: 4}
    head.Next = &ListNode{Val: 2}
    head.Next.Next = &ListNode{Val: 1}
    head.Next.Next.Next = &ListNode{Val: 3}
    
    sortedHead := sort# 排序链表

* 标签(题目类型):链表、排序

# 思路及实现

## 方式一:归并排序(续)

### C语言版本(续)

```c
// 释放链表内存
void freeList(struct ListNode* head) {
    struct ListNode *cur = head;
    while (cur != NULL) {
        struct ListNode *temp = cur;
        cur = cur->next;
        free(temp);
    }
}

struct ListNode* sortList(struct ListNode* head) {
    // ... 省略之前的代码 ...
    
    // 释放dummy节点内存
    free(dummy);
    
    return result;
}

说明:

  • freeList 函数用于释放链表占用的内存。
  • sortList 函数最后,释放了 dummy 节点占用的内存,并返回排序后的链表头节点 result
Python3版本
# Definition for singly-linked list.
class ListNode:
    def __init__(self, val=0, next=None):
        self.val = val
        self.next = next

class Solution:
    def sortList(self, head: ListNode) -> ListNode:
        if not head or not head.next:
            return head
        
        # 使用快慢指针找到链表中间节点
        slow, fast = head, head
        while fast.next and fast.next.next:
            slow = slow.next
            fast = fast.next.next
        
        # 分割链表
        mid = slow.next
        slow.next = None
        
        # 递归对左右两部分链表排序
        left = self.sortList(head)
        right = self.sortList(mid)
        
        # 合并两个已排序链表
        return self.merge(left, right)
    
    def merge(self, l1: ListNode, l2: ListNode) -> ListNode:
        dummy = ListNode(0)
        cur = dummy
        
        while l1 and l2:
            if l1.val < l2.val:
                cur.next = l1
                l1 = l1.next
            else:
                cur.next = l2
                l2 = l2.next
            cur = cur.next
        
        # 处理剩余节点
        cur.next = l1 if l1 is not None else l2
        
        return dummy.next

说明:

  • Python版本的实现与Java版本类似,但语法有所不同。
  • Python中的链表节点定义使用了类,并且链表操作更为简洁。
Golang版本
package main

import "fmt"

type ListNode struct {
    Val  int
    Next *ListNode
}

func sortList(head *ListNode) *ListNode {
    if head == nil || head.Next == nil {
        return head
    }
    
    slow, fast := head, head
    for fast.Next != nil && fast.Next.Next != nil {
        slow = slow.Next
        fast = fast.Next.Next
    }
    
    mid := slow.Next
    slow.Next = nil
    
    left := sortList(head)
    right := sortList(mid)
    
    return merge(left, right)
}

func merge(l1, 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
    }
    
    cur.Next = l1
    if l1 == nil {
        cur.Next = l2
    }
    
    return dummy.Next
}

func main() {
    // 示例链表: 4 -> 2 -> 1 -> 3
    head := &ListNode{Val: 4}
    head.Next = &ListNode{Val: 2}
    head.Next.Next = &ListNode{Val: 1}
    head.Next.Next.Next = &ListNode{Val: 3}
    
    sortedHead := List(head)
    
    // 打印排序后的链表
    current := sortedHead
    for current != nil {
        fmt.Print(current.Val, " ")
        current = current.Next
    }
}

说明:

  • Golang版本的实现也使用了归并排序的思想,并且遵循了与C语言和Python相似的结构。
  • sortList 函数对链表进行递归分割和排序,merge 函数合并两个已排序链表。
  • main 函数创建了一个示例链表,调用 sortList 函数进行排序,并打印排序后的结果。

方式二:插入排序

思路

除了归并排序外,也可以使用插入排序对链表进行排序。插入排序的基本思想是将链表视作一个数组,依次遍历链表节点,将每个节点插入到已排序部分的正确位置。

由于链表的插入操作涉及到修改节点的 next 指针,插入排序对于链表来说是一种可行的排序方法。但需要注意的是,插入排序在处理大规模数据时性能可能不如归并排序。

以下是使用插入排序对链表进行排序的示例代码(这里以Python为例):

代码实现

Java版本

class ListNode {
    int val;
    ListNode next;
    ListNode(int x) { val = x; }
}

public class Solution {
    public ListNode insertionSortList(ListNode head) {
        if (head == null || head.next == null) {
            return head;
        }
        
        ListNode dummy = new ListNode(0); // 哑节点
        dummy.next = head;
        ListNode cur = head.next;
        
        while (cur != null) {
            if (cur.val < head.val) {
                // 如果当前节点值小于头节点值,则需要将其插入到哑节点之后
                ListNode temp = cur.next;
                cur.next = dummy.next;
                dummy.next = cur;
                cur = temp;
            } else {
                // 否则在已排序的链表中查找插入位置
                ListNode pre = head;
                while (pre.next != cur && pre.next.val < cur.val) {
                    pre = pre.next;
                }
                // 插入节点
                ListNode temp = cur.next;
                cur.next = pre.next;
                pre.next = cur;
                cur = temp;
            }
        }
        
        return dummy.next;
    }
}
C版本
#include <stdlib.h>

typedef struct ListNode {
    int val;
    struct ListNode *next;
} ListNode;

ListNode* insertionSortList(ListNode* head) {
    if (!head || !head->next) {
        return head;
    }
    
    ListNode dummy = {0, head}; // 哑节点
    ListNode *cur = head->next;
    
    while (cur) {
        if (cur->val < head->val) {
            // 如果当前节点值小于头节点值,则需要将其插入到哑节点之后
            ListNode *temp = cur->next;
            cur->next = dummy.next;
            dummy.next = cur;
            cur = temp;
        } else {
            // 否则在已排序的链表中查找插入位置
            ListNode *pre = head;
            while (pre->next != cur && pre->next->val < cur->val) {
                pre = pre->next;
            }
            // 插入节点
            ListNode *temp = cur->next;
            cur->next = pre->next;
            pre->next = cur;
            cur = temp;
        }
    }
    
    return dummy.next;
}
Python3版本
class ListNode:
    def __init__(self, val=0, next=None):
        self.val = val
        self.next = next

class Solution:
    def insertionSortList(self, head: ListNode) -> ListNode:
        # 创建一个哑节点作为已排序部分的起始点
        dummy = ListNode(0)
        cur = head
        
        while cur:
            # 创建一个临时节点来保存当前节点
            temp = cur
            cur = cur.next
            
            # 插入操作
            pre = dummy
            while pre.next and pre.next.val < temp.val:
                pre = pre.next
            
            temp.next = pre.next
            pre.next = temp
        
        return dummy.next

说明:

  • insertionSortList 函数实现了插入排序算法,其中 dummy 节点用于简化插入操作。
  • 遍历链表中的每个节点,依次将它们插入到已排序部分的正确位置。
  • 插入操作通过比较 temp 节点的值和已排序部分节点的值来找到插入点,并修改相应的 next 指针。
    当然可以,以下是使用Java、C和Go语言实现链表插入排序算法的示例代码:
Go版本
package main

import "fmt"

type ListNode struct {
    Val  int
    Next *ListNode
}

func insertionSortList(head *ListNode) *ListNode {
    if head == nil || head.Next == nil {
        return head
    }
    
    dummy := &ListNode{Val: 0, Next: head} // 哑节点
    cur := head.Next
    
    for cur != nil {
        if cur.Val < head.Val {
            // 如果当前节点值小于头节点值,则需要将其插入到哑节点之后
            nextNode := cur.Next
            cur.Next = dummy.Next
            dummy.Next = cur
            cur = nextNode
        } else {
            // 否则在已排序的链表中查找插入位置
            pre := head
            for pre.Next != cur && pre.Next.Val < cur.Val {
                pre = pre.Next
            }
            // 插入节点
            nextNode := cur.Next
            cur.Next = pre.Next
            pre.Next = cur
            cur = nextNode
        }
    }
    
    return dummy.Next
}

func main() {
    // 示例链表构建与排序
    // ...
    // 打印排序后的链表
    // ...
}

在以上三个版本的插入排序实现中,我们都使用了哑节点(dummy node)来简化插入操作。哑节点是一个特殊的节点,它的值不重要,主要用于作为已排序部分的起始点,这样我们可以避免处理边界情况,如当需要插入的节点是头节点时。

注意,这些插入排序的链表实现时间复杂度为O(n^2),对于大规模数据来说可能不够高效。在实际应用中,如果链表

复杂度分析

  • 归并排序的时间复杂度为 O(n log n),空间复杂度为 O(log n)(递归栈空间),其中 n 是链表长度。
  • 插入排序的时间复杂度为 O(n^2),空间复杂度为 O(1)(不考虑递归栈空间)。

在实际应用中,如果链表长度很大,通常推荐使用归并排序。如果链表长度较小,或者对空间复杂度有严格要求,可以考虑使用插入排序。

总结

方式优点缺点时间复杂度空间复杂度
插入排序链表实现简单直观时间复杂度较高,为O(n^2)O(n^2)O(1)
归并排序链表时间复杂度较低,为O(n log n)需要额外空间进行递归或合并操作O(n log n)O(n) 或 O(log n)(原地归并)

注意:这里的空间复杂度是相对于链表本身大小n而言的。插入排序是原地排序,不需要额外空间(除了常数级别的空间),所以空间复杂度为O(1)。归并排序通常需要递归调用栈空间,或者额外的数组空间用于合并操作,因此空间复杂度为O(n)或O(log n)(如果采用原地归并技术)。

相似题目

相似题目难度链接
合并两个有序链表中等力扣:合并两个有序链表
删除链表的倒数第N个节点中等力扣:删除链表的倒数第N个节点
旋转链表中等力扣:旋转链表
链表中的节点每k个一组翻转困难力扣:链表中的节点每k个一组翻转

这些题目涵盖了链表操作的多个方面,从简单的合并、删除操作到复杂的翻转和排序算法,对于理解和掌握链表数据结构非常有帮助。通过解决这些题目,可以加深对链表操作的理解,并提升算法设计和实现的能力。

  • 39
    点赞
  • 15
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值