力扣刷题Day11

239.滑动窗口最大值

题目:力扣

优先队列实现

这道题在前一个月有刷到过,当时花了很久才理解力扣题解上的思路,今天再看到这道题的时候感觉只能想起来采用优先队列维护一个元素为k的大顶堆,每次遍历返回顶端元素 - 也就是最大的那个元素

那么其实还需要考虑的点就是边界问题 - 如何判断数组中的数当前是在滑动窗口当中呢?

这里就有个很巧妙的方法实现 - 在构造优先队列的时候,不光储存元素的值,同时储存元素的index,它的排序方式是

  • 先比较元素值,元素值大的放在前面 - 倒序
  • 若元素值相同,那么判断index值,index值小的放在前面

这样在移动滑动窗口的时候,就可以直接判断堆顶的元素是否超出滑动窗口的左边界。

        若超出则直接弹出就可以。//注意,这个判断不能只判断一次,需要考虑可能弹出后的堆顶元素仍然超出边界,需要把所有的都弹出 - 因此采用while循环而不是if

另一个需要注意的点是

        需要先将新的元素放入然后再进行边界判断 //需要考虑一种极端情况,k=1,那么如果先进行边界判断再将新元素放入,采用while循环判断会导致最后队列中不存在元素,会报空指针错误

整体代码实现:

public int[] maxSlidingWindow(int[] nums, int k) {
        //初始化优先队列和结果集
        PriorityQueue<int[]> window = new PriorityQueue<>( (o1, o2) ->{
            if(o1[0] != o2[0]){
                return o2[0] - o1[0];
            }else{
                return o1[1] - o2[1];
            }
        });
        
        int[] result = new int[nums.length-k+1];

        //初始化第一个大顶堆和第一个结果
        for(int i=0; i<k; i++){
            window.offer(new int[]{nums[i], i});
        }
        result[0] = window.peek()[0];
        
        for(int i=k; i<nums.length; i++){
            window.offer(new int[]{nums[i], i});
            while(window.peek()[1] < i-k+1){
                window.poll();
            }//这里需要把所有的超出边界的index都弹出;
            //因此不能只判断一次
            result[i-k+1] = window.peek()[0];
        }

        return result;

    }

单调队列实现

采用优先队列实现的时候,其实并没有严格地维护一个窗口,优先队列中储存的元素会超过滑动窗口中的数量。

其实很多的元素没有必要去维护,只需要去维护最有可能是最大值的元素

因此可以采用单调队列来实现:

例子 -- 输入: nums = [1,3,-1,-3,5,3,6,7], 和 k = 3

在left为0,right为2的窗口中 

index =0,首先将1放入队列;index向右移动

index =1,此时3比1要大,因此3肯定是比1 更可能成为这个窗口的最大值的,把1弹出,放入3

index = 2,-1比3要小,因此将-1放到3的后面[-1的index要比3大,-1有可能是后面的窗口最大值,因此不能删掉]

此时将队首元素存入结果集。

在left为1,right为3的窗口中 

index = 3, -3比-1小,因此将-3放入

判断队首元素是否在边界中,如果在则将队首元素存入结果集。

在left为2,right为4的窗口中

index = 5, 5比-3大,删除-3;5比-1大,删除-1; 5比3大,删除3,说明5更有可能成为在窗口[2,4]中的最大值,将5储存。

判断队首元素是否在边界中,如果在则将队首元素存入结果集。

...

整体代码思路:

  • 单调队列中储存的是元素下标而不是元素的值 - 用于判断临界值
  • 单调队列中队头为最大值的下标,整个队列单调递减,队尾的元素最小
  • 在窗口中,放入的元素比队尾的元素大,则将队尾元素移除。继续把要放入元素和队尾元素比较 - while循环
  • 一直到队尾元素比放入元素大 或者 队列为空时,将元素放入队尾。
  • 判断此时队头的下标是否超出了边界,超出则移除 - while循环
  • 最后返回此时队头的值

代码实现:

