剑指offer II 077、078 归并排序(有待加强)

剑指 Offer II 077. 链表排序

题目描述

截屏2022-02-17 上午11.49.20

思路:

题目进阶要求 O ( n l o g n ) O(nlogn) O(nlogn) 的时间复杂度,那么这里正好复习一下排序算法的时间复杂度:

  • O ( n 2 ) O(n^2) O(n2) 级别:选择排序、插入排序、冒泡排序;
  • O ( n l o g n ) O(nlogn) O(nlogn) 级别:堆排序、快速排序、归并排序;

因此考虑堆排序、快速排序、归并排序中的一个。其中堆排序和快速排序都需要元素的随机访问,对于链表来说不合适。只能考虑归并排序。

方法一:

最简单想到的归并排序就是自顶向下归并排序。我们将链表从中间拆分成两段,然后将两段链表分别排序后再合并在一起。整个过程递归地进行,递归边界是当前链表为空或者只有一个节点,此时的链表自然是有序的。

拆分链表使用快慢指针方法,合并两个有序链表使用双指针方法。

时间复杂度 O ( n l o g n ) O(nlogn) O(nlogn) ,由于使用了递归,空间复杂度 O ( l o g n ) O(logn) O(logn)

class Solution {
    // 快慢指针法寻找链表中点,并从中点处断开,返回后半段链表的头节点
    ListNode* split(ListNode* head) {
        ListNode* slow = head, * fast = head;
        while (fast->next != nullptr && fast->next->next != nullptr) {
            slow = slow->next;
            fast = fast->next->next;
        }
        ListNode* second = slow->next;
        slow->next = nullptr;
        return second;
    }
    // 双指针法合并两条链表,使用虚拟头节点
    ListNode* merge(ListNode* head1, ListNode* head2) {
        ListNode* res = new ListNode();
        ListNode* cur = res;
        while (head1 != nullptr && head2 != nullptr) {
            if (head1->val <= head2->val) {
                cur->next = head1;
                head1 = head1->next;
            } else {
                cur->next = head2;
                head2 = head2->next;
            }
            cur = cur->next;
        }
        cur->next = head1 != nullptr ? head1 : head2;

        return res->next;
    }
public:
    ListNode* sortList(ListNode* head) {
        //自顶向下归并排序
        if (head == nullptr || head->next == nullptr) {
            return head;
        }
        ListNode* first = head;
        ListNode* second = split(head);
        first = sortList(first);
        second = sortList(second);

        return merge(first, second);
    }
};
方法二

题目进阶还要求在常数空间内完成排序操作,如何对方法一进行空间优化呢?方法一空间消耗在递归上,那就不用递归而改用迭代方法,这就是自底向上的归并。

看了半天题解,也不会自己写,算了,等后面再研究吧。

剑指 Offer II 078. 合并排序链表数组

题目描述

截屏2022-02-17 下午4.37.49

思路

可以有以下几种思路:

挨个合并

先拿lists[0]lists[1]合并,合并后的头节点放在lists[0]里,然后再拿lists[0]lists[2]合并,合并结果还是放在lists[0]里,以此类推。时间复杂度 O ( n 2 ) O(n^2) O(n2)

二分法(归并排序):

首先两两捉对合并链表,这样一轮合并完成后,就变成了 n 2 \frac{n}{2} 2n 个有序链表,并且它们的头节点分别存放在下标为0, 2, 4,...中;

再两两捉对合并链表,这样一轮合并完成后,就变成了 n 4 \frac{n}{4} 4n 个有序链表,并且它们的头节点分别存放在下标为0, 4,...中;

最终合并完成的链表头节点存放在 lists[0] 中。

时间复杂度 O ( n l o g n ) O(nlogn) O(nlogn)

class Solution {
    ListNode* merge(ListNode* head1, ListNode* head2) {
        ListNode* res = new ListNode();
        ListNode* cur = res;
        while (head1 != nullptr && head2 != nullptr) {
            if (head1->val <= head2->val) {
                cur->next = head1;
                head1 = head1->next;
            } else {
                cur->next = head2;
                head2 = head2->next;
            }
            cur = cur->next;
        }
        cur->next = head1 != nullptr ? head1 : head2;
        return res->next;
    }
public:
    ListNode* mergeKLists(vector<ListNode*>& lists) {
        int n = lists.size();   //共有n个有序链表
        if (n == 0) {
            return nullptr;
        }
        for (int step = 1; step < n; step *= 2) {
            for (int i = 0; i + step < n; i += step * 2) {
                ListNode* first = lists[i], * second = lists[i + step];
                lists[i] = merge(first, second);
            }
        }
        return lists[0];
    }
};
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 2
    评论
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值