刷算法Leetcode---6(栈与队列篇)

前言

        本文是跟着代码随想录的栈与队列顺序进行刷题并编写的 代码随想录

        开学后又开始刷算法了,果然放假就不容易坚持下来了,还是在学校的生活规律一点

        首先说明一下:

                ①栈和队列的特性:栈是先进先出、队列先进后出

                ②java中的栈使用Deque实现,分为LinkedList和ArrayDeque

        这是力扣刷算法的其他文章链接:刷算法Leetcode文章汇总

栈与队列篇

232.用栈实现队列

需要实现push、pop、peek、empty四个操作,一般使用两个栈来实现一个队列

        ①两个栈AB,栈A作为队尾、栈B作为队头,每次push将所有元素转移至栈A,每次pop或peek将所有元素转移至栈B,判空同时判断栈AB

        ②上面方法可改进为,只用当栈B为空的时候才需要将栈A的所有元素转移过来,不需要每次转移所有元素,其余相同

225.用队列实现栈

实现栈的push、top、pop、empty四个操作

        ①两个队列,队列A用于top或者pop,直接从队头取出;队列B用于辅助入栈,先将元素放入队列B,再将队列A中所有元素转移至队列B,然后将AB互换;保证队列A始终有所有元素,会减少很多判断步骤;判空同时判断AB

        ②一个队列,思想为每次top或pop时保证理论上的栈顶元素在队头,有两种方式:

        a.始终保证栈顶元素在队头:每次入栈时,先将元素置于队尾,再将前面所有元素重新入队

        b.只有获取栈顶时才将栈顶元素移至队头:队列元素按照正常入队顺序放置,pop时将n-1个元素重新入队,但top操作还要将第n个元素也重新入栈

20.有效的括号

        ①核心思想:栈,左括号入栈,右括号匹配,看是否剩余或类型不符

        ②改进:Map存储左右括号的对应关系,减少硬编码

class Solution {
    public boolean isValid(String s) {
        Deque<Character> stack = new ArrayDeque<>();
        for (char c : s.toCharArray()){
            if(c == '(' || c == '{' || c == '['){
                stack.push(c);
            }
            else if(!stack.isEmpty()){
                if((c == ')' && stack.peek() == '(') 
                    || (c == '}' && stack.peek() == '{') 
                    || (c == ']' && stack.peek() == '[')) stack.pop();
                else return false;
            }
            else return false;
        }
        return stack.isEmpty();
    }
}

1047.删除字符串中的所有相邻重复项

        ①核心思想:栈将字符逐个入栈,当入栈字符与栈顶字符相同时,同时舍弃这两个字符。最后使用StringBuilder将栈中每个字符拼接

        ②改进:不使用栈进行判断,直接在构建StringBuilder时,进行拼接字符和StringBuilder的最后一个字符判断,相同则同时舍弃,最后拼接。官方使用StringBuffer,线程更安全但性能稍低

150.逆波兰表达式求值

        ①核心思想:栈,遇到数字就入栈,遇到符号就取出两个栈顶进行运算,将结果重新入栈。注意java中的String判断要使用equals

        ②改进:int[]数组模拟栈,数组的长度只需要(n+1)/2即可,记录数组当前的下标,遇到数字就存储,遇到符号就将下标对应的连续两个数进行运算,并将结果存储

class Solution {
    public int evalRPN(String[] tokens) {
        int n = tokens.length;
        int[] stack = new int[(n+1)/2];
        int index = -1;
        for(String s : tokens){
            if(s.equals("+") || s.equals("-") || s.equals("*") || s.equals("/")){
                if(index < 1) return -1;
                if(s.equals("+")) stack[--index] += stack[index+1];
                else if(s.equals("-")) stack[--index] -= stack[index+1];
                else if(s.equals("*")) stack[--index] *= stack[index+1];
                else if(s.equals("/")) stack[--index] /= stack[index+1];
            }
            else stack[++index] = Integer.parseInt(s);
        }
        return stack[index];
    }
}

239.滑动窗口最大值

        ①优先队列,队列元素为int[],包括value和index,根据元素的value进行降序。窗口滑动后,判断队列的队头下标是否在窗口范围内,不在就poll()

class Solution {
    public int[] maxSlidingWindow(int[] nums, int k) {
        int n = nums.length;
        int[] res = new int[n-k+1];
        PriorityQueue<int[]> maxHeap = new PriorityQueue<>((p1, p2) -> {
            return p2[0] == p1[0] ? p2[1]-p1[1] : p2[0]-p1[0];
        });
        for(int i = 0; i < k; i++) maxHeap.offer(new int[]{nums[i],i});
        res[0] = maxHeap.peek()[0];
        for(int i = k; i < n; i++){
            maxHeap.offer(new int[]{nums[i],i});
            while(maxHeap.peek()[1]<=i-k) maxHeap.poll();
            res[i-k+1] = maxHeap.peek()[0];
        }
        return res;
    }
}

        ②单调队列,有单调性的双端队列,队列只记录下标。对于数组下标i<j,若nums[j]>nums[i],且都在滑动窗口内,则nums[i]之后一定不会是滑动窗口的最大值,因此入队时将更小的队尾出队后再入队;在队头获取每次的最大值时,先将无效的下标出队再获取

        ③分组+最大前后缀,将nums每k个分为一组,预处理计算每个下标在组中的最大前后缀,最大前缀指的是从组头到该值中的最大值,最大后缀为从该值到组尾的最大值;最后滑动窗口的最大值为res[i]=max(suffixMax[i],prefixMax[i+k-1])

