leetcode刷题之栈与队列

例题1有效的括号

20. 有效的括号 - 力扣(LeetCode)

给定一个只包括 ‘(’,‘)’,‘{’,‘}’,‘[’,‘]’ 的字符串 s ,判断字符串是否有效。

有效字符串需满足:

  1. 左括号必须用相同类型的右括号闭合。
  2. 左括号必须以正确的顺序闭合。

此题是栈的经典应用,遇到左括号压栈,遇到右括号则弹出栈并与之匹配,如果不匹配直接返回false

最后判断栈是否为空,栈不为空也返回false

public boolean isValid(String s) {
    Stack<Character> stack=new Stack<>();
    for(int i=0;i<s.length();i++){
        char c=s.charAt(i);
        if(c=='(' || c=='[' || c=='{'){
            stack.push(c);          //左括号压栈
        }else{
            if(stack.isEmpty()) return false;
            char temp=stack.pop();   //右括号则弹指栈
            if(c==')' && temp!='(') return false;
            if(c==']' && temp!='[') return false;
            if(c=='}' && temp!='{') return false;  //如果括号不匹配,返回false
        }
    }
    return stack.isEmpty();    //最后看栈是否为空
}

例题2删除字符串的所有相邻重复项

1047. 删除字符串中的所有相邻重复项 - 力扣(LeetCode)

给出由小写字母组成的字符串 S,重复项删除操作会选择两个相邻且相同的字母,并删除它们。

在 S 上反复执行重复项删除操作,直到无法继续删除。

在完成所有重复项删除操作后返回最终的字符串。答案保证唯一。

public String removeDuplicates(String s) {
    Stack<Character> stack=new Stack<>();
    StringBuilder stringBuilder=new StringBuilder();   //收集最后结果
    for(int i=s.length()-1;i>=0;i--){
        if(!stack.isEmpty() && stack.peek()==s.charAt(i)){
            stack.pop();   //如果栈非空且与栈顶元素相同,则弹出
        }else{ 
            stack.push(s.charAt(i)); //反之则直接压入栈中
        }
    }
    while(!stack.isEmpty()){
        stringBuilder.append(stack.pop());    //从栈中依次弹出并加入StringBuilder
    }
    String res=stringBuilder.toString();   //将结果转换位字符串输出
    return res;
}

之所以选择从右向左处理,是因为从栈弹出时顺序较原先是相反的

这时从右向左,处理完毕后弹出正好是原来的顺序

例题3前k个高频元素

347. 前 K 个高频元素 - 力扣(LeetCode)

给你一个整数数组 nums 和一个整数 k ,请你返回其中出现频率前 k 高的元素。你可以按 任意顺序 返回答案。

public int[] topKFrequent(int[] nums, int k) {
    Map<Integer,Integer> map=new HashMap<>();
    for(int i=0;i<nums.length;i++){
        map.put(nums[i],map.getOrDefault(nums[i],0)+1);    //将元素和频次存入map中
    }
    Queue<Map.Entry<Integer,Integer>> q=new PriorityQueue<>(new Comparator<Map.Entry<Integer, Integer>>() {
        @Override
        public int compare(Map.Entry<Integer, Integer> o1, Map.Entry<Integer, Integer> o2) {
            return o2.getValue()-o1.getValue();
        }
    });                     //设置优先队列的排序规则
    for(Map.Entry<Integer,Integer> e: map.entrySet()){
        q.add(e);       //将map中的每个entry都加入优先队列中
    }
    int[] res=new int[k];
    for(int i=0;i<k;i++){
        res[i]=q.poll().getKey();     //取出前k个元素即为前K个高频元素
    }
    return res;
}

注意优先队列中存放的是map的entry,既有key也有value,只存放其中一个是不对的

优先队列默认是最小优先队列,要重写Comparator接口,设置排序规则

例题四逆波兰式

150. 逆波兰表达式求值 - 力扣(LeetCode)

