欢迎光临
我们一直在努力

hot100_排序链表

1 题目

给你链表的头结点 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 = [] 输出:[]


2 题解

2.1 辅助数组

2.1.1 核心思想

一句话核心:把链表问题,转化为数组问题求解。

链表的软肋是不能随机访问、不好排序;数组的强项是支持随机访问、排序成熟高效。

所以我们采用迂回策略:

摘节点 → 数组排序 → 重新串成****链表

不改动节点数据、不新建节点,只改变「指针的连接顺序」。

2.1.2 代码

/**
* Definition for singly-linked list.
* struct ListNode {
* int val;
* ListNode *next;
* ListNode() : val(0), next(nullptr) {}
* ListNode(int x) : val(x), next(nullptr) {}
* ListNode(int x, ListNode *next) : val(x), next(next) {}
* };
*/

class Solution {
public:
ListNode* sortList(ListNode* head) {
vector<pair<int, ListNode*>> pil;
ListNode* cur = head;
while(cur) {
pil.push_back({cur->val, cur});
cur = cur->next;
}
sort(pil.begin(), pil.end(), [](pair<int, ListNode*>& p1, pair<int, ListNode*>p2) {
return p1.first < p2.first;
});
ListNode dummy = ListNode(0);
ListNode* tail = &dummy;
for(auto node: pil) {
tail->next = node.second;
tail = tail->next;
}
tail->next = nullptr;
return dummy.next;
}
};

2.1.3 复杂度

时间复杂度:

O

(

n

log

n

)

O(n\\log n)

O(nlogn),sort的时间;遍历链表两次

O

(

n

)

O(n)

O(n) 空间复杂度:

O

(

n

)

O(n)

O(n),vector 存了全部节点指针

2.2 自顶向下归并排序(递归版)

2.2.1 核心思想

  • 分割:快慢指针找到链表中点,切断链表拆成左右两部分
  • 左递归:递归对左子链表执行排序
  • 右递归:递归对右子链表执行排序
  • 合并:将排序完成的左右两条有序链表合并为一条,向上返回
  • 递归终止条件是基础保护:if(!head || !head->next) return head;,不属于这 4 行核心业务逻辑。

    2.2.2 代码

    /**
    * Definition for singly-linked list.
    * struct ListNode {
    * int val;
    * ListNode *next;
    * ListNode() : val(0), next(nullptr) {}
    * ListNode(int x) : val(x), next(nullptr) {}
    * ListNode(int x, ListNode *next) : val(x), next(next) {}
    * };
    */

    class Solution {
    public:
    ListNode* sortList(ListNode* head) {
    if(!head || !head->next) {
    return head;
    }
    ListNode* slow = head;
    ListNode* fast = head->next;
    while(fast && fast->next) {
    slow = slow->next;
    fast = fast->next->next;
    }
    ListNode* mid = slow->next;
    slow->next = nullptr;
    ListNode* left = sortList(head);
    ListNode* right = sortList(mid);
    return merge(left, right);
    }
    ListNode* merge(ListNode* l1, ListNode* l2) {
    ListNode dummy(0);
    ListNode* cur = &dummy;
    while(l1 && 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 ? l1 : l2;
    return dummy.next;
    }
    };

    2.2.3 复杂度

    时间复杂度:

    O

    (

    n

    log

    n

    )

    O(n\\log n)

    O(nlogn) 空间复杂度:

    O

    (

    log

    n

    )

    O(\\log n)

    O(logn)(递归调用栈)

    2.3 自底向上归并排序(迭代版)

    2.3.1 核心思想

  • 测长度:先遍历一遍统计链表总长度,决定需要合并几轮
  • 外层轮数:合并段长 seg 从 1 开始,每轮翻倍(1、2、4、8…),直到 seg ≥ length 覆盖整条链表
  • 内层合并:把链表按 seg 长度切成一段段,相邻两段 merge 成一个更大的有序段,每轮把整条链表重连一遍
  • 返回结果:所有轮次结束,整条链表完全有序,返回头节点
  • 辅助函数:cut(head, k) 截断前 k 个节点并返回下一段起点;merge(l1, l2) 合并两条有序链表。这两个不属于核心思想,但写代码必须要有。

    2.3.2 代码

    /**
    * Definition for singly-linked list.
    * struct ListNode {
    * int val;
    * ListNode *next;
    * ListNode() : val(0), next(nullptr) {}
    * ListNode(int x) : val(x), next(nullptr) {}
    * ListNode(int x, ListNode *next) : val(x), next(next) {}
    * };
    */

    class Solution {
    public:
    ListNode* sortList(ListNode* head) {
    if(!head || !head->next) {
    return head;
    }
    int length = 0;
    ListNode* cur = head;
    while(cur) {
    length++;
    cur = cur->next;
    }
    ListNode dummy(0);
    dummy.next = head;
    for(int seg = 1; seg < length; seg <<= 1) {
    ListNode* tail = &dummy;
    ListNode* cur = dummy.next;
    while(cur) {
    ListNode* left = cur;
    ListNode* right = cut(cur, seg);
    cur = cut(right, seg);
    tail->next = merge(left, right);
    while(tail->next) {
    tail = tail->next;
    }
    }
    }
    return dummy.next;
    }

    ListNode* cut(ListNode* head, int k) {
    while(k && head) {
    head = head->next;
    }
    if (!head) return nullptr;
    ListNode* next_start = head->next;
    head->next = nullptr;
    return next_start;
    }

    ListNode* merge(ListNode* l1, ListNode* l2) {
    ListNode dummy(0);
    ListNode* cur = &dummy;
    while(l1 && 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 ? l1 : l2;
    return dummy.next;
    }
    };

    2.3.3 复杂度

    时间复杂度:

    O

    (

    n

    log

    n

    )

    O(n\\log n)

    O(nlogn) 空间复杂度:

    O

    (

    1

    )

    O(1)

    O(1)

    2.4 三种算法对比

    方案核心思路时间复杂度空间复杂度代码难度核心特点
    辅助数组法 遍历链表收集节点指针,数组排序后重新串联节点,空间换简单 O(nlogn) O(n) 笔试速通首选;需要处理尾节点置空,否则链表成环,不满足进阶 O (1) 空间
    自顶向下归并(递归) 分治,快慢指针二分拆链表,递归排序左右子链表,最后合并有序链表;先拆后合 O(nlogn) O(logn)(递归栈) ⭐⭐ 面试最常考,代码简洁好写;递归栈占用空间,无法做到严格常数空间
    自底向上归并(迭代) 迭代,从长度为 1 的有序小段开始两两合并,段长不断翻倍直至整条链表有序;从小段到大段合并 O(nlogn) O(1) ⭐⭐⭐ 本题最优解,无递归,满足进阶 O (1) 空间;需要实现 cut 截断函数,逻辑稍复杂

    3 148. 排序链表 – 力扣(LeetCode)

    赞(0)
    未经允许不得转载:171主机测评 » hot100_排序链表
    分享到: 更多 (0)

    评论 抢沙发

    • 昵称 (必填)
    • 邮箱 (必填)
    • 网址