栈和队列篇总结

2024-05-31 12:24:46 浏览数 (1)

20 有效括号

题目:

给定一个只包括 '('')''{''}''['']' 的字符串 s ,判断字符串是否有效。 有效字符串需满足:

  1. 左括号必须用相同类型的右括号闭合。
  2. 左括号必须以正确的顺序闭合。
  3. 每个右括号都有一个对应的相同类型的左括号。

示例 1: 输入:s = "()" 输出:true 示例 2: 输入:s = "()[]{}" 输出:true 示例 3: 输入:s = "(]" 输出:false 提示:

  • 1 <= s.length <= 104
  • s 仅由括号 '()[]{}' 组成

思路

因为题目中给出了s中的括号由(){}[]三者组成。遍历字符串, 如果遇到( 就将) 添加进入栈, 如果遇到[ 就将]添加进入栈, {也是相同的方式。

为什么用这种方式呢, 因为题目中说了

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

所以如果最终出现的括号顺序没错, 那么肯定是先({[ 然后再其他的。

之后在遍历到)]}的时候, 我们就可以直接进行判断, 如果stack.peek() == s[i]那么就弹出栈 .

最后判断栈是否为空, 如果为空说明我们所有的判断都是对的, 刚好全部匹配上。 反之,如果最后栈不为空或者匹配的时候就出现栈为空的现象, 那么就说明不能匹配成功,直接返回false即可

实现

代码语言:javascript复制
class Solution {
    public boolean isValid(String s) {
        if(s.length() % 2 != 0) return false;
        Stack<Character>st = new Stack<>();

        //弹出匹配
        for(int i= 0 ;i< s.length();i  ){
            if(s.charAt(i) == '('){
                st.push(')');
            }else if (s.charAt(i) == '{'){
                st.push('}');
            }else if( s.charAt(i) == '['){
                st.push(']');
            }else if(st.isEmpty() || st.peek() != s.charAt(i)){
                return false;
            }else {
                st.pop();
            }
        }
        return st.isEmpty();
    }
}

150 逆波兰表达式

题目:

给你一个字符串数组 tokens ,表示一个根据 逆波兰表示法 表示的算术表达式。 请你计算该表达式。返回一个表示表达式值的整数。 注意:

  • 有效的算符为 ' ''-''*''/'
  • 每个操作数(运算对象)都可以是一个整数或者另一个表达式。
  • 两个整数之间的除法总是 向零截断
  • 表达式中不含除零运算。
  • 输入是一个根据逆波兰表示法表示的算术表达式。
  • 答案及所有中间计算结果可以用 32 位 整数表示。

示例 1: 输入:tokens = ["2","1"," ","3","*"] 输出:9 解释:该算式转化为常见的中缀算术表达式为:((2 1) * 3) = 9 示例 2: 输入:tokens = ["4","13","5","/"," "] 输出:6 解释:该算式转化为常见的中缀算术表达式为:(4 (13 / 5)) = 6 示例 3: 输入:tokens = ["10","6","9","3"," ","-11","*","/","*","17"," ","5"," "] 输出:22 解释:该算式转化为常见的中缀算术表达式为: ((10 * (6 / ((9 3) * -11))) 17) 5 = ((10 * (6 / (12 * -11))) 17) 5 = ((10 * (6 / -132)) 17) 5 = ((10 * 0) 17) 5 = (0 17) 5 = 17 5 = 22 提示:

  • 1 <= tokens.length <= 104
  • tokens[i] 是一个算符(" ""-""*""/"),或是在范围 [-200, 200] 内的一个整数

逆波兰表达式: 逆波兰表达式是一种后缀表达式,所谓后缀就是指算符写在后面。

  • 平常使用的算式则是一种中缀表达式,如 ( 1 2 ) * ( 3 4 )
  • 该算式的逆波兰表达式写法为 ( ( 1 2 ) ( 3 4 ) * )

逆波兰表达式主要有以下两个优点:

  • 去掉括号后表达式无歧义,上式即便写成 1 2 3 4 * 也可以依据次序计算出正确结果。
  • 适合用栈操作运算:遇到数字则入栈;遇到算符则取出栈顶两个数字进行计算,并将结果压入栈中

思路

就是一种简单的使用栈的例子。

遍历整个数组, 如果遇到数字 ,就将数字压入栈, 如果遇到符号, 就从栈顶弹出两个数字,进行运算, 然后将运算的结果再压入栈中。 知道遍历完整个数组为止 。

实现

代码语言:javascript复制
class Solution {
    public int evalRPN(String[] tokens) {
        //按照顺序遍历string内容的值, 如果遇到数字就直接压入栈, 如果是符号就从栈中弹出两个数 ,然后进行计算
        Stack<Integer> st = new Stack<>(); 
        for(String item : tokens){
            if(item.equals(" ")){
                //遇到符号 弹出两个数, 然后进行运算,将结果再压入栈
                int num1 = st.pop();
                int num2 = st.pop();
                int res = num1   num2;
                st.push(res);
            }else if (item.equals("-")){
                int num1 = st.pop();
                int num2 = st.pop();
                int res = -num1   num2;
                st.push(res);
            }else if(item.equals("*")){
                int num1 = st.pop();
                int num2 = st.pop();
                int res = num1 * num2;
                st.push(res);
            }else if(item.equals("/")){
                int num1 = st.pop();
                int num2 = st.pop();
                int res = num2 / num1;
                st.push(res);
            }else{ 
                //如果不是以上的符号, 那么就一定是数字, 因为题目中限制了
                st.push(Integer.valueOf(item));
            }
        }
        return st.pop();
    }
}

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 示例 2: 输入:nums = [1], k = 1 输出:[1] 提示:

  • 1 <= nums.length <= 105
  • -104 <= nums[i] <= 104
  • 1 <= k <= nums.length

思路:

本题首先相到的是暴力解法, 通过外层遍历获取每个窗口的位置 ,然后再进行内层循环遍历得到每个窗口中的最大值,思路是可行的, 但是时间复杂度是O(n2) 力扣上是过不去的。

接下来想到的是通过使用队列的方式, 将每个窗口都进行入队列操作, 然后得到的内容再进行, 与此同时再对入队列的每个数进行取最大值操作,然后将得到的最大值写入数组中 。通过这样可以将时间复杂度缩小至O(nlogn)但是再力扣中还是过不了。。。

最终通过别人的题解得到了思路是 使用单调队列。

每次添加进队列的数 都是单调递减的, 如果当前需要添加的数 > 队列中的数 ,那么就需要将队列清空。 必须要保证队列中的数是单调递减的 。 这样一旦滑动窗口, 每个窗口的最大值就算当前队列的头部的。 因为我们在入队列的时候就进行了筛选, 必须保证队列是单调递减的。

与此同时,队列中的数是每个窗口内的数进行入队列, 出队列操作,所以我们操作数的范围因该是[i, i k] 但是存储的是每个数在数组中的索引 ,同时记录的数组范围是 [0 ,i - k 1 ] 所以选择的数 i - k 1 必须大于 才能被加入res数组中。

实现

代码语言:javascript复制
class Solution {
    public int[] maxSlidingWindow(int[] nums, int k) {
        ArrayDeque<Integer> deque = new ArrayDeque<>();
        int n = nums.length;
        int[] res = new int[n - k   1];
        int idx = 0;
        for(int i = 0; i < n; i  ) {
            // 根据题意,i为nums下标,是要在[i - k   1, i] 中选到最大值,只需要保证两点
            // 1.队列头结点需要在[i - k   1, i]范围内,不符合则要弹出
            while(!deque.isEmpty() && deque.peek() < i - k   1){
                deque.poll();
            }
            // 2.既然是单调,就要保证每次放进去的数字要比末尾的都大,否则也弹出
            while(!deque.isEmpty() && nums[deque.peekLast()] < nums[i]) {
                deque.pollLast();
            }
        	//入队列当前数的索引。
            deque.offer(i);

            // 因为单调,当i增长到符合第一个k范围的时候,每滑动一步都将队列头节点放入结果就行了
            if(i >= k - 1){
                res[idx  ] = nums[deque.peek()];
            }
        }
        return res;

        
        
        /**
         int []res = new int[nums.length - k   1];
        Queue<Integer> que = new LinkedList<>();

        //将k个添加进入队列, 然后同时记录最大值
        int max = Integer.MIN_VALUE;
        for(int i= 0 ;i < k; i  ){
            que.add(nums[i]);
            max = Math.max(max, nums[i]);
        }
        int j = 0;
        res[j  ] = max;
        //如果到 k 个了, 那么就将最大值 写入res数组, 判断第一个是不是最大值, 如果是, 那么就需要循环找出队列的最大值, 如果不是 ,那么就直接出队列一个。 然后最大值还是沿用之前的
        for(int i = k ; i < nums.length; i  ){
            //判断出队列的是不是第一个

            int pop = que.poll();
            if(pop == max){
                if(que.isEmpty()){
                    max = Integer.MIN_VALUE;
                }else{
                    max = Integer.MIN_VALUE;
                    for(Integer x : que){
                        max = Math.max(max, x);
                    }
                }
            } 
            //如果不是, 那么就直接添加新的元素 ,然后再判断一次最大值
            que.add (nums[i]);
            max = Math.max(nums[i], max);
            res[j  ] = max;
        }
        return res;
        */
    }
}

347 前k个高频元素

题目:

给你一个整数数组 nums 和一个整数 k ,请你返回其中出现频率前 k 高的元素。你可以按 任意顺序 返回答案。 示例 1: 输入: nums = [1,1,1,2,2,3], k = 2 输出: [1,2] 示例 2: 输入: nums = [1], k = 1 输出: [1] 提示:

  • 1 <= nums.length <= 105
  • k 的取值范围是 [1, 数组中不相同的元素的个数]
  • 题目数据保证答案唯一,换句话说,数组中前 k 个高频元素的集合是唯一的

进阶:你所设计算法的时间复杂度 必须 优于 O(n log n) ,其中 n 是数组大小。

思路

通过Java中的map 实现对每个字符 以及字符出现的次数进行累加。 然后利用stream流的形式再对统计的次数进行排序 (key: 出现的数 value:每个数出现的次数统计

最后通过迭代器遍历map, 将前k个依次添加到数组中返回。

实现

代码语言:javascript复制
class Solution {
    public int[] topKFrequent(int[] nums, int k) {
        int res[] = new int[k];
        Map<Integer, Integer> getAll = new LinkedHashMap<>();
        
        //直接将元素 和 次数全部存储到map中
        for(int i= 0;i < nums.length;i  ){
            if(getAll.containsKey(nums[i])){
                int count = getAll.get(nums[i]);
                count  ;
                getAll.put(nums[i], count);  
            }else{ 
                getAll.put(nums[i], 1 );  // i 表示哪个数  count[i]表示这个数出现的次数
            }
        }
        //通过stream流的形式 实现对value的降序
        LinkedHashMap<Integer, Integer> map = getAll.entrySet().stream()
            .sorted(Map.Entry.comparingByValue(Comparator.reverseOrder()))
            .collect(Collectors.toMap(Map.Entry::getKey, Map.Entry::getValue,
                    (oldV, newV) -> oldV, LinkedHashMap::new));

        Iterator<Integer> iter = map.keySet().iterator();
        int i = 0;
        while( i < k && iter.hasNext()){
            int key=iter.next();
            int value = map.get(key);
            res[i  ] = key;
            System.out.println(key " " value);
        }
        return res;
    }
}

0 人点赞