题目:
给你一个整数数组
nums
,有一个大小为k
的滑动窗口从数组的最左侧移动到数组的最右侧。你只可以看到在滑动窗口内的k
个数字。滑动窗口每次只向右移动一位。返回 滑动窗口中的最大值 。
示例 1:
输入: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示例 2:
输入:nums = [1], k = 1 输出:[1]提示:
1 <= nums.length <= 105
-104 <= nums[i] <= 104
1 <= k <= nums.length
自己看到题目的第一想法
使用一个滑动窗口, 从最左边开始, 每次都包含三个元素. 在这三个元素进入滑动窗口的时候, 记录住最大的值. 这时候窗口开始右移, 当窗口右移的时候, 需要知道剩下的两个数中, 谁是最大的值. 然而似乎并无法办到, 因为我们不能对数组、不能对窗口进行排序, 因此在不使用额外空间辅助的情况戏啊, 唯一的办法就是对剩下的数据再进行一次比对. 这样下来时间复杂度依旧是O(n*k), k 为窗口的大小.
也考虑过使用双向队列, 使用两个栈, 都失败了.
大概半个小时左右, 我放弃了. 先看视频吧, 毕竟是困难难度的~
看完代码随想录之后的想法
首先可以先想一下这么一个概念, 如果一个队列, 从左往右的每个元素, 右边的元素都不大于或者不小于左边的元素, 就称这个元素为单调队列.
对于滑动窗口的最大值, 我们可以将滑动窗口可容纳的元素全部放到模拟窗口用的数据结构中, 比如双向队列(Java 中的 Deque: ArrayDeque、LinkedList). 同时如果右边的数比左边的大, 则删除左边的元素. 这样可以得到这样一个队列, 该队列中, 左边的元素一定是最大的. 因此窗口初始的时候, 只需要拿模拟窗口用的队列最左边的元素即可. 此时将窗口右移. 窗口又移后, 需要判断窗口队列的最左侧的元素, 是否已经被移除出去了. 如果窗口队列最左侧的元素和窗口的第一个元素相等的话, 则表示需要移除. 这时候把窗口队列中第一个元素移除, 窗口右移后, 把新窗口最右侧的元素添加到窗口队列中. 如此循环到结束后. 得到所有的窗口最大值.
class Solution {
public int[] maxSlidingWindow(int[] nums, int k) {
ArrayDeque<Integer> window = new ArrayDeque<>();// 这里定义的变量别用接口, 会导致 LeetCode 性能变慢
int[] result = new int[nums.length - k + 1];
for (int i = 0; i < nums.length; i++) {
while (window.size() > 0 && window.peekLast() < nums[i]) {
window.pollLast();
}
window.add(nums[i]);
if (i >= k - 1) {
result[i - k + 1] = window.peek();
if (window.peek() == nums[i - k + 1]) {
window.poll();
}
}
}
return result;
}
}
自己实现过程中遇到哪些困难
1.对 Deque 相关的函数不是很熟悉, poll、pollLast、peak、peakLast、offer 都不是很熟悉. 使用了一天好一些了.
2. 感觉判断条件上, 还是不太清晰, 但问题也不大. 二次编码时, 改一个地方就 AC 了.
[LeetCode] 347. 前 K 个高频元素 文章解释
[LeetCode] 347. 前 K 个高频元素 视频解释
题目:
给你一个整数数组
nums
和一个整数k
,请你返回其中出现频率前k
高的元素。你可以按 任意顺序 返回答案。示例 1:
输入: nums = [1,1,1,2,2,3], k = 2 输出: [1,2]示例 2:
输入: nums = [1], k = 1 输出: [1]提示:
1 <= nums.length <= 105
k
的取值范围是[1, 数组中不相同的元素的个数]
- 题目数据保证答案唯一,换句话说,数组中前
k
个高频元素的集合是唯一的进阶:你所设计算法的时间复杂度 必须 优于
O(n log n)
,其中n
是数组大小。
自己看到题目的第一想法
第一反应就是要遍历一遍, 统计出各个数字出现的频率. 然后根据频率对数字进行排序, 取前 k 名. 统计需要 n 次, 插入最多需要(n^2)/2 次. 因此时间复杂度为O(n + (n^2)/2) -- 这里可以合并变成 O(n^2) 吗?
自己的想法也是可以 AC 的, 但是效率很低.
class Solution {
public int[] topKFrequent(int[] nums, int k) {
Map<Integer, Integer> counts = new HashMap<>();
for (int i = 0; i < nums.length; i++) {
counts.put(nums[i], counts.getOrDefault(nums[i], 0) + 1);
}
List<Pair> pairs = new ArrayList<>();
Iterator<Integer> keys = counts.keySet().iterator();
while (keys.hasNext()) {
int key = keys.next();
int value = counts.get(key);
Pair pair = new Pair();
pair.number = key;
pair.count = value;
if (pairs.isEmpty()) {
pairs.add(pair);
} else {
for (int i = 0; i < pairs.size(); i++) {
if (pairs.get(i).count < pair.count) {
pairs.add(i, pair);
break;
} else if (i == pairs.size() - 1) {
pairs.add(pairs.size(), pair);
break;
}
}
}
if (pairs.size() > k) {
pairs.remove(pairs.size() - 1);
}
}
int[] result = new int[pairs.size()];
for (int i = 0; i < pairs.size(); i++) {
result[i] = pairs.get(i).number;
}
return result;
}
private class Pair {
public int number;
public int count;
}
}
看完代码随想录之后的想法
1、第一步依旧是统计数字出现的频率.
2、第二步有点区别, 需要使用 PriorityQueue 这种数据结构, 底层通过堆来优化插入的效率. 堆是由二叉搜索树来构建的, 因此插入删除效率会高一些.
// 最大堆方案
class Solution {
public int[] topKFrequent(int[] nums, int k) {
Map<Integer, Integer> count = new HashMap<>();
for (int number : nums) {
count.put(number, count.getOrDefault(number, 0) + 1);
}
PriorityQueue<int[]> queue = new PriorityQueue<>(new Comparator<int[]>(){
@Override
public int compare(int[] ints, int[] t1) {// 返回负数的话, 第一个对象在前
// 如果 ints[1] - t1[1] > 0 成立, 说明 ints[1] 更大
// 因为 ints[1] 更大, 并且 ints 是在第一个位置
// 因此这里需要返回一个负数才能让 ints 排在靠前的位置
// 因此需要返回 t1[1] - ints[1]
return t1[1] - ints[1];
}
});
for (Map.Entry<Integer, Integer> entry : count.entrySet()) {
queue.add(new int[]{entry.getKey(), entry.getValue()});
}
int[] result = new int[k];
for (int i = 0; i < k; i++) {
result[i] = queue.poll()[0];
}
return result;
}
}
// 最小堆方案
class Solution {
public int[] topKFrequent(int[] nums, int k) {
Map<Integer, Integer> count = new HashMap<>();
for (int number : nums) {
count.put(number, count.getOrDefault(number, 0) + 1);
}
PriorityQueue<int[]> queue = new PriorityQueue<>(new Comparator<int[]>(){
@Override
public int compare(int[] ints, int[] t1) {// 返回负数的话, 第一个对象在前
// 如果 ints[1] - t1[1] > 0 成立, 说明 ints[1] 更大
// 因为 ints[1] 更大, 并且 ints[1] 是第一个位置
// 因此这里需要返回一个非负数才能让 t1 排在靠前的位置
// 因此需要返回 ints[1] - t1[1]
return ints[1] - t1[1];
}
});
for (Map.Entry<Integer, Integer> entry : count.entrySet()) {
if (queue.size() < k) {
queue.add(new int[]{entry.getKey(), entry.getValue()});
} else {
if (queue.peek()[1] < entry.getValue()) {
queue.poll();
queue.add(new int[]{entry.getKey(), entry.getValue()});
}
}
}
int[] result = new int[k];
for (int i = k - 1; i >= 0; i--) {
result[i] = queue.poll()[0];
}
return result;
}
}