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;
}
};