leetcode 大顶推 小顶堆 优先队列实现前K大/小 (347、692、451、973、373)

347 前K个高频元素

题目描述: 给定一个非空的整数数组,返回其中出现频率前 k 高的元素。
思路: 用map统计每个元素出现的次数,map的数据格式相当于pair。建立小顶堆,保持优先队列的大小为k,当优先队列已经装满k个元素后,再向队列中push数据时,如果大于堆顶数据,则压入队列,并将最上面的数据pop出去(即pop出当前队列中最小的元素);如果小于,则不进行入队列出队列的操作。
具体的思路及时间复杂度见代码中注释。

class Solution {
public:
    // 堆排序 时间复杂度O(nlogk) 统计频率O(n) 遍历map放入优先队列中O(nlogk) 放入一个元素为O(logn)
    vector<int> topKFrequent(vector<int>& nums, int k) {
        unordered_map<int, int> cnt;
        for(int i = 0; i < nums.size(); i++) 
            cnt[nums[i]]++;
        priority_queue<pair<int, int>, vector<pair<int, int>>, greater<pair<int, int>> > Q; // 记住喔!! greater可以实现pop出的元素为最小值元素 该参数默认为less,实现的是pop出最大值元素
       for(auto &p : cnt) {
            Q.push(make_pair(p.second, p.first));
            if(Q.size() > k)
                Q.pop();
        }
        vector<int> ans;
        while(!Q.empty()) {
            ans.push_back(Q.top().second);
            Q.pop();
        }
        return ans;
    }
};

692 前K个高频单词

题目描述: 给一非空的单词列表,返回前 k 个出现次数最多的单词。返回的答案应该按单词出现频率由高到低排序。如果不同的单词有相同出现频率,按字母顺序排序。
思路: 和347题目的思路相同。先统计出现频率,再利用小顶堆排序。注意本题目有要求单词的排序顺序,需要自己定义优先队列的排序规则。在最后需要reverse()操作。

class Solution {
public:
    struct cmp {
        bool operator () (pair<int, string> a, pair<int, string> b) {
            if(a.first != b.first)
                return a.first > b.first;
            return a.second < b.second; // 题目要求字母顺序,即a应该在b前面,a是小于b的, a应该被放在堆底。
        }
    };

    vector<string> topKFrequent(vector<string>& words, int k) {
        unordered_map<string, int> cnt;
        for(int i = 0; i < words.size(); i++)
            cnt[words[i]] ++;
        priority_queue<pair<int, string>, vector<pair<int, string>>, cmp> Q;
        for(auto &p : cnt) {
            Q.push(make_pair(p.second, p.first));
            if(Q.size() > k) 
                Q.pop();
        }
        vector<string> ans;
        while(!Q.empty()) {
            cout<< Q.top().first<<" "<< Q.top().second<<endl;
            ans.push_back(Q.top().second);
            Q.pop();
        }
        reverse(ans.begin(), ans.end());
        return ans;
    }
};

451 根据字符出现频率排序

题目描述: 给定一个字符串,请将字符串里的字符按照出现的频率降序排列。
实例: 输入:“tree”,输出:“eert”/“eetr”。
思路1: 堆排序。先统计出现频率,再利用优先队列实现堆排序。最后从优先队列取出字符时,注意要取出对应的出现频率次。时间复杂度O(nlogn)。
思路2: 桶排序。具体思路见代码注释。注意字符串中不只包含字母,统计出现频率时,应该开一个大小为128的向量数组。

// 思路1:
class Solution {
public:
    // 堆排序 优先队列
    string frequencySort(string s) {
        unordered_map<char, int> mp;
        for(int i = 0; i < s.size(); i++)
            mp[s[i]]++;
        priority_queue<pair<int, char>> Q;
        for(auto &p : mp)
            Q.push(make_pair(p.second, p.first));
        string ans = "";
        while(!Q.empty()) {
            for(int i = 0; i < Q.top().first; i++)
                ans += Q.top().second;
            Q.pop();
        }
        return ans;
    }
};
// 思路2:
class Solution {
public:
    // 桶排序
    string frequencySort(string s) {
        if(s.size() == 0)
            return "";
        vector<int> cnt(128, 0); // char类型为一个字节,一个字节有八位。ascii编码一共有128个
        for(int i = 0; i < s.size(); i++) // 统计出现频率
            cnt[s[i]]++;
        vector<vector<int>> buckets(s.size() + 1);  //建桶
        for(int i = 0; i < cnt.size(); i++)
            buckets[cnt[i]].push_back(i);
        string ans = "";
        for(int i = buckets.size() - 1; i >= 1; i--) 
            for(int j = 0; j < buckets[i].size(); j++) 
                ans.append(i, buckets[i][j]);  // 按要求顺序遍历各个桶内元素
        return ans;
    }
};

973 最接近原点的K个点

