栈与队列之练习进阶篇

栈与队列之练习进阶篇

一、单调队列(由双端队列实现)

解决问题:连续区间最值问题

1.剑指 Offer 59 - II. 队列的最大值

请定义一个队列并实现函数 max_value 得到队列里的最大值,要求函数max_valuepush_backpop_front均摊时间复杂度都是O(1)

若队列为空,pop_frontmax_value 需要返回 -1

示例 1:

输入: 
["MaxQueue","push_back","push_back","max_value","pop_front","max_value"]
[[],[1],[2],[],[],[]]
输出: [null,null,null,2,1,2]

分析:

1>.题目要求使用队列实现元素的队尾入队操作、队首出队操作以及返回队列元素最大值,且均摊时间复杂度均为O(1)

2>.我们都知道,在队列中,出队以及入队操作的时间复杂度均为O(1),但返回队列元素最大值的一般做法是使用循环遍历队列,找到最大值,其时间复杂度不言而喻是**O(n)**了,那么如何将max_value操作的时间复杂度变为O(1)呢?

3>.引入队列的另一种结构,单调队列,它是通过双端队列实现

4>.单调队列的思想:单调队列的队首一定表示队列中最大的元素,当有元素入队时,若队列为空,以及队列不为空且队尾元素大于该元素时,该元素从队尾入队否则(即队尾元素小于该元素)使用循环将队列中所有小于该元素的元素从队尾出队,再将该元素从队尾入队

5>.当给定区间的元素遍历结束后,若单调队列为空,表示该队列无最大值,否则单调队列的队首元素即为给定连续区间的最大值

class MaxQueue {

    //普通队列
    private Queue<Integer> queue;
    //单调队列
    private LinkedList<Integer> monotonicQueue;

    public MaxQueue() {
        //初始化队列
        this.queue = new LinkedList<>();
        this.monotonicQueue = new LinkedList<>();
    }

    //返回队列中的最大元素
    public int max_value() {
        //若单调队列为空,说明此区间无最大值
        if (this.monotonicQueue.isEmpty()) {
            return -1;
        }
        //否则,单调队列队首元素即为给定区间的最大值
        return this.monotonicQueue.peekFirst();
    }

    //入队操作
    public void push_back(int value) {
        //将元素添加到普通队列
        queue.offer(value);
        
        //将元素添加到单调队列
        while (!this.monotonicQueue.isEmpty() && this.monotonicQueue.peekLast() < value) {  //判断入队元素是否大于队中元素,若大于,循环从队尾移出队列中所有小于此入队元素的元素
            this.monotonicQueue.pollLast();
        }
        this.monotonicQueue.offerLast(value); //从队尾添加该元素
    }

