一、力扣题239. 滑动窗口最大值
给你一个整数数组 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)。
更优的方式是用队列来解题,php的队列的底层结构是双层链表,可以从两头取出元素的。要降低每次移动时窗口内获取最大值的时间复杂度,可以自定义一个队列来获取最大值。
先把数组的前 k 个元素放入队列,放入队列时,队列会比较这个元素 x 与队列尾部元素 min 的大小,如果该元素 x 小于 min ,会直接放入;如果大于 min ,会把该尾部元素 min 弹出,然后继续与新的队列尾部元素 min 比较,直到 x 比 min 小或者队列为空时,再把 x 放入队列。这样就能保证队列里一定是从大到小的排序,排在顶部的一定是队列里的最大值。
接下来滑动窗口会移动,要先把被移出滑动窗口的元素 a 与队列顶部元素 max 对比,如果等于 max,就把 a 弹出;如果不等于,说明元素 a 在数组中是排在 max 的前面并小于 max 的,此时队列中根本没有 a 的值(在把元素放入队列时,会把比这个元素小的元素全部弹出队列),所以不需要弹出元素。
然后就需要把滑动窗口新移入的元素放入队列,队列的判断逻辑和上面放入队列的逻辑一样。
这样设置之后,队列中就一定是从大到小的排列,每次滑动窗口滑动后进行一次push 和 pop 操作后,就能直接获取 队顶元素 作为最大值。在这样的遍历过程中,数组中的每个元素至多只会被进行一次push和pop操作,所以时间复杂度为O(n),空间复杂度为O(k)。
class Solution {
/**
* @param Integer[] $nums
* @param Integer $k
* @return Integer[]
*/
function maxSlidingWindow($nums, $k) {
$MyQueue = new MyQueue();
$result = [];
for($i = 0; $i < $k; $i++) {
$MyQueue->push($nums[$i]);
}
$result[] = $MyQueue->front();
for($i = $k; $i < count($nums); $i++) {
$MyQueue->pop($nums[$i - $k]);
$MyQueue->push($nums[$i]);
$result[] = $MyQueue->front();
}
return $result;
}
}
class MyQueue {
private $queue;
function __construct() {
$this->queue = new splQueue();
}
function push($x) {
// top() 为获取队尾元素的值,pop()为弹出队列尾部元素
while(!$this->queue->isEmpty() && $x > $this->queue->top()) {
$this->queue->pop();
}
// enqueue() 为将元素放入队列
$this->queue->enqueue($x);
}
function pop($x) {
// bottom() 为获取队列顶部元素,dequeue() 为弹出队列顶部元素
if($x == $this->queue->bottom()) {
$this->queue->dequeue();
}
}
function front() {
return $this->queue->bottom();
}
}
二、力扣题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
是数组大小。
如果使用寻常解法,就是先用map遍历数组取得每个元素出现的频率(key为元素值,value为元素出现次数),然后把map按照次数从大到小排列,最后取前 k 个元素。这样做的时间复杂度是O(nlogn)。
而本题的需求只是取前k个元素,所以只要在比较过程中留下前 k 个元素就可以了,这样的时间复杂度就是O(nlogk)。
使用到的是最小堆,最小堆会自动把最小的值放在堆顶,当堆中的 k 个元素填满后,继续遍历元素时,把该元素与堆顶元素对比,如果小于堆顶元素,直接跳过,如果大于,则弹出堆顶元素,放入该元素。这样遍历到最后,堆中就只剩下前 k 个元素了。
因为php的数组自由度很大,所以虽说是用map,实际上还是用数组,数组的key放元素值、value放元素的个数。当放入堆中时,放入的是数组 [key, value],因为这两个都需要用到,value用于比较,key 用于放入最后返回的数组中。所以需要对最小堆的比较函数重写。
class Solution {
/**
* @param Integer[] $nums
* @param Integer $k
* @return Integer[]
*/
function topKFrequent($nums, $k) {
$queue = new MyMinHeap;
$res = [];
foreach($nums as $key) {
$res[$key] = isset($res[$key]) ? $res[$key] + 1 : 1;
}
foreach($res as $key => $v) {
if($queue->count() < $k) {
$queue->insert([$key, $v]);
} else if($queue->top()[1] < $v ) {
$queue->extract();
$queue->insert([$key, $v]);
}
}
$res = [];
while(!$queue->isEmpty()) {
$res[] = $queue->extract()[0];
}
return $res;
}
}
class MyMinHeap extends SplMinHeap {
public function compare($value1, $value2) {
return $value2[1] - $value1[1];
}
}