第一题
从双重for循环的暴力解法超出时间限制。
学习记录:
创建一个单调递减队列,因为要能从前后都可以pop,所以用deque双向队列。
单调递减队列的逻辑:
当要压入元素时,需要一个一个对比之前队尾元素的大小,如果队尾元素小于要压入的值,直接pop掉,然后将要压入的元素直接放到单调递减队列的对应位置中;
class Solution {
private:
class MyQueue { //创建单调递减队列
public:
deque<int> que;
void push(int val) {
while (!que.empty() && val > que.back()) { //如果队列尾部的元素小于要压入的元素,则全部弹出以后再压入,保证单调递减
que.pop_back();
}
que.push_back(val);
}
void pop(int val) {
if (!que.empty() && val == que.front()) { //当要弹出的值为队头元素时才弹出,否则不管(因为在push的时候已经保证了单调递减,队头为当前滑动窗口最大值,最大值之前的元素本身就没有push进来)
que.pop_front();
}
}
int getfront() {
return que.front();
}
};
public:
vector<int> maxSlidingWindow(vector<int>& nums, int k) {
vector<int> max_list;
MyQueue myque;
for (int i=0; i < k; i++) { //第一个窗口,循环结束后队列里为最大值及后面小于它的值(单调递减)
myque.push(nums[i]);
}
max_list.push_back(myque.getfront()); //第一个窗口的最大值
for (int i=k; i<nums.size(); i++) { //从第二个窗口开始直到结束,i从k开始是因为队列是从尾部增加元素的,因此要从第一个窗口结束的后一位开始入队
myque.pop(nums[i-k]); //弹出上一个窗口的起始值,以便维护窗口大小
myque.push(nums[i]);
max_list.push_back(myque.getfront());
}
return max_list;
}
};
复习,双端队列做:
class Solution {
public:
vector<int> maxSlidingWindow(vector<int>& nums, int k) {
if (k == 1) return nums;
vector<int> res;
deque<int> dq;
for (int i = 0; i < nums.size(); i++) {
while (!dq.empty() && dq.front() <= i-k) { //移除所有不在窗口里的索引
dq.pop_front();
}
while (!dq.empty() && nums[dq.back()] < nums[i]) { //保持队列对应元素单调递减,因为既在窗口里又小于nums[i]的元素已经不可能是maxnum了,所以可以直接删掉它们
dq.pop_back();
}
dq.push_back(i);
if (i >= k-1) res.push_back(nums[dq.front()]);//以窗口右边界为一次观察
}
return res;
}
};
第二题
想到的是用map存次数,然后排序再输出前k高的元素,由于map没有sort,所以又新建了一个vector<pair>存了map的键值对,然后对vector进行排序的。调了很久,已AC:
class Solution {
public:
vector<int> topKFrequent(vector<int>& nums, int k) {
std::map<int,int> count;
for (int i : nums) {
count[i]++;
}
vector<std::pair<int,int>> value;
for (auto it = count.begin(); it != count.end(); it++) {
value.push_back(make_pair(it->second, it->first));
// cout<<value.back().first<<","<<value.back().second<<endl;
}
sort(value.begin(),value.end());
for (auto it = value.begin(); it!= value.end();it++) {
// cout<<it->first<<","<<it->second<<endl;
}
vector<int> result;
for (int i = 1; i <= k; i++) {
result.push_back(value.at(value.size()-i).second);
// cout<<result.back()<<endl;
}
return result;
}
};
学习记录:
因为是求频率前k高的元素,只需要维护k个有序集合就可以了,没必要全部排序。
大顶堆、小顶堆:特别擅长在大数据集中求前k个高频或者低频的结果;维护k个节点的堆里数值比较大或者比较小的元素,如果求高频、用大顶堆,那么数值最大的根节点就被pop出去了,留下的是数值小的元素;因此求高频用小顶堆,低频用大顶堆。
priority_queue 优先级队列的底层实现是堆,底层存储数据的容器是vector,其也是容器适配器。可以把它当成堆。默认情况下priority_queue是大堆。
在创建小顶堆时,小顶堆的元素应该是pair<int, int>类型,将map的键值对都存入小顶堆中。pair类型的小顶堆默认是以first来比较的,因此可以使用默认函数greater定义小顶堆,然后将map的键和值调换一下作为小顶堆的pair。以下是代码:
class Solution {
public:
vector<int> topKFrequent(vector<int>& nums, int k) {
std::map<int,int> count;
for (int i:nums) {
count[i]++;
}
priority_queue<pair<int,int>, vector<pair<int,int>>, greater<pair<int,int>>> pri_que;
for (auto it = count.begin(); it != count.end(); it++) {
pri_que.push(make_pair(it->second,it->first));
if (pri_que.size() > k) {
pri_que.pop();
}
}
vector<int> result;
for (int i = k-1; i >= 0; i--) {
result.push_back(pri_que.top().second);
pri_que.pop();
}
return result;
}
};
以下为代码随想录自定义比较类mycomparison的写法,他是将map键值对直接存到小顶堆里,定义小顶堆时用的是自定义比较类,也就是比较pair.second的大小来排序(默认的less创建大顶堆,重载的“()”是左边小于右边):
class Solution {
public:
// 小顶堆
class mycomparison {
public:
bool operator()(const pair<int, int>& lhs, const pair<int, int>& rhs) {
return lhs.second > rhs.second;
}
};
vector<int> topKFrequent(vector<int>& nums, int k) {
// 要统计元素出现频率
unordered_map<int, int> map; // map<nums[i],对应出现的次数>
for (int i = 0; i < nums.size(); i++) {
map[nums[i]]++;
}
// 对频率排序
// 定义一个小顶堆,大小为k
priority_queue<pair<int, int>, vector<pair<int, int>>, mycomparison> pri_que;
// 用固定大小为k的小顶堆,扫面所有频率的数值
for (unordered_map<int, int>::iterator it = map.begin(); it != map.end(); it++) {
pri_que.push(*it);
if (pri_que.size() > k) { // 如果堆的大小大于了K,则队列弹出,保证堆的大小一直为k
pri_que.pop();
}
}
// 找出前K个高频元素,因为小顶堆先弹出的是最小的,所以倒序来输出到数组
vector<int> result(k);
for (int i = k - 1; i >= 0; i--) {
result[i] = pri_que.top().first;
pri_que.pop();
}
return result;
}
};
priority_queue类的模板参数列表,里面有三个参数:class T,class Container = vector<T>,class Compare = less<typename Container::value_type>
template <class T, class Container = vector<T>,
class Compare = less<typename Container::value_type> >
class priority_queue;
- class T:T是优先队列中存储的元素的类型。
- class Container = vector<T>:Container是优先队列底层使用的存储结构,可以看出来,默认采用vector。
- class Compare = less<typename Container::value_type> :Compare是定义优先队列中元素的比较方式的类。
自定义类的比较方式是需要用户自己给出的,并且需要手动填充到Compare 参数的位置。Compare后面跟着的less<typename Container::value_type>就是默认的比较类,默认是按小于(less)的方式比较,这种比较方式创建出来的就是大堆。所以优先队列默认就是大堆。
如果需要创建小堆,就需要将less改为greater。
less类的函数参数。
template <class T>
struct less : binary_function <T,T,bool> {
bool operator() (const T& x, const T& y) const {return x<y;}
};
上面是less类的内部函数,less类的内部重载(),这也就是前面提到的仿函数,参数列表中有左右两个参数,左边小于右边的时候返回true,此时优先队列就是大堆。
template <class T>
struct greater : binary_function <T,T,bool> {
bool operator() (const T& x, const T& y) const {return x>y;}
};
上面是greater类的内部函数,greater类的内部重载(),这也就是前面提到的仿函数,参数列表中有左右两个参数,左边大于右边的时候返回true,此时优先队列就是小堆。
注意:less类和greater类只能比较内置类型的数据的大小,如果用户需要比较自定义类型的数据,就需要自己定义一个比较类,并且重载()。
同时less类和greater类也具有模板参数,因为他们也是模板,所以我们如果要存储自定义类型的元素,就要将自定义类型作为模板参数传递给less类和greater类。参考下列定义,存储的元素类型为pair<int,int>,就需要将greater后面的参数也写上pair<int,int>:
priority_queue< pair<int,int>, vector<pair<int,int>>, greater<pair<int,int>>> pri_que;