LeetCode 剑指 Offer II 排序 专题总结

  • 📚 博客主页:⭐️这是一只小逸白的博客鸭~⭐️
  • 👉 欢迎 关注❤️点赞👍收藏⭐️评论📝
  • 😜 小逸白正在备战实习,经常更新面试题LeetCode题解,欢迎志同道合的朋友互相交流~
  • 💙 若有问题请指正,记得关注哦,感谢~

往期文章 :

074. 合并区间

题目:

以数组 intervals 表示若干个区间的集合,其中单个区间为 intervals[i] = [starti, endi] 。请你合并所有重叠的区间,并返回一个不重叠的区间数组,该数组需恰好覆盖输入中的所有区间。

示例:

输入:intervals = [[1,3],[2,6],[8,10],[15,18]]
输出:[[1,6],[8,10],[15,18]]
解释:区间 [1,3] 和 [2,6] 重叠, 将它们合并为 [1,6].

提示:

  • 1 <= intervals.length <= 104
  • intervals[i].length == 2
  • 0 <= starti <= endi <= 104

思路:

快排 + 遍历
现按区间的开始时间进行排序
然后创建一个二维数组,先将第一个区间加入,而后遍历区间

  • 如果本区间结尾小于下一个区间开始,直接加入下一个区间
  • 否则 更新本区间的结束时间,取本区间与下一区间的结束值大值
class Solution {
public:
    vector<vector<int>> merge(vector<vector<int>>& intervals) {
        if(intervals.size() == 0) return {};
        //sort(intervals.begin(), intervals.end());
        quickSort(intervals, 0, intervals.size() - 1);
        vector<vector<int>> ans;
        ans.emplace_back(intervals[0]);
        for(int i = 1; i < intervals.size(); i++) {
            // 如果前一个数的结尾大于这个数的开头,就可以合并
            if(ans.back()[1] >= intervals[i][0]) {
                if(ans.back()[1] < intervals[i][1])
                    ans.back()[1] = intervals[i][1];
                // 否则跳过这个数
            }else {
                // 不能合并
                ans.emplace_back(intervals[i]);
            }
        }
        return ans;
    }
    // 快排
    void quickSort(vector<vector<int>>& arr, int l, int r) {
        if(l >= r) return ;
        int i = l, j = r, mid = arr[(l + r) >> 1][0];
        do{
            while(arr[i][0] < mid) i++;
            while(arr[j][0] > mid) j--;
            if(i <= j) 
                swap(arr[i++], arr[j--]);
        }while(i <= j);
        quickSort(arr, l, j);
        quickSort(arr, i, r);
    }
};

075. 数组相对排序

题目:

给定两个数组,arr1 和 arr2,

  • arr2 中的元素各不相同
  • arr2 中的每个元素都出现在 arr1 中
    对 arr1 中的元素进行排序,使 arr1 中项的相对顺序和 arr2 中的相对顺序相同。未在 arr2 中出现过的元素需要按照升序放在 arr1 的末尾。

示例:

输入:arr1 = [2,3,1,3,2,4,6,7,9,2,19], arr2 = [2,1,4,3,9,6]
输出:[2,2,2,1,4,3,3,9,6,7,19]

提示:

  • 1 <= arr1.length, arr2.length <= 1000
  • 0 <= arr1[i], arr2[i] <= 1000
  • arr2 中的元素 arr2[i] 各不相同
  • arr2 中的每个元素 arr2[i] 都出现在 arr1 中

思路:

将排序规则 arr2 加入map中,自定义sort排序,将规则改成

  • 在map中存在的为小值
  • 如果都在map中,则比较数组下标看谁小(map值)
  • 否则直接比较两个值
class Solution {
public:
    vector<int> relativeSortArray(vector<int>& arr1, vector<int>& arr2) {
        unordered_map<int, int> map;
        for(int i = 0; i < arr2.size(); i++)
            map[arr2[i]] = i;
        sort(arr1.begin(), arr1.end(), [&](int x, int y) {
            if(map.count(x)) {
                return map.count(y)? map[x] < map[y] : true;
            }else {
                return map.count(y)? false : x < y;
            }
        });
        return arr1;
    }
};

076. 数组中的第 k 大的数字

题目:

给定整数数组 nums 和整数 k,请返回数组中第 k 个最大的元素。
请注意,你需要找的是数组排序后的第 k 个最大的元素,而不是第 k 个不同的元素。

示例:

输入: [3,2,1,5,6,4] 和 k = 2
输出: 5

提示:

  • 1 <= k <= nums.length <= 104
  • -104 <= nums[i] <= 104

思路:

优先队列(大根堆)
K大 等价于 第n = nums.size() - k + 1 小,所以我们存储最小的 n 个元素,堆顶就是第k大元素
当堆达到 n + 1时 将堆顶移除,这样就可以保持将 大于 第k大的元素移除掉

class Solution {
public:
    int findKthLargest(vector<int>& nums, int k) {
        // 第k大就是第nums.size() - k + 1 小;
        // 小到大排序
        priority_queue<int, vector<int>, less<int>> pq;
        int n = nums.size() - k + 1;
        for(int i : nums) {
            pq.emplace(i);
            if(pq.size() > n) pq.pop();
        }
        return pq.top();
    }
};