class Solution {
    public int[] maxSlidingWindow(int[] nums, int k) {
        int n = nums.length;
        int[] res = new int[n-k+1];
        int[] prefixMax = new int[n];
        int[] suffixMax = new int[n];
        for(int i = 0, j = n-1; i < n; i++, j--){
            if(i % k == 0)
                prefixMax[i] = nums[i];
            else
                prefixMax[i] = Math.max(prefixMax[i-1],nums[i]);
            if(j==n-1 || (j+1)%k==0)
                suffixMax[j] = nums[j];
            else
                suffixMax[j] = Math.max(suffixMax[j+1],nums[j]);
        }
        for(int i = 0; i <= n - k; i++){
            res[i] = Math.max(suffixMax[i],prefixMax[i+k-1]);
        }
        return res;
    }
}

347.前K个高频元素

        ①map+优先队列,使用map统计每个元素个数,PriorityQueue元素为value-num,自定义Comparator根据num逆序;入队时,如果队中已经有k个元素,并且新元素的num大于队尾元素,就将队尾出队后新元素入队

        ②map+List部分快排,使用,ap统计每个元素个数,然后使用List<int[]>记录value-num;该题可以转化为找到第k个高频元素及其前面的元素,但前k-1个的排序并不重要,可以使用部分快排的思想,每次看pivot是否为第k个,并将前面的元素记录至结果数组中

class Solution {
    public int[] topKFrequent(int[] nums, int k) {
        Map<Integer, Integer> map = new HashMap<>();
        for (int num : nums) {
            map.put(num, map.getOrDefault(num, 0) + 1);
        }
        // 获取每个数字出现次数
        List<int[]> values = new ArrayList<>();
        for (Map.Entry<Integer, Integer> entry : map.entrySet()) {
            int num = entry.getKey(), count = entry.getValue();
            values.add(new int[]{num, count});
        }
        int[] res = new int[k];
        quickSortForK(values, 0, values.size() - 1, res, 0, k);
        return res;
    }

    public void quickSortForK(List<int[]> values, int left, int right, int[] ret, int resIndex, int k) {
        int picked = (int) (Math.random() * (right - left + 1)) + left;
        Collections.swap(values, picked, left);

        int pivot = values.get(left)[1];
        int index = left;
        for (int i = left + 1; i <= right; i++) { // 降序
            if (values.get(i)[1] >= pivot) {
                Collections.swap(values, index + 1, i);
                index++;
            }
        }
        Collections.swap(values, left, index);

        if (k <= index - left) {
            quickSortForK(values, left, index - 1, ret, resIndex, k);
        } else {
            for (int i = left; i <= index; i++) {
                ret[resIndex++] = values.get(i)[0];
            }
            if (k > index - left + 1) {
                quickSortForK(values, index + 1, right, ret, resIndex, k - (index - left + 1));
            }
        }
    }
}

        ③map+List排序,map记录每个元素个数,List<int[]>记录value-sum,使用Collections.sort进行排序,再用stream转化为结果数组

import java.util.Map.Entry;

class Solution {
    public int[] topKFrequent(int[] nums, int k) {
        Map<Integer, Integer> map = new HashMap<>();
        for (int num : nums) {
            map.put(num, map.getOrDefault(num, 0) + 1);
        }
        List<Map.Entry<Integer, Integer>> values = new ArrayList<>(map.entrySet());
        Collections.sort(values, Comparator.comparing(Entry::getValue, Comparator.reverseOrder()));
        return values.subList(0, Math.min(k, values.size())).stream().mapToInt(Entry::getKey).toArray();
    }
}

        写这题时一直在尝试j找到java中map有没有一种根据value排序的方法,编写Comparator自定义比较方式,但是没能成功实现,各位能不能帮忙看看能怎么实现一下:

class Solution {
    public int[] topKFrequent(int[] nums, int k) {
        TreeMap<Integer,Integer> treeMap = new TreeMap<>(new ValueComparator());
        for(int num : nums){
            treeMap.put(num,treeMap.getOrDefault(num, 0) + 1);
        }
        int[] res = new int[k];
        int i = 0;
        for(Map.Entry<Integer,Integer> entry : treeMap.entrySet()){
            if(i < k){
                res[i++] = entry.getKey();
            }else break;
        }
        return res;
    }
}

class ValueComparator implements Comparator<Integer>{
    Map<Integer,Integer> base;
    public ValueComparator(){this.base = new TreeMap<>();}
    public ValueComparator(Map<Integer,Integer>base){this.base=base;}
    @Override
    public int compare(Integer key1, Integer key2){
        int value = base.getOrDefault(key1, 0).compareTo(base.getOrDefault(key2, 0));
        if(value==0)return key1.compareTo(key2);
        return value;
    }
}

栈与队列总结

        ①主要特性:栈先进先出、队列先进后出

        ②栈使用场景:括号匹配(要求字符串的顺序和个数)、字符串去重(匹配问题)、逆波兰式求解(计算结果要重新入栈)

        ③队列使用场景:优先队列(要求排序、k个高频元素)、双端队列(需要处理队尾元素)、单调队列(有单调性的双端队列、滑动窗口最大值)

        ④map排序可以转换为List<int[]>,然后用sort排序

        ⑤栈和队列是一种思想,不一定要使用stack和queue来实现,有时使用int[]也可以

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值