239. 滑动窗口最大值
给你一个整数数组 nums,有一个大小为 k 的滑动窗口从数组的最左侧移动到数组的最右侧。你只可以看到在滑动窗口内的 k 个数字。滑动窗口每次只向右移动一位。
返回 滑动窗口中的最大值 。
示例 1:
输入:nums = [1,3,-1,-3,5,3,6,7], k = 3
输出:[3,3,5,5,6,7]
解释:
class Solution {
public int[] maxSlidingWindow(int[] nums, int k) {
int n = nums.length;
PriorityQueue<int[]> pq = new PriorityQueue<int[]>(new Comparator<int[]>() {
public int compare(int[] pair1, int[] pair2) {
return pair1[0] != pair2[0] ? pair2[0] - pair1[0] : pair2[1] - pair1[1];
}
});
for (int i = 0; i < k; ++i) {
pq.offer(new int[]{nums[i], i});
}
int[] ans = new int[n - k + 1];
ans[0] = pq.peek()[0];
for (int i = k; i < n; ++i) {
pq.offer(new int[]{nums[i], i});
while (pq.peek()[1] <= i - k) {
pq.poll();
}
ans[i - k + 1] = pq.peek()[0];
}
return ans;
}
}
这段代码定义了一个名为 Solution
的类,其中包含一个方法 maxSlidingWindow
,用于求解数组中大小为 k
的滑动窗口里的最大值。该方法利用优先队列(具体实现为 PriorityQueue
)来高效地维护窗口内的最大值,下面是代码的详细解析:
方法签名
public int[] maxSlidingWindow(int[] nums, int k)
- 输入:
nums
: 整型数组,表示输入序列。k
: 窗口大小。
- 输出:
- 返回一个数组,包含每个大小为
k
的滑动窗口的最大值。
- 返回一个数组,包含每个大小为
代码解析
-
初始化:
- 计算输入数组长度
n
。 - 初始化一个优先队列
pq
,用于存放窗口内的元素及其在原数组中的索引。这里的优先队列采用自定义比较器,使得队列顶部元素始终为当前窗口内最大值(值最大,且最早出现的排前面)。
- 计算输入数组长度
-
预处理前 k 个元素:
- 先将前
k
个元素加入优先队列pq
。
- 先将前
-
构建结果数组:
- 初始化结果数组
ans
,大小为n - k + 1
。 - 记录下当前窗口(即前
k
个元素)的最大值,作为结果数组的第一个元素。 - 遍历数组剩余部分,每次迭代时:
- 向队列添加新进入窗口的元素(当前元素)。
- 若队首元素已不在当前窗口内(索引小于
i - k
),则将其移出队列,确保队列始终代表当前窗口。 - 记录下队首元素作为当前窗口的最大值,加入结果数组。
- 初始化结果数组
-
返回结果:
- 完成遍历后,返回结果数组
ans
。
- 完成遍历后,返回结果数组
注意
- 代码中存在一个语法错误,创建
pq.offer
时应为pq.offer(new int[]{nums[i], i})
,原代码缺少括号闭合。 - 自定义比较器实现了降序排列,首先按值降序,值相同时按索引降序,确保了最早的元素被优先淘汰,满足滑动窗口逻辑。
修正后的代码如下:
class Solution {
public int[] maxSlidingWindow(int[] nums, int k) {
int n = nums.length;
PriorityQueue<int[]> pq = new PriorityQueue<int[]>(new Comparator<int[]>() {
public int compare(int[] pair1, int[] pair2) {
return pair1[0] != pair2[0] ? pair2[0] - pair1[0] : pair2[1] - pair1[1];
}
});
for (int i = 0; i < k; ++i) {
pq.offer(new int[]{nums[i], i});
}
int[] ans = new int[n - k + 1];
ans[0] = pq.peek()[0];
for (int i = k; i < n; ++i) {
pq.offer(new int[]{nums[i], i});
while (pq.peek()[1] <= i - k) {
pq.poll();
}
ans[i - k + 1] = pq.peek()[0];
}
return ans;
}
}
请根据上述解析,修复语法错误后,这段代码将能正确实现滑动窗口最大值的求解逻辑。
347. 前 K 个高频元素
给你一个整数数组 nums 和一个整数 k ,请你返回其中出现频率前 k 高的元素。你可以按 任意顺序 返回答案。
示例 1:
输入: nums = [1,1,1,2,2,3], k = 2
输出: [1,2]
方法一:堆
class Solution {
public int[] topKFrequent(int[] nums, int k) {
Map<Integer, Integer> occurrences = new HashMap<Integer, Integer>();
for (int num : nums) {
occurrences.put(num, occurrences.getOrDefault(num, 0) + 1);
}
// int[] 的第一个元素代表数组的值,第二个元素代表了该值出现的次数
PriorityQueue<int[]> queue = new PriorityQueue<int[]>(new Comparator<int[]>() {
public int compare(int[] m, int[] n) {
return m[1] - n[1];
}
});
for (Map.Entry<Integer, Integer> entry : occurrences.entrySet()) {
int num = entry.getKey(), count = entry.getValue();
if (queue.size() == k) {
if (queue.peek()[1] < count) {
queue.poll();
queue.offer(new int[]{num, count});
}
} else {
queue.offer(new int[]{num, count});
}
}
int[] ret = new int[k];
for (int i = 0; i < k; ++i) {
ret[i] = queue.poll()[0];
}
return ret;
}
}
这段代码定义了一个名为 Solution
的类,其中包含一个方法 topKFrequent
用于找到输入数组 nums
中出现频率最高的 k
个元素。该方法使用了哈希映射(HashMap
)记录每个元素出现的次数,并使用优先队列(PriorityQueue
)进行排序,最终提取前 k
个高频元素。下面是代码的详细解析:
-
记录元素出现次数:
- 遍历输入数组
nums
,使用HashMap
occurrences
记录每个元素及其出现的次数,使用getOrDefault
方法简化计数逻辑。
- 遍历输入数组
-
初始化优先队列:
- 定义一个优先队列
queue
,其中每个元素为int[]
类型数组,数组的第一个元素存数值,第二个元素存该数值出现的频次。 - 使用自定义比较器
Comparator
,使得队列按频次从小到大排序,以方便之后的处理。
- 定义一个优先队列
-
构建优先队列:
- 遍历
occurrences
的条目,将数值和频次加入队列queue
中。 - 当队列大小达到
k
后,检查新元素的频次是否大于队头元素频次,若是则移除队头元素并加入新元素,确保队列始终存储的是当前最大的k
个频次的元素。
- 遍历
-
提取结果:
- 初始化一个大小为
k
的数组ret
用于存放结果。 - 弹出队列中的元素,提取数值部分存入
ret
,直到队列为空。 - 最后返回数组
ret
,即为频率最高的k
个元素。
- 初始化一个大小为
注意点
- 代码中存在一处逻辑错误,应将条件判断语句块正确嵌套。修正后的代码应为:
if (queue.size() == k) {
if (queue.peek()[1] < count) {
queue.poll();
queue.offer(new int[]{num, count});
}
} else {
queue.offer(new int[]{num, count});
}
- 修正后的代码片段将正确实现原意,即当队列满时比较并可能替换队首元素,否则直接加入新元素。
整体而言,此方法通过哈希映射高效统计频次,利用优先队列进行排序并限制大小,最终提取前 k
高频元素,是处理此类问题的一种典型且高效的算法实现。
方法二:基于快速排序
class Solution {
public int[] topKFrequent(int[] nums, int k) {
Map<Integer, Integer> occurrences = new HashMap<Integer, Integer>();
for (int num : nums) {
occurrences.put(num, occurrences.getOrDefault(num, 0) + 1);
}
// 获取每个数字出现次数
List<int[]> values = new ArrayList<int[]>();
for (Map.Entry<Integer, Integer> entry : occurrences.entrySet()) {
int num = entry.getKey(), count = entry.getValue();
values.add(new int[]{num, count});
}
int[] ret = new int[k];
qsort(values, 0, values.size() - 1, ret, 0, k);
return ret;
}
public void qsort(List<int[]> values, int start, int end, int[] ret, int retIndex, int k) {
int picked = (int) (Math.random() * (end - start + 1)) + start;
Collections.swap(values, picked, start);
int pivot = values.get(start)[1];
int index = start;
for (int i = start + 1; i <= end; i++) {
// 使用双指针把不小于基准值的元素放到左边,
// 小于基准值的元素放到右边
if (values.get(i)[1] >= pivot) {
Collections.swap(values, index + 1, i);
index++;
}
}
Collections.swap(values, start, index);
if (k <= index - start) {
// 前 k 大的值在左侧的子数组里
qsort(values, start, index - 1, ret, retIndex, k);
} else {
// 前 k 大的值等于左侧的子数组全部元素
// 加上右侧子数组中前 k - (index - start + 1) 大的值
for (int i = start; i <= index; i++) {
ret[retIndex++] = values.get(i)[0];
}
if (k > index - start + 1) {
qsort(values, index + 1, end, ret, retIndex, k - (index - start + 1));
}
}
}
}
这段代码实现了一个名为 Solution
的类,其中包含一个方法 topKFrequent
用于找出数组 nums
中出现频率最高的 k
个元素。代码采用了哈希映射(HashMap
)记录每个元素出现的频率,并使用了快速排序(qsort
)算法的一个变体来找出这些高频元素。下面是代码的详细解析:
记录元素频率
- 使用
HashMap
occurrences
记录下每个元素及其出现的次数。 - 将
HashMap
的内容转换为List<int[]>
形式的values
,每个数组包含元素值及其出现次数。
快速排序变体
- 定义
qsort
方法,用于对values
列表进行快速排序,同时填充结果数组ret
。- 选择一个随机索引
picked
作为基准值,与起始元素交换,以实现随机化,提高平均性能。 - 通过双指针法将元素分为小于和大于等于基准值的两部分,完成一次划分。
- 递归地在合适的子数组中继续进行快速排序,直到完成排序。
- 当前
k
大的元素完全位于某侧子数组中时,直接处理并更新结果数组ret
,否则递归处理另一侧子数组。
- 选择一个随机索引
返回结果
- 最终,
topKFrequent
方法返回数组ret
,包含频率最高的k
个元素。
注意点
- 该实现在最坏情况下的时间复杂度可能退化为 O(N^2),因为快速排序的选择和交换操作是在基于列表的环境中进行的,这比直接在数组上的操作更耗时。
- 代码中使用的快速排序变体特别针对寻找 top-K 问题进行了优化,减少了不必要的排序操作,直接在过程中收集结果。
整体而言,这段代码通过哈希映射快速统计元素频率,然后利用快速排序的一个变体高效找到前 k
大的高频元素,是处理这类问题的一种可行方案,尽管在效率上可能不是最优。