【算法】多路归并算法

一、合并 K 个升序链表

题目链接

题目描述

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

示例 1:

输入: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

示例 2:

输入:lists = []
输出:[]

示例 3:

输入:lists = [[]]
输出:[]

思路分析
先思考如果两个升序链表该如何排序成一个链表?
可以用双指针把两个链表归并排序。
多个链表的话可以循环两两合并,最终合并成一个链表。但是这样效率太低。
可以使用优先级队列合并
维护当前每个链表没有被合并的元素的最前面一个放入小堆中,自定义比较排序,每次取出堆顶的链表,把该链表的头节点链接到排序链表中,然后把下一个节点再push进优先级队列中,如果是空节点就跳过。

priority_queue自定义比较函数

1️⃣ 使用仿函数

struct cmp
{
    
    
    bool operator()(ListNode* h1, ListNode* h2)
    {
    
    
        return h1->val > h2->val;
    }
};

priority_queue<ListNode*, vector<ListNode*>, cmp> q;

2️⃣ 使用函数指针

static bool cmp(ListNode* h1, ListNode* h2)
{
    
    
    return h1->val > h2->val;
}

priority_queue<ListNode*, vector<ListNode*>, decltype(&cmp)> q(&cmp);

这里要注意如果是在类里面定义优先级队列要定义在函数体内。
3️⃣ 使用lambda表达式

auto cmp = [](const ListNode* h1, const ListNode* h2){
    
    
    return h1->val > h2->val;
};
priority_queue<ListNode*, vector<ListNode*>, decltype(cmp)> q(cmp);

代码如下:

class Solution {
    
    
public:
    struct cmp
    {
    
    
        bool operator()(ListNode* h1, ListNode* h2)
        {
    
    
            return h1->val > h2->val;
        }
    };

    priority_queue<ListNode*, vector<ListNode*>, cmp> q;

    ListNode* mergeKLists(vector<ListNode*>& lists) {
    
    
        int n = lists.size();
        for(int i = 0; i < n; i++)
        {
    
    
            if(lists[i])
            q.push(lists[i]);
        }
        ListNode* head = new ListNode;
        ListNode* tail = head;
        while(q.size())
        {
    
    
            tail->next = q.top();
            ListNode* cur = nullptr;
            if(q.top())
            {
    
    
                cur = q.top()->next;
                tail = tail->next;
            }
            q.pop();
            if(cur) q.push(cur);
        }
        return head->next;
    }
};

二、有序矩阵中第 K 小的元素

题目链接

题目描述

给你一个 n x n 矩阵 matrix ,其中每行和每列元素均按升序排序,找到矩阵中第 k 小的元素。
请注意,它是 排序后 的第 k 小元素,而不是第 k 个 不同 的元素。
你必须找到一个内存复杂度优于 O(n2) 的解决方案。

示例 1:

输入:matrix = [[1,5,9],[10,11,13],[12,13,15]], k = 8
输出:13
解释:矩阵中的元素为 [1,5,9,10,11,12,13,13,15],第 8 小元素是 13

示例 2:

输入:matrix = [[-5]], k = 1
输出:-5

思路分析
这道题其实根上面一道题一样,只不过把链表变成了数组
为了让数组模拟链表的形式来实现多路归并,我们可以定义一个结构体

struct op
{
    
    
    int _val;
    int _row;
    int _col;
    op(int val, int row, int col)
    : _val(val)
    , _row(row)
    , _col(col)
    {
    
    }
};

优先级队列中维护的是结构体,结构体包含下标和数值,这样就可以做到模拟链表形式。
代码如下:

class Solution {
    
    
public:
    struct op
    {
    
    
        int _val;
        int _row;
        int _col;
        op(int val, int row, int col)
        : _val(val)
        , _row(row)
        , _col(col)
        {
    
    }
    };

    struct cmp
    {
    
    
        bool operator()(const op& p1, const op& p2)
        {
    
    
            return p1._val > p2._val;
        }
    };

    priority_queue<op, vector<op>, cmp> q;
    int kthSmallest(vector<vector<int>>& matrix, int k) {
    
    
        int n = matrix.size();
        for(int i = 0; i < n; i++)
        {
    
    
            q.push({
    
    matrix[i][0], i, 0});
        }
        for(int i = 0; i < k - 1; i++)
        {
    
    
            auto op = q.top();
            q.pop();
            if(op._col < n - 1)
            {
    
    
                q.push({
    
    matrix[op._row][op._col + 1], op._row, op._col + 1});
            }
        }
        return q.top()._val;
    }
};

拓展思路:二分法

