算法训练day9 | php | 239. 滑动窗口最大值, 347.前 K 个高频元素

一、力扣题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];
    }
}

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值