【力扣一刷】代码随想录day13(239、347、 栈与队列总结)

目录

【239. 滑动窗口最大值】- 第1道困难题

方法一  暴力解法,超时

方法二  使用自定义规则的双端队列实现

【347. 前K个高频元素】

【栈与队列总结】


【239. 滑动窗口最大值】- 第1道困难题

方法一  暴力解法,超时

提交结果:49 / 51 个通过的测试用例,还剩2个没运行完就超时了

总结:做了一定的优化,即如果上一个滑动窗口的最大值不是上一个滑动窗口最左边的值,就只需要将上一个滑动窗口的最大值(交集的最大值)与当前滑动窗口最右边的值进行比较,两者更大的值就是当前滑动窗口的最大值。但是,最坏的情况下,依然是O(n*k)的时间复杂度。

class Solution {
    public int getLeftMax(int[] nums, int start, int k, int max){
        int leftMax;
        if (max > nums[start]){
            return max;
        }
        else{
            leftMax = nums[start + 1];
            for (int i = start + 1; i < start + k; i++){
                if (nums[i] > leftMax) leftMax = nums[i];
            }
        }
        return leftMax;
    }

    public int[] maxSlidingWindow(int[] nums, int k) {

        // 返回的数组长度为 nums.length - k + 1
        int[] res = new int[nums.length - k + 1];

        // 获取第一个窗口的最大值
        res[0] = nums[0];
        for (int i = 0; i < k; i++){
            if (nums[i] > res[0]) res[0] = nums[i];
        }

        // 将上个窗口的最大值和当前窗口的新值比较,获取当前窗口的最大值
        for (int i = 1; i <= nums.length - k; i++){
            // 获取当前窗口的最右边的值(新值)
            int newValue = nums[i+k-1];
            // 获取上一个窗口和当前窗口交集的最大值
            int leftMax = getLeftMax(nums, i-1, k, res[i-1]);
            // 新值与交集的最大值比较,取更大的作为当前窗口最大值
            res[i] = Math.max(leftMax, newValue);
        }
        return res;
    }
}

方法二  使用自定义规则的双端队列实现

思路:利用双端队列实现,队列只存可能为最大值的值,队头始终是最大值

1、设定双端队列的规则

队头始终是队列的最大值,队列始终是降序排序的。

2、利用push(int x)方法维护队列规则

需要先将队列中x前面比x小的值弹出,再将x压入队列。

3、利用for循环遍历数组中的每个元素

  • 当还没遍历到k个值时,只需要将遍历到的元素压入队列
  • 当遍历到第k个值时,还要记录当前滑动窗口的最大值,弹出窗口最左边的值

注意:

当前窗口最左边的值很可能在【将当前遍历元素push进队列】的时候就已经弹出(维护规则所需),因此要加判断条件。

  • 如果队列的第一个值不是当前窗口最左边的值,则证明之前已经被弹出;
  • 如果队列的第一个值是当前窗口最左边的值,则需要弹出。

附上卡哥的视频讲解单调队列正式登场!| LeetCode:239. 滑动窗口最大值

class Solution {
    // 队列deque维护的规则是,始终队头的元素是最大的,队头到队尾是降序的
    Deque<Integer> deque = new ArrayDeque<>();

    // 将当前窗口的新值压入队列
    public void push(int x){
        // 1.将队列中x前面比x小的值弹出
        while (!deque.isEmpty() && deque.getLast() < x) deque.removeLast();
        // 2.将x压入队列
        deque.addLast(x);
    }

    public int[] maxSlidingWindow(int[] nums, int k) {
        int[] res = new int[nums.length - k + 1];
        int num = 0;
        for(int i = 0; i < nums.length; i++){
            // 当还没遍历到k个值时,只需要将遍历到的元素压入队列
            push(nums[i]);
            // 当遍历到第k个值时,就要记录当前滑动窗口的最大值,弹出窗口最左边的值
            if (i >= k - 1){
                res[num++] = deque.getFirst();  // 记录最大值,由于队头始终最大,获取队头即可
                if (deque.getFirst() == nums[i-k+1]) deque.removeFirst();  // 若最左边的值还在队列中,则弹出
            }
        }
        return res;
    }
}