观察矩阵找到规律:
矩阵任一点的数值一定比左边和上边的围成的矩阵的值要大。
在这里插入图片描述

根据这个性质,假如我们找到了一个值,让矩阵的左上角的值 <= 该值,然后统计左上角有多少个值,如果小于k,说明这个值取小了,如果大于等于k,说明答案不大于该值。

例如如图(取8):
在这里插入图片描述
那么怎么统计数量呢?

从左下角开始,i为行,j为列
如果matrix[i][j]≤target,说明该列都要被统计,num += i + 1;然后j++进入下一列。
如果matrix[i][j]>target,就向上移动。

代码如下:

class Solution {
    
    
public:
    bool search(vector<vector<int>>& vv, int n, int target, int k)
    {
    
    
        int num = 0;
        int i = n - 1, j = 0;
        while(i >= 0 && j < n)
        {
    
    
            if(vv[i][j] <= target)
            {
    
    
                num += i + 1;
                j++;
            }
            else
            {
    
    
                i--;
            }
        }
        return num >= k;
    }


    int kthSmallest(vector<vector<int>>& matrix, int k) {
    
    
        int n = matrix.size();
        int l = matrix[0][0], r = matrix[n - 1][n - 1];
        while(l < r)
        {
    
    
            int mid = l + (r - l) / 2;
            if(search(matrix, n, mid, k))
            {
    
    
                r = mid;
            }
            else
            {
    
    
                l = mid + 1;
            }
        }
        return l;
    }
};

这里要注意二分的是值域。

三、查找和最小的 K 对数字

题目链接

题目描述

给定两个以 升序排列 的整数数组 nums1 和 nums2 , 以及一个整数 k 。
定义一对值 (u,v),其中第一个元素来自 nums1,第二个元素来自 nums2 。
请找到和最小的 k 个数对 (u1,v1), (u2,v2) … (uk,vk) 。

示例 1:

输入: nums1 = [1,7,11], nums2 = [2,4,6], k = 3
输出: [1,2],[1,4],[1,6]
解释: 返回序列中的前 3 对数:
[1,2],[1,4],[1,6],[7,2],[7,4],[11,2],[7,6],[11,4],[11,6]

示例 2:

输入: nums1 = [1,1,2], nums2 = [1,2,3], k = 2
输出: [1,1],[1,1]
解释: 返回序列中的前 2 对数:
[1,1],[1,1],[1,2],[2,1],[1,2],[2,2],[1,3],[1,3],[2,3]

示例 3:

输入: nums1 = [1,2], nums2 = [3], k = 3
输出: [1,3],[2,3]
解释: 也可能序列中所有的数对都被返回:[1,3],[2,3]

思路分析
优先级队列维护两个下标,i表示nums1中的下标,j表示nums2中的下标。
正常思路是假设枚举到(ai, bi),那么接下来就把(ai+1, bi), (ai, bi+1)push进优先级队列,但这样会出现重复的情况,所以可以先把nums1的每个元素和nums2的首元素组成的二元组push进去,这样每次只用让nums2的下标变化就行。
小优化:始终确保 nums1 为两数组中长度较少的那个,然后通过标识位来记录是否发生过交换,确保答案的点顺序的正确性。

举个例子:
首次取出的二元组为 (0,0),即点对 (nums1[0],nums2[0]),取完后将序列的下一位点对 (nums1[0],nums2[1])以二元组 (0,1)(0, 1)(0,1) 形式放入优先队列。

代码如下:

class Solution {
    
    
public:
    typedef pair<int, int> PII;
    vector<vector<int>> kSmallestPairs(vector<int>& nums1, vector<int>& nums2, int k) {
    
    
        vector<vector<int>> vv;
        int n = nums1.size(), m = nums2.size();
        bool flag = true;
        if(n > m)
        {
    
    
            swap(n, m);
            swap(nums1, nums2);
            flag = false;
        }
        auto cmp = [&](const auto& e1, const auto& e2){
    
    
            return nums1[e1.first] + nums2[e1.second] > nums1[e2.first] + nums2[e2.second];
        };
        priority_queue<PII, vector<PII>, decltype(&cmp)> q(cmp);
        for(int i = 0; i < min(n, k); i++)
        {
    
    
            q.push({
    
    i, 0});
        }
        while(vv.size() < k && q.size())
        {
    
    
            auto [a, b] = q.top();
            q.pop();
            flag ? vv.push_back({
    
    nums1[a], nums2[b]}) : vv.push_back({
    
    nums2[b], nums1[a]});
            if(b < m - 1) q.push({
    
    a, b + 1});
        }
        return vv;
    }
};


猜你喜欢

转载自blog.csdn.net/qq_66314292/article/details/131470992