滑动窗口最大值
暴力解法
会超时,计算每个窗口内最大值,并将其加入最后的窗口最大值数组。这里我有使用一些剪枝,但仍会超时。具体代码如下。算法的时间复杂度为O(n*k),空间复杂度为O(n-k+1),简化为O(n)。
class Solution {
public:
// 辅助函数,用于找到从begin到end索引范围内的最大值
int find_max(vector<int>& nums, int begin, int end) {
int max = -10000; // 初始化最大值为一个很小的数
for (int i = begin; i <= end; i++) { // 遍历begin到end之间的所有元素
if (nums[i] > max) { // 如果当前元素大于max,则更新max
max = nums[i];
}
}
return max; // 返回最大值
}
// 主函数,用于求解滑动窗口的最大值序列
vector<int> maxSlidingWindow(vector<int>& nums, int k) {
vector<int> ans; // 创建一个空向量,用于存储每个窗口的最大值
int left = 0; // 初始化窗口的左边界
int right = k - 1; // 初始化窗口的右边界(k-1是初始窗口的最后一个元素的索引)
int max = find_max(nums, left, right); // 找到初始窗口的最大值
for (int i = 0; i <= nums.size() - k; i++) { // 遍历所有可能的窗口
if (left == 0) { // 如果是第一个窗口,直接将最大值加入答案
ans.push_back(max);
left++; // 窗口左边界向右移动
right++; // 窗口右边界向右移动
}
else if (left > 0 && nums[left - 1] != max && nums[right] < max) { // 如果左边界不是第一个元素,且离开的元素不是最大值,且新加入的元素小于最大值
ans.push_back(max); // 保持最大值不变,加入答案
left++; // 窗口左边界向右移动
right++; // 窗口右边界向右移动
}
else { // 其他情况,需要重新找到当前窗口的最大值
max = find_max(nums, left, right); // 重新找到当前窗口的最大值
ans.push_back(max); // 将最大值加入答案
left++; // 窗口左边界向右移动
right++; // 窗口右边界向右移动
}
}
return ans; // 返回最大值序列
}
};
单调队列
单调队列 滑动窗口最大值【基础算法精讲 27】_哔哩哔哩_bilibilihttps://www.bilibili.com/video/BV1bM411X72E/?spm_id_from=333.337.search-card.all.click&vd_source=fc4a6e70e3a87b7ea67c2024e326e7c5这个视频里对飞机在飞机上从上往下看找高山的例子我感觉很形象。
这里寻找最大值,将可能的最大值存入双端队列中,每次移动都先判断其是否有可能成为最大值,即若队列非空,它比队列中队首的数小(因为队首的数在下一次移动会弹出),但比它入队前的队尾要大,则将队尾弹出,将它入队,形成一个有序的双端队列。
我们需要一个数据结构,能够实现(双端队列)
- 移除最左边的元素
- 移除最右边的元素
- 在最右边插入元素
以及(单调性)
- 从队首到队尾单调递减
即一个单调队列。代码如下。
class Solution {
public:
vector<int> maxSlidingWindow(vector<int>& nums, int k) {
vector<int> ans; // 存储结果的数组
deque<int> dq; // 双端队列,用于存储可能的最大值候选
for (int i = 0; i < nums.size(); ++i) {
// 移除窗口之外的元素索引
while (!dq.empty() && dq.front() < i - k + 1) {
dq.pop_front();
}
// 移除所有小于当前元素的索引,保证队列是单调递减的
while (!dq.empty() && nums[dq.back()] < nums[i]) {
dq.pop_back();
}
// 将当前元素索引加入队列
dq.push_back(i);
// 当窗口形成时,将最大值加入结果数组
if (i >= k - 1) {
ans.push_back(nums[dq.front()]);
}
}
return ans;
}
};
算法的时间复杂度为O(n),空间复杂度为O(k)。
前K个高频元素
哈希表加快排
首先创建了一个 unordered_map
来存储每个元素及其出现的频率。然后,将 map
中的键值对复制到一个 vector
中。接下来,我们使用 sort
函数对 vector
进行排序,排序的依据是键值对的第二个元素(即频率),排序完成后,我们创建了一个 vector
ans
来存储前 k
个高频元素的键。我们遍历排序后的 vector
,将前 k
个元素的键放入 ans
中。
class Solution {
public:
vector<int> topKFrequent(vector<int>& nums, int k) {
// 创建一个哈希map来存储元素及其出现频数
unordered_map<int, int> freq;
for (int num : nums) {
freq[num]++;
}
// 将map中的键值对存储到vector中
vector<pair<int, int>> freqVec(freq.begin(), freq.end());
// 根据频率对键值对进行排序
sort(freqVec.begin(), freqVec.end(), [](const pair<int, int>& a, const pair<int, int>& b) {
return a.second > b.second; // 按照频率降序排序
});
// 创建答案数组
vector<int> ans(k);
// 将前k个高频元素的键放入答案数组
for (int i = 0; i < k; ++i) {
ans[i] = freqVec[i].first;
}
return ans;
}
};
算法的时间复杂度为O(nlogn),空间复杂度为O(n)。
哈希表加最小堆
我们首先使用unordered_map
来统计每个元素的出现频率。然后,利用一个最小堆来维护频率最高的k个元素。每次从哈希表中取出一个元素,就将其插入到堆中,如果堆的大小超过了k,我们就弹出堆顶元素,这样堆中始终维护的是频率最高的k个元素。最后,我们将堆中的元素取出到答案数组中,由于堆是最小堆,所以我们需要从后往前填充数组,以确保数组是按照频率从高到低排序的。
class Solution {
public:
vector<int> topKFrequent(vector<int>& nums, int k) {
// 创建一个哈希map来存储元素及其出现频数
unordered_map<int, int> freq;
for (int num : nums) {
freq[num]++;
}
// 创建一个最小堆,用于存储频率最高的k个元素
// 堆中的元素是pair,first是频率,second是元素值
priority_queue<pair<int, int>, vector<pair<int, int>>, greater<pair<int, int>>> minHeap;
// 遍历哈希表,将元素插入堆中
for (auto& pair : freq) {
minHeap.push(make_pair(pair.second, pair.first));
// 如果堆的大小超过k,弹出堆顶元素(频率最小的)
if (minHeap.size() > k) {
minHeap.pop();
}
}
// 从堆中取出元素到答案数组中
vector<int> ans(k);
for (int i = k - 1; i >= 0; --i) {
ans[i] = minHeap.top().second;
minHeap.pop();
}
return ans;
}
};
时间复杂度O(nlogk),k为堆的大小,空间复杂度O(n)