077. 链表排序

题目:

给定链表的头结点 head ,请将其按 升序 排列并返回 排序后的链表 。

示例:

在这里插入图片描述
输入:head = [4,2,1,3]
输出:[1,2,3,4]

提示:

  • 链表中节点的数目在范围 [0, 5 * 104] 内
  • -105 <= Node.val <= 105

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

思路:

归并思想:找中点 + 递归成单个链表节点 + 合并链表
找中点时记得将中点的前一个与后面断开

class Solution {
public:
    // 找中点 + 递归成单个链表节点 + 合并链表
    ListNode* sortList(ListNode* head) {
        // 到只剩一个元素返回
        if(head == nullptr || head->next == nullptr)
            return head;
        // 数组分成两段并返回中点
        ListNode* head1 = head;
        ListNode* head2 = serachMid(head);
        head1 = sortList(head1);
        head2 = sortList(head2);
        // 合并并返回
        return merge(head1,head2);
    }
    // 找中点
    ListNode* serachMid(ListNode* head) {
        ListNode *slow = head, *fast = head;
        while(fast->next && fast->next->next) {
            slow = slow->next;
            fast = fast->next->next;
        }
        ListNode* mid = slow->next;
        slow->next = nullptr;
        return mid;
    }
    // 合并链表
    ListNode* merge(ListNode* head1, ListNode* head2) {
        ListNode* dummy = new ListNode(-1);
        ListNode* cur = dummy;
        while(head1 && head2) {
            if(head1->val <= head2->val) {
                cur->next = head1;
                head1 = head1->next;
            }else {
                cur->next = head2;
                head2 = head2->next;
            }
            cur = cur->next;
        }
        if(head1) cur->next = head1;
        if(head2) cur->next = head2;
        return dummy->next;
    }
};

078. 合并排序链表(困难)

题目:

给定一个链表数组,每个链表都已经按升序排列。
请将所有链表合并到一个升序链表中,返回合并后的链表。

示例:

输入:lists = [[1,4,5],[1,3,4],[2,6]]
输出:[1,1,2,3,4,4,5,6]
解释:链表数组如下:
[
1->4->5,
1->3->4,
2->6
]
将它们合并到一个有序链表中得到。
1->1->2->3->4->4->5->6

提示:

  • k == lists.length
  • 0 <= k <= 10^4
  • 0 <= lists[i].length <= 500
  • -10^4 <= lists[i][j] <= 10^4
  • lists[i] 按 升序 排列
  • lists[i].length 的总和不超过 10^4

思路:

优先队列(小根堆)的两个思路:
首先我用的方法一,然后一直报堆栈溢出错误,后来用的方法二,然后发现方法一是可能会发生循环
解决:在加入堆后,将本节点的next节点去掉

  • 方法一:先将所有节点入队列(需重写优先队列的排序方法),然后遍历队列将所有节点连接起来
    也可以将节点值直接加入队列(不需要重写排序方法),但是连接节点时需要重新创建节点
  • 方法二:将每个链表头加入堆,然后重新连接节点后将次节点的下一个节点重新入堆,思想跟 LeetCode 剑指 Offer II 061. 和最小的 k 个数对很像,可以参考我的题解 LeetCode 剑指 Offer II 堆 专题总结

方法一、小根堆 + 全部入堆

class Solution {
public:
    // 小根堆 + 全部入堆
    ListNode* mergeKLists(vector<ListNode*>& lists) {
        auto cmp = [&](ListNode* node1, ListNode* node2){
            return node1->val > node2->val;
        };
        priority_queue<ListNode*, vector<ListNode*>, decltype(cmp)> pq(cmp);
        ListNode* dummy = new ListNode(-1);
        ListNode* ans = dummy;
        for(auto& head : lists) {
            ListNode* cur = head;
            while(cur) {
                pq.emplace(cur);
                // 加入后将后节点去掉  不然可能循环
                ListNode* pre = cur;
                cur = cur->next;
                pre->next = nullptr;
            }
        }
        while(!pq.empty()) {
            ListNode* node = pq.top();
            pq.pop();
            cout << node->val << endl;
            ans->next = node;
            ans = ans->next;
        }
        return dummy->next;
    }
};

方法二、小根堆 + 加入子链表头结点

class Solution {
public:
    // 小根堆 + 子链表头结点先入堆
    ListNode* mergeKLists(vector<ListNode*>& lists) {
        auto cmp = [&](ListNode* node1, ListNode* node2){
            return node1->val > node2->val;
        };
        priority_queue<ListNode*, vector<ListNode*>, decltype(cmp)> pq(cmp);
        for(auto& head : lists) {
            // 记得头指针不能为空
            if(head)
                pq.push(head);
        }
        ListNode* dummy = new ListNode(-1);
        ListNode* ans = dummy;
        while(!pq.empty()) {
            ListNode* node = pq.top();
            pq.pop();
            ans->next = node;
            // 再改行节点后面点入堆
            if(node->next != nullptr) pq.push(node->next);
            ans = ans->next;
        }
        return dummy->next;
    }
};
  • 15
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 25
    评论
评论 25
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

一只小逸白

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值