239.滑动窗口最大值
给定一个数组 nums,有一个大小为 k 的滑动窗口从数组的最左侧移动到数组的最右侧。你只可以看到在滑动窗口内的 k 个数字。滑动窗口每次只向右移动一位。 返回滑动窗口中的最大值。 进阶: 你能在线性时间复杂度内解决此题吗?
提示:
- 1 <= nums.length <= 10^5
- -10^4 <= nums[i] <= 10^4
- 1 <= k <= nums.length
思路
这道题的关键是如何在滑动窗口中快速找到最大值。为了实现这一目标,我们可以使用一个双端队列(deque)来存储数组中的索引,而不是直接存储元素值。以下是解题的核心思路:
- 双端队列:我们使用一个双端队列来维护当前滑动窗口中的元素索引。这个队列的特点是它的前端始终包含当前窗口的最大值的索引。
- 移除旧的元素:当滑动窗口向右移动时,我们需要确保队列的前端索引对应的元素仍然在当前窗口范围内。如果不在范围内,我们就从队列的前端移除该索引。
- 移除小于当前元素的索引:在添加一个新元素到队列之前,我们从队列的后端移除所有小于当前元素的索引。这是因为这些元素不可能是当前窗口或任何后续窗口的最大值。
- 添加新的元素:将当前元素的索引添加到队列的后端。
- 收集结果:每次当我们处理到一个元素并且它的索引大于或等于
k - 1时(也就是说,当滑动窗口完全形成时),我们将队列的前端索引对应的元素添加到结果数组中。这是因为队列的前端始终包含当前窗口的最大值的索引。
题解
class Solution {
public:
vector<int> maxSlidingWindow(vector<int>& nums, int k) {
std::deque<int> dq;
std::vector<int> res;
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);
// 如果当前元素的索引大于或等于 k - 1,则将双端队列的前端索引对应的元素添加到结果数组中
if (i >= k - 1) {
res.push_back(nums[dq.front()]);
}
}
return res;
}
};
347.前 K 个高频元素
给定一个非空的整数数组,返回其中出现频率前 k 高的元素。 示例 1:
- 输入: nums = [1,1,1,2,2,3], k = 2
- 输出: [1,2]
示例 2:
- 输入: nums = [1], k = 1
- 输出: [1]
提示:
- 你可以假设给定的 k 总是合理的,且 1 ≤ k ≤ 数组中不相同的元素的个数。
- 你的算法的时间复杂度必须优于 , n 是数组的大小。
- 题目数据保证答案唯一,换句话说,数组中前 k 个高频元素的集合是唯一的。
- 你可以按任意顺序返回答案。
思路
1. 统计频率
首先,我们需要知道每个元素在数组中出现了多少次。为此,我们可以使用哈希表(在C++中通常使用unordered_map)。键是数组中的元素,值是该元素的出现次数。
2. 使用优先级队列
我们的目标是找到出现次数最多的k个元素。为了高效地实现这一点,我们可以使用一个小顶堆。小顶堆是一种特殊的二叉树,其中父节点的值小于或等于其子节点的值。由于C++的priority_queue默认是大顶堆,我们需要提供一个自定义的比较函数来创建小顶堆。
为什么使用小顶堆呢?因为我们可以持续地将元素和它们的频率放入堆中,如果堆的大小超过了k,我们就可以弹出堆顶的元素(这是频率最低的元素)。这样,我们可以确保堆中始终保持频率最高的k个元素。
3. 提取结果
当我们处理完所有的元素后,堆中的元素就是出现次数最多的k个元素。我们可以从堆中弹出这些元素并将它们放入一个列表中。
这种方法的主要优点是,当我们处理每个元素时,它的时间复杂度是 (O(\log k))(因为插入和删除堆中的元素的时间复杂度是 (O(\log k)))。因此,总的时间复杂度是 (O(n \log k)),其中n是数组的长度。这比简单地对所有元素进行排序要快得多,特别是当k远小于n时。
题解
class Solution {
public:
vector<int> topKFrequent(vector<int>& nums, int k) {
// 步骤1: 使用哈希表统计每个数字的频率
std::unordered_map<int, int> freq_map;
for (int num : nums) {
freq_map[num]++;
}
// 定义优先级队列的自定义比较函数。此函数确保队列为小顶堆。
auto cmp = [](const auto& a, const auto& b) {
return a.second > b.second;
};
// 使用自定义比较函数初始化优先级队列
std::priority_queue<std::pair<int, int>, std::vector<std::pair<int, int>>, decltype(cmp)> pq(cmp);
// 步骤2: 使用频率哈希表填充优先级队列
for (const auto& [num, freq] : freq_map) {
pq.push({num, freq});
// 如果优先级队列的大小超过k,则弹出最小的元素(即频率最低的元素)
if (pq.size() > k) {
pq.pop();
}
}
// 步骤3: 从优先级队列中提取前k个高频元素
std::vector<int> res;
while (!pq.empty()) {
res.push_back(pq.top().first);
pq.pop();
}
return res;
}
};