题目描述
- 标签(题目类型):链表、排序
给你链表的头结点 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个一组翻转 |
这些题目涵盖了链表操作的多个方面,从简单的合并、删除操作到复杂的翻转和排序算法,对于理解和掌握链表数据结构非常有帮助。通过解决这些题目,可以加深对链表操作的理解,并提升算法设计和实现的能力。