    //出队操作
    public int pop_front() {
        //判断队列是否为空
        if (this.queue.isEmpty()) {
            return -1;
        }
        //记录出队元素
        int temp = this.queue.poll();
        //若出队元素与此时单调队列中最大值相等,也需要将单调队列中的队首(即最大元素)移出
        if (temp == this.monotonicQueue.peekFirst()) {
            this.monotonicQueue.pollFirst();
        }
        return temp;
    }
}
2.滑动窗口最大值(leetcode 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
class Solution {
    //创建一个内部类(表示单调队列类,在类中定义一个单调队列以及入队、出队、返回队列最大值的方法)
    private class MonotonicQueue {
        //定义单调队列
        private LinkedList<Integer> queue;

        public MonotonicQueue() {
            this.queue = new LinkedList<>();
        }

        //入队操作
        public void offer(int value) {
            //如果队列不为空,移出队列中比value值小的元素
            while (!this.queue.isEmpty() && this.queue.peekLast() < value) {
                this.queue.pollLast();
            }
            this.queue.offerLast(value);
        }

        //出队操作
        public int poll() {
            if (this.queue.isEmpty()) {
                return -1;
            }
            return this.queue.pollFirst();
        }

        //求队列最大值
        public int max() {
            if (this.queue.isEmpty()) {
                return -1;
            }
            return this.queue.peekFirst();
        }
    }

    public int[] maxSlidingWindow(int[] nums, int k) {
        //对入参进行判断
        if(nums==null || nums.length==0){
            return nums;
        }

        //创建单调队列
        MonotonicQueue monotonicQueue = new MonotonicQueue();

        //创建集合存放每个区间的最大元素
        LinkedList<Integer> result = new LinkedList<>();

        //遍历数组
        for (int i = 0; i < nums.length; i++) {
            if (i < k - 1) {
                //入队
                monotonicQueue.offer(nums[i]);
            } else {
                monotonicQueue.offer(nums[i]);
                //求三个元素中的最大值
                int max = monotonicQueue.max();
                //将最大元素添加到集合中
                result.add(max);
                //记录移除的元素
                int temp = nums[i - k + 1];
                //如果移除的元素是最大元素,将双端队列中的元素也移除
                if (temp == max) {
                    monotonicQueue.poll();
                }
            }
        }
        //将集合转为数组
        return result.stream().mapToInt(Integer::intValue).toArray();
    }
}

二、单调栈

解决问题:下一个更大元素问题

单调栈结构思想

1>.for循环从区间的最后一个元素开始,直到区间的第一个元素结束

2>.创建一个单调栈,如果栈不为空且对应位置的区间元素大于栈顶元素while循环将栈中小于该元素的所有元素弹出栈,这样在程序执行的过程中,栈顶元素永远为最大元素,当循环结束后,如果栈为空,返回-1否则返回栈顶元素,将其存储在res数组中,再将元素元素压入栈中

3>.for循环结束后,res数组中元素即为给定区间所有元素的下一个更大元素

1.使用单调栈求解区间[2,1,5,3,4,9,6]每一个元素的下一个更大元元素
//暴力法
//int[] nums={2,1,5,3,4,9,6}; 求此区间每个元素的下一个更大元素
        //对入参进行判断
        if(nums==null || nums.length==0){
            return nums;
        }
        //创建一个数组用于存放下一最大元素
        int[] result=new int[nums.length];
        //使用-1填充数组
        Arrays.fill(result,-1);
        //从区间第一个元素开始遍历
        for(int i=0;i<nums.length;i++){
            //内层循环从外层循环的元素之后一个元素开始循环
            for(int j=i+1;j<nums.length;j++){
                //判断第i个元素是否有下一个更大元素
                if(nums[j]>nums[i]){
                    //将第i个元素的下一个更大元素存储到result数组中
                    result[i]=nums[j];
                    break;
                }
            }
        }
        return result;

//使用单调栈解决下一个最大元素
        //对入参进行判断
        if(nums==null || nums.length==0){
            return nums;
        }
        //创建一个单调栈
        Stack<Integer> stack=new Stack<>();
        int[] result=new int[nums.length];
        //从区间最后一个元素开始遍历,直到区间第一个元素
        for(int i=nums.length-1;i>=0;i--){
            //while循环将栈中比nums[i]小的元素全部弹出
            while(!stack.isEmpty() && stack.peek() <=nums[i]){
                stack.pop();
            }
            //如果栈为空,表示该元素后没有下一个大于它的元素,返回-1并存储到result数组中;否则返回栈顶元素
            result[i]=stack.isEmpty()? -1:stack.peek();
            //将该元素压入栈中,继续判断下一个元素
            stack.push(nums[i]);
        }
        return result;
2.每日温度(leetcode 739)

给定一个整数数组 temperatures ,表示每天的温度,返回一个数组 answer ,其中 answer[i] 是指对于第 i 天,下一个更高温度出现在几天后。如果气温在这之后都不会升高,请在该位置用 0 来代替。

示例 1:

输入: temperatures = [73,74,75,71,69,72,76,73]
输出: [1,1,4,2,1,1,0,0]
 public int[] dailyTemperatures(int[] temperatures){
        //对入参进行判断
        if(temperatures==null || temperatures.length==0){
            return temperatures;
        }
        int[] result=new int[temperatures.length];
        //创建一个单调栈,注意此题中,栈中存储的是区间元素的索引
        Stack<Integer> stack=new Stack();
        //从区间的最后一个元素开始
        for(int i=temperatures.length-1;i>=0;i--){
            while(!stack.isEmpty() && temperatures[stack.peek()] <=temperatures[i]){
                stack.pop();
            }
            result[i]=(stack.isEmpty()? 0:(stack.peek()-i));
            stack.push(i);
        }
        return result;
3.下一个更大元素 I(leetcode 496)

nums1 中数字 x下一个更大元素 是指 xnums2 中对应位置 右侧第一个x 大的元素。

给你两个 没有重复元素 的数组 nums1nums2 ,下标从 0 开始计数,其中nums1nums2 的子集。

对于每个 0 <= i < nums1.length ,找出满足 nums1[i] == nums2[j] 的下标 j ,并且在 nums2 确定 nums2[j]下一个更大元素 。如果不存在下一个更大元素,那么本次查询的答案是 -1
返回一个长度为 nums1.length 的数组 ans 作为答案,满足 ans[i] 是如上所述的 下一个更大元素。

示例 1:

输入:nums1 = [4,1,2], nums2 = [1,3,4,2].
输出:[-1,3,-1]
解释:nums1 中每个值的下一个更大元素如下所述:
- 4 ,用加粗斜体标识,nums2 = [1,3,4,2]。不存在下一个更大元素,所以答案是 -1- 1 ,用加粗斜体标识,nums2 = [1,3,4,2]。下一个更大元素是 3- 2 ,用加粗斜体标识,nums2 = [1,3,4,2]。不存在下一个更大元素,所以答案是 -1

解题思路:

方法1:暴力法

1>.创建一个数组,用于存储所求区间的下一个更大元素,首先用fill方法将数组元素全部填充为-1

2>.双层for循环,所求区间1在外,在区间2中找区间1中元素在内层

3>.如果在区间2中找到区间1元素,从区间2该元素的下一个元素开始找其后比它大的元素,此时需要while循环,将找到的结果存储在所创建的数组中,立刻终止while循环

4>.整个循环结束后,返回数组

方法2:利用单调栈

1>.创建一个单调栈两个数组

2>.使用单调栈求出区间2所有元素下一个更大元素存储到res1数组中

3>.双层for循环,找到区间1中元素区间2中的对应位置

4>.取出res1数组对应的元素存储到res2数组中,返回数组res2

//暴力法:时间复杂度O(n^3)
    int[] result=new int[nums1.length];
    Arrays.fill(result,-1);
    //遍历数组nums1
    for(int i=0;i<nums1.length;i++){
        //遍历数组nums2
        for(int j=0;j<nums2.length;j++){
            //找数组nums1中元素在数组nums2的对应位置
            if(nums2[j]==nums1[i]){
                //遍历数组nums1中元素在数组nums2对应位置之后的元素,找下一个大于它的元素
                while(j+1<nums2.length){
                    if(nums1[i]<nums2[j]){
                        //找到下一个更大元素,将结果存储到result数组中
                        result[i]=nums2[j];
                        //立刻终止循环
                        break;
                    }
                    j++;
                }
            }
        }
    }
    return result;

//使用单调栈:时间复杂度O(n^2)
//解决一个区间中的元素在另一区间对应位置之后的下一个更大元素
        //对入参进行判断
        if (nums1 == null || nums2 == null || nums1.length == 0 || nums2.length == 0) {
            return null;
        }
        int[] res1 = new int[nums2.length];
        int[] res2 = new int[nums1.length];
        Stack<Integer> stack = new Stack<>();
        //使用单调栈求出第二个区间每个元素的下一个更大元素,将结果存储到res1数组中
        for (int i = nums2.length - 1; i >= 0; i--) {
            while (!stack.isEmpty() && stack.peek() <= nums2[i]) {
                stack.pop();
            }
            res1[i] = (stack.isEmpty()) ? -1 : stack.peek();
            stack.push(nums2[i]);
        }
        //遍历两个区间,如果区间1中的元素等于区间2中的元素,将res1中对应位置的结果赋给res2数组中返回
        for (int i = 0; i < nums1.length; i++) {
            for (int j = 0; j < nums2.length; j++) {
                if (nums1[i] == nums2[j]) {
                    res2[i] = res1[j];
                    break;
                }
            }
        }
        return res2;
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值