思路描述: 我们有一个由平面上的点组成的列表 points。需要从中找出 K 个距离原点 (0, 0) 最近的点。(这里,平面上两点之间的距离是欧几里德距离。)你可以按任何顺序返回答案。除了点坐标的顺序之外,答案确保是唯一的。
思路: 堆排序。根据题目要求,找出和原点欧几里得距离最小的K个点。维护一个K个值的大顶堆。同347题。注意优先队列的排序方式。

class Solution {
public:
    struct node {
        int x;
        int y;
        int dis;
        friend bool operator < (node a, node b) { // 不在比较函数中进行运算
            return a.dis < b.dis;
        }
    };

    vector<vector<int>> kClosest(vector<vector<int>>& points, int K) {
        priority_queue<node> Q;
        for(int i = 0; i < points.size(); i++) {
            Q.push(node{points[i][0], points[i][1], points[i][0] * points[i][0] + points[i][1] * points[i][1]});
            if(Q.size() > K) {
                Q.pop();
            }
        }
        vector<vector<int>> ans;
        vector<int> tmp(2, 0);
        while(!Q.empty()) {
            tmp[0] = Q.top().x;
            tmp[1] = Q.top().y;
            ans.push_back(tmp);
            Q.pop();
        }
        return ans;
    }
};

373 查找和最小的K对数字

题目描述: 给定两个以升序排列的整形数组 nums1 和 nums2, 以及一个整数 k。定义一对值 (u,v),其中第一个元素来自 nums1,第二个元素来自 nums2。找到和最小的 k 对数字 (u1,v1), (u2,v2) … (uk,vk)。
思路1: 堆排序,思路同973题。
思路2: 二分。二分出前k小的组合的最大值,再根据二份出来的sum值,将小于sum的组合装进答案中。注意边界值的数字对可能有多个,导致有大于K对数字符合最小的K对数字的要求,在二分时要注意更新sum值的位置。

// 思路1
class Solution {
public:
    struct node {
        int sum;
        int first;
        int second;
        friend bool operator < (const node& a, const node& b) {
            return a.sum < b.sum;
        }
    };

    vector<vector<int>> kSmallestPairs(vector<int>& nums1, vector<int>& nums2, int k) {
        priority_queue<node> Q;
        for(int i = 0 ; i < nums1.size(); i++) {
            for(int j = 0; j < nums2.size(); j++) {
                if(!Q.size() == k && (nums1[i] + nums2[j] >= Q.top().sum))
                    break;
                Q.push(node{nums1[i] + nums2[j], nums1[i], nums2[j]});
                if(Q.size() > k)
                    Q.pop();
            }
        }
        vector<vector<int>> ans;
        vector<int> tmp(2, 0);
        while(!Q.empty()) {
            node tmpNode = Q.top();
            tmp[0] = tmpNode.first;
            tmp[1] = tmpNode.second;
            ans.push_back(tmp);
            Q.pop();
        }
        reverse(ans.begin(), ans.end());
        return ans;
    }
};
// 思路2
class Solution {
public:
    // 二分
    int getNum(vector<int>& nums1, vector<int>& nums2, int k, int mid, int n1, int n2) {
        int num = 0;
        for(int i = 0; i < n1 && num <= k; i++) {
            for(int j = 0; j < n2 && num <= k; j++) {
                if(nums1[i] + nums2[j] <= mid)
                    num++;
                else
                    break;
            }
        }
        return num;
    }
    vector<vector<int>> kSmallestPairs(vector<int>& nums1, vector<int>& nums2, int k) {
        vector<vector<int>> ans;
        int n1= nums1.size(), n2 = nums2.size();
        if(n1 == 0 || n2 == 0)
            return ans;
        // 更新k值
        k = min(k, n1 * n2);
        // 二分前k小的组合的最大值 时间复杂度为k * log(high)
        int low = nums1[0] + nums2[0],  high = nums1[n1 - 1] + nums2[n2 - 1], sum = 0;
        while(low <= high) {
            int mid = (low + high) / 2;
            // cout<<mid<<endl;
            int num = getNum(nums1, nums2, k, mid, n1, n2);
            if(num >= k) {
                high = mid - 1;
                sum = mid;
            }
            else
                low = mid + 1;
        }
        // cout<< sum <<endl;
        // 根据二份出来的sum值 将小于sum的组合装进答案中 先装小于的 再装等于的
        for(int i = 0; i < n1 && ans.size() < k; i++) {
            for(int j = 0; j < n2 && ans.size() < k; j++) {
                if(nums1[i] + nums2[j] < sum) {
                    vector<int> tmp;
                    tmp.push_back(nums1[i]);
                    tmp.push_back(nums2[j]);
                    ans.push_back(tmp);
                }
                else
                    break;
            }
        }
        for(int i = 0; i < n1 && ans.size() < k; i++) {
            for(int j = 0; j < n2 && ans.size() < k; j++) {
                if(nums1[i] + nums2[j] == sum) {
                    vector<int> tmp;
                    tmp.push_back(nums1[i]);
                    tmp.push_back(nums2[j]);
                    ans.push_back(tmp);
                }
                else if(nums1[i] + nums2[j] > sum)
                    break;
            }
        }
        return ans;
    }
};
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值