时间复杂度: O(n),所有操作相当于n个元素分别被push和pop一次,因此还是O(n)

空间复杂度: O(k),最坏的情况下,将滑动窗口内k个元素都push进队列

【347. 前K个高频元素】

思路:Map(统计频次)+PriorityQueue(构建大小为k的小顶堆,对频次进行排序)

1、计算各元素出现的频率表map,key为元素值,value为出现的频次

2、创建小顶堆,指定排序方式(升序:o1-o2   降序:o2-o1)

传入的是数组,第一个是整数,第二个是整数出现的频次

必须传入数组,因为虽然排序的是频次,但还要记录频次对应的整数是哪个,用于返回结果

3、遍历map,利用大小为k的小顶堆获取频率前k高的元素

4、返回结果,注意要将小顶堆弹出的值倒序存入res(res是从大到小,频次前k高的整数)

class Solution {
    public int[] topKFrequent(int[] nums, int k) {
        // 1、计算各元素出现的频率表map,key为元素值,value为出现的频次
        Map<Integer, Integer> map = new HashMap<>();
        for (int i = 0; i < nums.length; i++) map.put(nums[i], map.getOrDefault(nums[i], 0) + 1);

        // 2、创建小顶堆,指定排序方式(升序:o1-o2   降序:o2-o1)
        // 传入的是数组,第一个是整数,第二个是整数出现的频次
        // 必须传入数组,因为虽然排序的是频次,但是还要记录频次对应的整数是哪个,用于返回结果
        PriorityQueue<int[]> pq = new PriorityQueue<>((o1, o2) -> o1[1] - o2[1]); // 小顶堆是升序

        // 3、遍历map,利用大小为k的小顶堆获取频率前k高的元素
        for (Integer key: map.keySet()){
            int[] pair = new int[2];
            pair[0] = key; // pair的第一个元素是整数
            pair[1] = map.get(key); // pair的第二个元素是整数出现的频次

            pq.offer(pair); // 将pair加入堆进行排序,调整小顶堆

            // 如果小顶堆的大小大于k,就将最小的堆顶元素弹出,始终保持堆的大小为k
            if (pq.size() > k){
                pq.poll();
            }
        }

        // 4、返回结果
        int[] res = new int[k];
        for(int i = k - 1; i >= 0; i--){
            res[i] = pq.poll()[0]; // 获取的是pair中的第一个元素,即整数
        }
        return res;
    }
}

时间复杂度: O(nlogk),遍历map是O(n),遍历过程还要维护小顶堆O(logk),相乘得O(nlogk)

空间复杂度: O(n),如果nums中每个元素值都不相同,那么map就是O(n)

【栈与队列总结】

一、利用Deque实现栈和队列(官方推荐)


1、创建Deque对象
ArrayDeque是基于数组实现的双端队列,而LinkedList是基于链表实现的双端队列。

  • Deque<> deque = new ArrayDeque<>();  // 基于数组实现
  • Deque<> deque = new LinkedList<>();  // 基于链表实现

2、Deque接口定义的常用方法

  • 针对<栈>的方法:push()和pop()
  • 针对<队列>的方法:offer()和poll()

二、优先级队列PriorityQueue的使用


1、利用PriorityQueue构建小顶堆/大顶堆的方式

PriorityQueue<int> pq = new PriorityQueue<>((o1, o2) -> o1 - o2); // 升序 -> 小顶堆

PriorityQueue<int> pq = new PriorityQueue<>((o1, o2) -> o2 - o1); // 降序 -> 大顶堆


2、堆压入元素/弹出元素

pq.offer(value);  // 压入元素,自动调整堆的排序

pq.poll();  // 弹出堆顶元素(最小值/最大值)


3、获取堆的大小

int size = pq.size();

  • 6
    点赞
  • 9
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值