239.滑动窗口的最大值(双指针)
给你一个整数数组 nums,有一个大小为 k 的滑动窗口从数组的最左侧移动到数组的最右侧。你只可以看到在滑动窗口内的 k 个数字。滑动窗口每次只向右移动一位。返回滑动窗口中的最大值。
输入:nums = [1,3,-1,-3,5,3,6,7], k = 3 输出:[3,3,5,5,6,7] 解释: 滑动窗口的位置 最大值 --------------- ----- [1 3 -1] -3 5 3 6 7 3 1 [3 -1 -3] 5 3 6 7 3 1 3 [-1 -3 5] 3 6 7 5 1 3 -1 [-3 5 3] 6 7 5 1 3 -1 -3 [5 3 6] 7 6 1 3 -1 -3 5 [3 6 7] 7
取步幅为 1 的、感受野为 k 的滑动窗口内的元素最大值。本题在卡哥的代码随想录中归类在栈和队列章节中,本人对于栈和数据结构的掌握程度还不熟练,读完第一感觉是双指针(三指针)解法,左 left 指针和右 right 指针维护一个 k 大小的滑动窗口,最大值 peak 指针找当前窗口内的最大值。为了区别暴力解法(嵌套循环),我们对窗口的滑动机制做出优化。滑动窗口的步幅为 1,假设当前为第 i 次循环,此时的 peak 和 peak 指向的元素有以下几种情况:
1. 第 i 次的 peak 指向的值大于第 i+1 次的右边界元素,但是 peak 指向 left 的位置,此时的 peak 不在滑动窗口的感受野内,无效。重新在当前窗口内找最大值,这是最坏的情况,和嵌套循环没有区别;
2. 第 i 次的 peak 指向的值大于第 i+1 次的右边界元素, 且 peak 指向非 left 的位置,此时的 peak 也是 i+1 次滑动窗口内的最大值;
3. 第 i 次的 peak 指向的值小于第 i+1 次的右边界元素,那么此时右边界元素就是该窗口内的最大元素,和 peak 的位置无关;
可以看出,最坏的情况一的时间复杂度为O(n*k),就是数组为降序数组,最大值一直出现在滑动窗口的左边界的情况下。最好的情二和三,只需要一次判断即可。
class Solution {
public:
vector<int> maxSlidingWindow(vector<int>& nums, int k) {
vector<int> result;
int left = 0, peak = 0;
for (int i = 0; i < k; i++) {
if (nums[i] > nums[peak]) {
peak = i;
}
}
result.push_back(nums[peak]);
for (int right = k; right < nums.size(); right++) {
if (nums[peak] > nums[right] && peak > left) {
result.push_back(nums[peak]);
} else if (nums[peak] < nums[right]) {
peak = right;
result.push_back(nums[peak]);
} else {
peak = left + 1;
for (int i = left + 2; i <= right; i++) {
if (nums[i] > nums[peak]) {
peak = i;
}
}
result.push_back(nums[peak]);
}
left++;
}
return result;
}
};
239.滑动窗口的最大值(单调双向队列)
在卡哥的代码随想录里,自定义了一个单调的双向队列来遍历这个整形数组,以返回每个滑动窗口内的最大值。在队列中,实现了 pop、push 和返回队首元素接口。该队列中,仅维护可能成为最大值的元素,每当滑动窗口移出的元素和队首元素相同时,将队首元素出队;再向队尾添加一个元素,在 push 元素的过程中,需要判定,如果小于队列中的元素,则正常 push,如果大于队列中的元素,则将前面的元素清空,总要维持,队列中的元素为最大的。这样每一次取队首,就是取当前滑动窗口内的最大值。
class Solution {
public:
vector<int> maxSlidingWindow(vector<int>& nums, int k) {
vector<int> result;
int left = 0, peak = 0;
for (int i = 0; i < k; i++) {
if (nums[i] > nums[peak]) {
peak = i;
}
}
result.push_back(nums[peak]);
for (int right = k; right < nums.size(); right++) {
if (nums[peak] > nums[right] && peak > left) {
result.push_back(nums[peak]);
} else if (nums[peak] < nums[right]) {
peak = right;
result.push_back(nums[peak]);
} else {
peak = left + 1;
for (int i = left + 2; i <= right; i++) {
if (nums[i] > nums[peak]) {
peak = i;
}
}
result.push_back(nums[peak]);
}
left++;
}
return result;
}
};
347.前 k 个高频元素
给你一个整数数组 nums 和一个整数 k,请你返回其中出现频率前 k 高的元素。你可以按照任意顺序返回答案。
第一反应使用哈希表,得到各个数字出现的频率,然后对频率进行排序,最后输出前 k 个元素即可。但是,如何高效地进行排序?快排?可以使用优先队列(小根堆),维护一个 k 大小的小根堆,将频率小的元素都弹出去,最后保留前 k 个频率最高的。因为不太懂优先队列和小根堆,笔至于此。
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;
for (int i = 0; i< nums.size(); i++) {
map[nums[i]]++;
}
priority_queue<pair<int, int>, vector<pair<int, int>>, mycomparison> pri_que;
for (unordered_map<int, int>::iterator it = map.begin(); it != map.end(); it++) {
pri_que.push(*it);
if (pri_que.size() > k) {
pri_que.pop();
}
}
vector<int> result(k);
for (int i = k - 1; i >= 0; i--) {
result[i] = pri_que.top().first;
pri_que.pop();
}
return result;
}
};
总体思想不难,了解一下优先队列和小根堆就好。