目录
1.T239:滑动窗口最大值
思路
代码实现
法1、自定义单调队列
法2、用元素索引代替元素
2.T347:前K个高频元素
代码实现
大顶堆
小顶堆
T:给你一个整数数组 nums,有一个大小为 k 的滑动窗口从数组的最左侧移动到数组的最右侧。你只可以看到在滑动窗口内的 k 个数字。滑动窗口每次只向右移动一位。
返回 滑动窗口中的最大值 。
提示:
1 <= nums.length <= 105
-104 <= nums[i] <= 104
1 <= k <= nums.length
进阶:
你能在线性时间复杂度内解决此题吗?
S:
个人认为本题应该是代码随想录刷题主线(不算“其他题目”)至今最难的一题
如果考虑每次都怎么在滑动窗口中求局部最大值,那样时间复杂度绝对超出线性,目测O(n*k)起步
首先,可以明确的是:滑动窗口每次移动,都必定会有1个数进窗,有1个数出窗;
从这一角度出发,我们能不能使用以栈或队列数据结构实现的容器,使得每次弹出的数都是局部最大值?
很明显,没有现成的,但是可以自己实现!
class MyQue {
private Deque queue = new ArrayDeque<>();
// 看一下当前要弹出的数值,是否等于队列出口元素的数值,相等才弹出
public void poll(int value) {
if (!queue.isEmpty() && queue.getFirst() == value) {
queue.poll();
}
}
// 如果push的数值大于入口元素的数值,那么就将队列后端的数值一个一个地弹出,直到push的数值小于等于队列入口元素的数值
// 这样就保证了队列里的数值是(从队头到队尾)单调非递增的
public void add(int value) {
while (!queue.isEmpty() && value > queue.getLast()) {
queue.removeLast();
// queue.poll();
}
queue.add(value);//addLast
}
// 查询当前队列里的最大值 直接返回队列前端也就是front就行
public int peek() {
return queue.getFirst();//等效于peek
}
}
class Solution {
public int[] maxSlidingWindow(int[] nums, int k) {
if (nums == null || nums.length <= 1) return nums;
MyQue myque = new MyQue();
int[] res = new int[nums.length - k + 1];//数组的长度
// 先将前k的元素放进队列
for (int i = 0; i < k; ++i) {
myque.add(nums[i]);
}
int num = 0;
res[num++] = myque.peek();//漏了
for (int i = k; i < nums.length; ++i) {
myque.poll(nums[i - k]);// 滑动窗口移除最前面元素(当然不是一定会移除)
myque.add(nums[i]);
res[num++] = myque.peek();
}
return res;
}
}
public int[] maxSlidingWindow(int[] nums, int k) {
ArrayDeque deque = new ArrayDeque<>();
int n = nums.length;
int[] res = new int[n - k + 1];
int idx = 0;
for(int i = 0; i < n; i++) {
// 根据题意,i为nums下标,是要在窗口[i - k + 1, i]中选最大值,只要保证两点
// 1.队列头结点需要在[i - k + 1, i]范围内,不符合则要弹出
while(!deque.isEmpty() && deque.peek() < i - k + 1) {//难点!~法1中比较队头元素是否等于要出窗的value
// while(!deque.isEmpty() && nums[deque.peek()] == nums[i - k + 1]) {
deque.poll();
}
// 2.既然是单调,就要保证每次放进去的数字要比末尾的都大,否则也弹出
while(!deque.isEmpty() && nums[deque.peekLast()] < nums[i]) {
deque.pollLast();
}
deque.offer(i);
// 因为单调,当i增长到符合第一个k范围的时候,每滑动一步都将队列头节点放入结果就行
if(i >= k - 1){
res[idx++] = nums[deque.peek()];
}
}
return res;
}
其实不管是法1还是法2,关键点就俩:
T:给你一个整数数组 nums 和一个整数 k ,请你返回其中出现频率前 k 高的元素。你可以按 任意顺序 返回答案。
提示:
进阶:你所设计算法的时间复杂度 必须 优于 O(n log n) ,其中 n 是数组大小。
S:介于时间复杂度的要求和暗示,应该是要用二叉树或类似结构。
堆是一棵完全二叉树,树中每个结点的值都不小于(或不大于)其左右孩子的值。 如果父亲结点是大于等于左右孩子就是大顶堆,小于等于左右孩子就是小顶堆。
本题就非常适合使用优先级队列来对部分频率进行排序。
在Java中用大顶堆更加简单
public int[] topKFrequent(int[] nums, int k) {
if (nums == null || nums.length <= 1 || k < 1) return nums;
Map map = new HashMap<>();
for (int num : nums) {
map.put(num, map.getOrDefault(num, 0) + 1);//这个方法好!
}
//用优先队列:加入队列会自动按规则排序
PriorityQueue queue = new PriorityQueue<>((pair1, pair2) -> pair2[1] - pair1[1]);
for (Map.Entry entry : map.entrySet()) {
queue.add(new int[]{entry.getKey(), entry.getValue()});
}
int[] res = new int[k];
for (int i = 0; i < k; ++i) {
// res[i] = queue.poll();
res[i] = queue.poll()[0];
}
return res;
}
既然题目中说“返回其中出现频率前 k 高的元素,顺序任意”,那么用小顶堆也完全没问题,只要队列中元素达到k个以后,每次新加元素的时候,看看要不要把小的踢出去就行~
PriorityQueue queue = new PriorityQueue<>((pair1, pair2) -> pair1[1] - pair2[1]);
for (Map.Entry entry : map.entrySet()) {
if (queue.size() < k) {
queue.add(new int[]{entry.getKey(), entry.getValue()});
} else if (entry.getValue() > queue.peek()[1]) {
queue.poll();
queue.add(new int[]{entry.getKey(), entry.getValue()});
}
}