public int[] maxSlidingWindow(int[] nums, int k) {
      LinkedList<Integer> queue = new LinkedList<>();
     
      int[] result = new int[nums.length-k+1];

        for(int right=0; right<nums.length;right++){
            //判断队尾元素
            while(!queue.isEmpty() && nums[queue.peekLast()] <= nums[right]){
                queue.pollLast();
            }
                
            //添加新元素到队尾
            queue.addLast(right);
            
            //计算左边界
            int left = right - k+1;

            //移除队头超出边界的元素
            while(queue.peekFirst() < left){
                queue.pollFirst();
            }
            
            //存放结果
            if(right-k+1>=0){
                result[right-k+1] = nums[queue.peekFirst()];
            }
        }
       

        return result;

    }

参考资料:代码随想录

347.前 K 个高频元素

题目:力扣

这道题同样是可以采用优先级队列 - 求前k个高频元素,也就是维护长度为k的小顶堆

        为什么是小顶堆呢?

因为如果采用大顶堆,那么在k之后的也就是频率低的元素并不在堆顶,每次移动更新堆的时候,并不能找到那个在k之后的元素将其弹出;

而用小顶堆的话,在k之后的元素会被挤到堆顶,那么每次在更新的时候就可以很方便的将其弹出,完成小顶堆的更新。

        因此队列会是长度为k单调递增的队列。

对于统计频率,非常常用的一个操作是通过map来储存频率。

这里的操作总共分为三步:

  1. 遍历数组。用map来储存频率;
  2. 然后将map中的key,value的键值对放入优先队列中,并维护一个k长度的优先队列
  3. 最后返回优先队列中的元素的key

具体的代码实现:

 public int[] topKFrequent(int[] nums, int k) {
        HashMap<Integer, Integer> map = new HashMap<>();
        for(int i : nums){
            map.put(i, map.getOrDefault(i, 0)+1);
        }

        PriorityQueue<Map.Entry<Integer, Integer>> queue = new PriorityQueue<>((o1, o2) ->{
            return o1.getValue() - o2.getValue();
        });

        for(Map.Entry<Integer, Integer> o : map.entrySet()){
            queue.offer(o);

            if(queue.size() > k){//使用优先队列的话,只需要判断长度是否超过k,
              //因为将新的元素放入队列中会自动排序的,不需要进行value的大小比较
                queue.poll();
            }
        }

        int[] result = new int[k];

        for(int i= k-1; i >=0; i--){
            result[i] = queue.poll().getKey();
        }

        return result;
    }

 参考资料:代码随想录

71. 简化路径

题目:力扣

这道题实在模拟Linux操作系统中的pwd查找路径。

在对字符串用 ‘/’ 进行分割之后,可以发现,有四种不同的字符

  • 空字符串 - 当出现连续的///的时候就会分割出空字符串
  • 一个点
  • 两个点
  • 文本串 - 即目录名

1. 对于空字符串和一个点,是不需要进行处理的 - 也就是输出路径中可以忽略的。

2. 对于两个点是需要返回到上一级 - 因此可以采用栈来储存之前的目录,遇到两个点,若栈不为空,则直接弹出栈顶。

3. 对于文本串则是将其放入栈中,说明是下一级目录。

实现代码:

    public String simplifyPath(String path) {
        String[] pathlist = path.split("/");

        LinkedList<String> stack = new LinkedList<>();

        for(String s : pathlist){
            if(s.equals("") || s.equals(".")){ //条件1
                continue;
            }else if(s.equals("..")){//条件2
                if(!stack.isEmpty()){
                    stack.poll();
                }
            }else{//条件3
                stack.push(s);
            }
        }

        String result = "";
        while(!stack.isEmpty()){
            result = "/"+ stack.poll() + result;
        }

        if(result.equals("")){//输出的时候注意,如果是空字符,那么说明stack是空的,此时是有一个“/”
            return "/";
        }

        return result;
    }

参考资料:力扣

栈与队列总结

栈在系统中的应用是非常广泛的。

递归的实现是栈:每一次递归调用都会把函数的局部变量、参数值和返回地址等压入调用栈中,然后递归返回的时候,从栈顶弹出上一次递归的各项参数,所以这就是递归为什么可以返回上一层位置的原因。

Java中的栈目前用的最多的是采用DequeArray, Linkedlist这类双端队列用来实现栈的。

栈可以解决:括号匹配问题、字符串去重问题、波兰表达式

在java中PriorityQueue 优先队列是非常有用的队列。

可以用来排序、维护k长度的大顶堆/小顶堆

用于解决:滑动窗口最大值问题、前k个高频元素。

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值