根据 逆波兰表示法,求表达式的值。

有效的算符包括 +、-、*、/ 。每个运算对象可以是整数,也可以是另一个逆波兰表达式。

注意 两个整数之间的除法只保留整数部分。

可以保证给定的逆波兰表达式总是有效的。换句话说,表达式总会得出有效数值且不存在除数为 0 的情况。

此题是根据后缀表达式计算算数值

遇到数直接压栈,遇到运算符则弹出栈的两个元素,并计算两个数后再压入栈中

注意弹出的顺序,后弹出的元素在前面

public int evalRPN(String[] tokens) {
    Deque<Integer> stack = new LinkedList<Integer>();
    for(int i=0;i<tokens.length;i++){
        String token=tokens[i];
        if(token.equals("+") || token.equals("-") || token.equals("*") || token.equals("/")){
            int num2=stack.pop();
            int num1=stack.pop();    //后弹出的元素运算在前
            switch (token) {
                case "+":
                    stack.push(num1 + num2);
                    break;
                case "-":
                    stack.push(num1 - num2);
                    break;
                case "*":
                    stack.push(num1 * num2);
                    break;
                case "/":
                    stack.push(num1 / num2);
                    break;
                default:
            }
        }else{
            int temp=Integer.valueOf(tokens[i]);
            stack.push(temp);      //遇到数直接压栈
        }

    }
    return stack.pop();
}

例题5滑动窗口最大值(困难题)

239. 滑动窗口最大值 - 力扣(LeetCode)

法一优先队列

public int[] maxSlidingWindow(int[] nums, int k) {
    int[] res=new int[nums.length-k+1];
    Queue<int[]> q=new PriorityQueue<>(new Comparator<int[]>() {
        @Override
        public int compare(int[] o1, int[] o2) {
            return o2[1]-o1[1];
        }
    });         //优先队列存放一个数组,第0个元素是下标,第1个元素是值,按值进行优先队列排序
    for(int i=0;i<k;i++){
        q.offer(new int[]{i,nums[i]});       //先把前k个元素加入优先队列
    }
    res[0]=q.peek()[1];  //先把第一个结果加入
    for(int i=1;i<nums.length-k+1;i++){
        q.offer(new int[]{i+k-1,nums[i+k-1]});   //之后每一步向右滑动一步,加入优先队列
        while(q.peek()[0]<i){
            q.poll();     //最重要一步,在写结果前要判断,优先队列的最大值是否过期
        }                 //当为i时,最大值的下标最少也要为i
        res[i]=q.peek()[1];
    }
    return res;
}

此题最重要的是每步都加入一个新元素,即右侧向右滑动一格,但左侧不需要立即滑动

虽然左侧已经过期,但只要其不是最大值就不影响结果

换句话说,在右侧滑动之后,只要保证最大值下标未过期就可以

如果一个已经过期的比未过期的大,那它肯定会被优先队列弹出,相反一个很小的数可能一直留在优先队列

法二单调队列

public int[] maxSlidingWindow(int[] nums, int k) {
    int n = nums.length;
    Deque<Integer> deque = new LinkedList<Integer>();
    for (int i = 0; i < k; ++i) {
        while (!deque.isEmpty() && nums[i] >= nums[deque.peekLast()]) {
            deque.pollLast();
        }
        deque.offerLast(i);
    }

    int[] ans = new int[n - k + 1];
    ans[0] = nums[deque.peekFirst()];
    for (int i = k; i < n; ++i) {
        while (!deque.isEmpty() && nums[i] >= nums[deque.peekLast()]) {
            deque.pollLast();
        }
        deque.offerLast(i);
        while (deque.peekFirst() <= i - k) {
            deque.pollFirst();
        }
        ans[i - k + 1] = nums[deque.peekFirst()];
    }
    return ans;
}

你可能感兴趣的:(leetcode刷题,算法笔记,leetcode,算法,数据结构)