算法题解(栈、队列、堆 篇)

栈与队列理论基础

队列是先进先出,栈是先进后出。

在这里插入图片描述

栈先进后出,如图所示:
在这里插入图片描述
栈提供 pushpop 等等接口,所有元素必须符合先进后出规则,所以栈不提供走访功能,也不提供迭代器(iterator)。 不像是set 或者map 提供迭代器iterator来遍历所有元素。

栈是以底层容器完成其所有的工作,对外提供统一的接口,底层容器是可插拔的(也就是说我们可以控制使用哪种容器来实现栈的功能)。

栈的内部结构,栈的底层实现可以是vector,deque,list 都是可以的, 主要就是数组和链表的底层实现。

在这里插入图片描述deque是一个双向队列,只要封住一段,只开通另一端就可以实现栈的逻辑了。


队列中先进先出的数据结构,同样不允许有遍历行为,不提供迭代器, SGI STL中队列一样是以deque为缺省情况下的底部结构。


232. 用栈实现队列 - 1.28

232. 用栈实现队列
在这里插入图片描述
在这里插入图片描述


解析:维护一个主栈1,添加元素和取队首元素时 判断栈2是否为空,如果为空,则先把栈1的元素转移到栈2,再对栈2进行取值返回。

class MyQueue {

    Stack<Integer> stack1;
    Stack<Integer> stack2;
    public MyQueue() {
        stack1 = new Stack<Integer>(); //主栈
        stack2 = new Stack<Integer>(); //辅助栈
    }
    
    public void push(int x) {
        stack1.push(x); //入栈
    }
    
    public int pop() {
        dumpStackIn();
        return stack2.pop();
    }
    
    public int peek() {
        dumpStackIn();
        return stack2.peek();    
    }
    
    public boolean empty() {
        return stack1.isEmpty() && stack2.isEmpty();
    }

    private void dumpStackIn(){
        // 如果stack2栈为空,则将stack1栈全部弹出并压入stack2栈中
        if(stack2.isEmpty()){
            while(!stack1.isEmpty()){
                stack2.push(stack1.pop());
            }
        }
    }
}

/**
 * Your MyQueue object will be instantiated and called as such:
 * MyQueue obj = new MyQueue();
 * obj.push(x);
 * int param_2 = obj.pop();
 * int param_3 = obj.peek();
 * boolean param_4 = obj.empty();
 */

在这里插入图片描述

225. 用队列实现栈 - 1.28

225. 用队列实现栈
在这里插入图片描述
在这里插入图片描述


解析:使用两个队列a和b,添加元素时,先添加到b中,然后把a中元素添加到b中,再交换b和a,剩下的操作关注a队列即可。

class MyStack {

    Queue<Integer> a;
    Queue<Integer> b;
    public MyStack() {
        a = new LinkedList<>();
        b = new LinkedList<>();
    }
    
    public void push(int x) {
        //暂时放到b队列
        b.offer(x); 
        //将a队列中元素全部转给b队列
        while(!a.isEmpty()){
            b.offer(a.poll());
        }
        //交换a和b,将元素都放到a中
        Queue<Integer> temp;
        temp = a;
        a = b;
        b = temp;
    }
    
    public int pop() {
        return a.poll(); // 因为a中的元素和栈中的保持一致,所以这个和下面两个的操作只看queue1即可
    }
    
    public int top() {
        return a.peek();
    }
    
    public boolean empty() {
        return a.isEmpty();
    }
}

/**
 * Your MyStack object will be instantiated and called as such:
 * MyStack obj = new MyStack();
 * obj.push(x);
 * int param_2 = obj.pop();
 * int param_3 = obj.top();
 * boolean param_4 = obj.empty();
 */

在这里插入图片描述

20. 有效的括号 - 1.28

20. 有效的括号

在这里插入图片描述


解析:创建栈来存储和匹配,遍历获取当前字符,如果是 ( [ { 就直接添加进栈;如果当前字符是’)’,先判断栈是否为空,不为空时取出栈顶元素进行匹配,匹配上就弹出对应的字符,否则返回false。下面的情况类似。

class Solution {
    public boolean isValid(String s) {
        //创建栈来存储和匹配
        Stack<Character> stack = new Stack<>();
        for(int i=0; i<s.length(); i++){
            char c = s.charAt(i); //获取当前字符
            switch(c){
                case '(':
                case '[':
                case '{':
                    stack.push(c); //如果是 ( [ { 就直接添加进栈
                    break;
                case ')':  //如果当前字符是')',先判断栈是否为空,不为空时取出栈顶元素进行匹配,匹配上就弹出对应的字符,否则返回false。下面的情况类似。
                    if(!stack.isEmpty()){
                        if(stack.peek() == '(') stack.pop();
                        else return false;                            
                    }else{
                        return false;
                    }
                    break;
                case ']':
                    if(!stack.isEmpty()){
                        if(stack.peek() == '[') stack.pop();
                        else return false;                            
                    }else{
                        return false;
                    }
                    break;
                case '}':
                    if(!stack.isEmpty()){
                        if(stack.peek() == '{') stack.pop();
                        else return false;                            
                    }else{
                        return false;
                    }
                    break;                                    
            }
        }
        //如果栈为空,说明都匹配成功了,返回true;否则返回false
        return stack.isEmpty(); 

    }
}

在这里插入图片描述

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

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

在这里插入图片描述


解法一:当栈不为空时,判断当前字符与栈顶字符是否相等,若相等就出栈,不相等就入栈;栈为空时,直接入栈。最后转为字符串反转后返回。

class Solution {
    public String removeDuplicates(String s) {
        Stack<Character> stack = new Stack<>();
        char[] ch = s.toCharArray();
        for(char c : ch){
            if(!stack.isEmpty()){
                //当栈不为空时,判断当前字符与栈顶字符是否相等,若相等就出栈
                if(c - 'a' == stack.peek() - 'a'){
                    stack.pop();
                }else{
                    stack.push(c); //不相等就入栈
                }
            }else{
                stack.push(c); //栈为空时,直接入栈
            }
        }
        StringBuilder sb = new StringBuilder();
        while(!stack.isEmpty()){
            sb.append(stack.pop()); //转为字符串
        }
        return sb.reverse().toString(); //反转后
    }
}

在这里插入图片描述

150. 逆波兰表达式求值 - 2.7

150. 逆波兰表达式求值

在这里插入图片描述
在这里插入图片描述


解析:如果是算法运算符则取栈顶的两个元素进行运算,否则就直接入栈

class Solution {
    public int evalRPN(String[] tokens) {
        Deque<Integer> stack = new LinkedList();
        for(String s : tokens){
            //如果是算法运算符则取栈顶的两个元素进行运算,否则就直接入栈
            if(s.equals("+") || s.equals("-") || s.equals("*") || s.equals("/")){
                int a = stack.pop();
                int b = stack.pop();
                int res = 0;
                switch(s){
                    case "+": res = b+a; break;
                    case "-": res = b-a; break;
                    case "*": res = b*a; break;
                    case "/": res = b/a; break;
                }
                stack.push(res); //计算后的结果放入栈中
            }else{
                stack.push(Integer.valueOf(s)); //入栈
            }
        }
        return stack.pop(); //将结果取出后返回
    }
}

在这里插入图片描述

239. 滑动窗口最大值 - 2.8

239. 滑动窗口最大值

给你一个整数数组 nums,有一个大小为 k 的滑动窗口从数组的最左侧移动到数组的最右侧。你只可以看到在滑动窗口内的 k 个数字。滑动窗口每次只向右移动一位。

返回 滑动窗口中的最大值 。

在这里插入图片描述


解析:

遍历数组,将 数 存放在双向队列中,并用 L,R 来标记窗口的左边界和右边界。队列中保存的并不是真的 数,而是该数值对应的数组下标位置,并且数组中的数要从大到小排序。如果当前遍历的数比队尾的值大,则需要弹出队尾值,直到队列重新满足从大到小的要求。刚开始遍历时,L 和 R 都为 0,有一个形成窗口的过程,此过程没有最大值,L 不动,R 向右移。当窗口大小形成时,L 和 R 一起向右移,每次移动时,判断队首的值的数组下标是否在 [L,R] 中,如果不在则需要弹出队首的值,当前窗口的最大值即为队首的数。

在这里插入图片描述

class Solution {
    public int[] maxSlidingWindow(int[] nums, int k) {
        if(nums == null || nums.length < 2) return nums;
        // 双向队列 保存当前窗口最大值的数组位置 保证队列中数组位置的数值按从大到小排序
        LinkedList<Integer> queue = new LinkedList();
        // 结果数组
        int[] result = new int[nums.length-k+1];
        // 遍历nums数组
        for(int i = 0;i < nums.length;i++){
            // 保证从大到小 如果前面数小则需要依次弹出,直至满足要求
            while(!queue.isEmpty() && nums[queue.peekLast()] <= nums[i]){
                queue.pollLast();
            }
            // 添加当前值对应的数组下标
            queue.addLast(i);
            // 判断当前队列中队首的值是否有效
            if(queue.peek() <= i-k){
                queue.poll();   
            } 
            // 当窗口长度为k时 保存当前窗口中最大值
            if(i+1 >= k){
                result[i+1-k] = nums[queue.peek()];
            }
        }
        return result;
    }
}

在这里插入图片描述

*剑指 Offer 09. 用两个栈实现队列 - 12.27

剑指 Offer 09. 用两个栈实现队列

用两个栈实现一个队列。队列的声明如下,请实现它的两个函数 appendTail 和 deleteHead ,分别完成在队列尾部插入整数和在队列头部删除整数的功能。(若队列中没有元素,deleteHead 操作返回 -1 )

在这里插入图片描述


解析:先初始化两个栈,appendTail方法负责添加元素到栈1;调用deleteHead方法时,先把栈1的值转移到栈2中,然后取栈2 栈顶值出栈返回,就是结果,注意记录后还需把栈2的值转移回栈1中去。

class CQueue {
    Stack<Integer> stack1;
    Stack<Integer> stack2;
    public CQueue() {
        stack1 = new Stack<>();
        stack2 = new Stack<>();
    }
    
    public void appendTail(int value) {
        stack1.push(value); //添加元素
    }
    
    public int deleteHead() {
        //当队列中没有元素,返回-1
        if(stack1.isEmpty() && stack2.isEmpty()) return -1;
        //将栈1中的元素转移到栈2中
        while(!stack1.isEmpty()){
            int temp = stack1.pop();
            stack2.push(temp);
        }
        //记录要删除的值
        int result = stack2.pop();
        //把栈2的值再转移到栈1中
        while(!stack2.isEmpty()){
            int temp = stack2.pop();
            stack1.push(temp);
        }        
        return result;
    }
}

/**
 * Your CQueue object will be instantiated and called as such:
 * CQueue obj = new CQueue();
 * obj.appendTail(value);
 * int param_2 = obj.deleteHead();
 */

在这里插入图片描述


解法二:优化上面解法

class CQueue {
    Stack<Integer> stack1;
    Stack<Integer> stack2;
    public CQueue() {
        stack1 = new Stack<>();
        stack2 = new Stack<>();
    }
    
    public void appendTail(int value) {
        stack1.push(value); //添加元素
    }
    
    public int deleteHead() {
        //当队列中没有元素,返回-1
        if(stack1.isEmpty() && stack2.isEmpty()) return -1;
        //如果栈2不为空,则可直接返回;否则,需要将栈1的元素转移到栈2中;
        if(!stack2.isEmpty()){
            return stack2.pop();
        }else {
            while(!stack1.isEmpty()){
                int temp = stack1.pop();
                stack2.push(temp);
            }
        } 
        return stack2.pop();
    }
}

/**
 * Your CQueue object will be instantiated and called as such:
 * CQueue obj = new CQueue();
 * obj.appendTail(value);
 * int param_2 = obj.deleteHead();
 */

在这里插入图片描述


剑指 Offer 30. 包含min函数的栈 - 12.27

剑指 Offer 30. 包含min函数的栈
定义栈的数据结构,请在该类型中实现一个能够得到栈的最小元素的 min 函数在该栈中,调用 min、push 及 pop 的时间复杂度都是 O(1)。

在这里插入图片描述


解析:借助辅助栈来存放递减元素

class MinStack {

    private Stack<Integer> stack;
    private Stack<Integer> minStack;   

    /** initialize your data structure here. */
    public MinStack() {
        stack = new Stack<>();
        minStack = new Stack<>();
    }
    
    public void push(int x) {
        stack.push(x);
        //如果最小栈为空 或 最小栈的栈顶元素大于等于x,就添加元素
        if(minStack.isEmpty() || minStack.peek() >= x){
            minStack.push(x);
        }
    }
    
    public void pop() {
        //主栈出栈,然后比较,如果主栈出栈的元素 等于 最小栈的栈顶元素,那么最小栈也要出栈
        if(stack.pop().equals(minStack.peek())){
            minStack.pop();
        }
    }
    
    public int top() {
        return stack.peek();
    }
    
    public int min() {
        return minStack.peek();
        
    }
}

/**
 * Your MinStack object will be instantiated and called as such:
 * MinStack obj = new MinStack();
 * obj.push(x);
 * obj.pop();
 * int param_3 = obj.top();
 * int param_4 = obj.min();
 */

在这里插入图片描述

*剑指 Offer 59 - I. 滑动窗口的最大值

剑指 Offer 59 - I. 滑动窗口的最大值1

在这里插入图片描述


解法一:

class Solution {
    public int[] maxSlidingWindow(int[] nums, int k) {
        if(nums.length == 0) return nums;
        int left = 0, right = left+k-1; //滑动窗口左右指针
        List<Integer> list = new ArrayList<>();
        while(right <= nums.length-1){
            int max = Integer.MIN_VALUE; //记录最大值
            for(int i=left; i<=right; i++){
                if(nums[i] > max) max = nums[i];  //找到当前窗口的最大值
            }
            list.add(max); //添加进集合
            left++; //移动左指针
            right++; //移动右指针
        }
        //集合赋值给数组返回
        int res[] = new int[list.size()];
        for(int i=0; i<list.size(); i++){
            res[i] = list.get(i);
        }
        return res;
    }
}

在这里插入图片描述

解法二:

参考:滑动窗口的最大值(单调队列,清晰图解)

class Solution {
    public int[] maxSlidingWindow(int[] nums, int k) {
        //单调队列
        //下面是要注意的点:
        //队列按从大到小放入
        //如果首位值(即最大值)不在窗口区间,删除首位
        //如果新增的值小于队列尾部值,加到队列尾部
        //如果新增值大于队列尾部值,删除队列中比新增值小的值,如果在把新增值加入到队列中
        //如果新增值大于队列中所有值,删除所有,然后把新增值放到队列首位,保证队列一直是从大到小
        if (nums.length == 0)   return nums;

        Deque<Integer> deque = new LinkedList<>();
        int[] arr = new int[nums.length - k + 1];
        int index = 0;  //arr数组的下标
        //未形成窗口区间
        for (int i = 0; i < k; i++) {
            //队列不为空时,当前值与队列尾部值比较,如果大于,删除队列尾部值
            //一直循环删除到队列中的值都大于当前值,或者删到队列为空
            while (!deque.isEmpty() && nums[i] > deque.peekLast())  deque.removeLast();
            //执行完上面的循环后,队列中要么为空,要么值都比当前值大,然后就把当前值添加到队列中
            deque.addLast(nums[i]);
        }
        //窗口区间刚形成后,把队列首位值添加到队列中
        //因为窗口形成后,就需要把队列首位添加到数组中,而下面的循环是直接跳过这一步的,所以需要我们直接添加
        arr[index++] = deque.peekFirst();
        //窗口区间形成
        for (int i = k; i < nums.length; i++) {
            //i-k是已经在区间外了,如果首位等于nums[i-k],那么说明此时首位值已经不再区间内了,需要删除
            if (deque.peekFirst() == nums[i - k])   deque.removeFirst();
            //删除队列中比当前值大的值
            while (!deque.isEmpty() && nums[i] > deque.peekLast())  deque.removeLast();
            //把当前值添加到队列中
            deque.addLast(nums[i]);
            //把队列的首位值添加到arr数组中
            arr[index++] = deque.peekFirst();
        }
        return arr;
    }
}

在这里插入图片描述

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

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

在这里插入图片描述


解法一:集合

class MaxQueue {

    List<Integer> list;
    public MaxQueue() {
        list = new ArrayList<>();
    }
    
    public int max_value() {
        if(list.size() == 0) return -1;
        int max = Integer.MIN_VALUE;
        for(int i=0; i<list.size(); i++){
            if(list.get(i) > max) max = list.get(i); //找出最大值
        }
        return max;
    }
    
    public void push_back(int value) {
        list.add(value);
    }
    
    public int pop_front() {
        if(list.size() == 0) return -1;
        int res = list.get(0);
        list.remove(0); //删除首元素
        return res;
    }
}

/**
 * Your MaxQueue object will be instantiated and called as such:
 * MaxQueue obj = new MaxQueue();
 * int param_1 = obj.max_value();
 * obj.push_back(value);
 * int param_3 = obj.pop_front();
 */

在这里插入图片描述

解法二:队列与双向队列

class MaxQueue {

    Queue<Integer> queue;
    Deque<Integer> deque;
    public MaxQueue() {
        queue = new LinkedList<>();
        deque = new LinkedList<>();
    }
    
    public int max_value() {
        //如果双向队列不为空,就返回双向队列首元素
        return deque.isEmpty() ? -1 : deque.peekFirst();
    }
    
    public void push_back(int value) {
        queue.offer(value); //队列正常入队
        while(!deque.isEmpty() && deque.peekLast() < value){
            deque.pollLast();  //如果双向队列的尾元素小于要添加的值,就移除该尾元素
        }
        deque.offerLast(value); //添加进尾部
    }
    
    public int pop_front() {
        if(queue.isEmpty()) return -1;
        if(queue.peek().equals(deque.peekFirst())){
            deque.pollFirst(); //如果两个队列的首元素相等,则双向队列也要弹出首元素
        }
        return queue.poll(); //弹出队列的元素
    }
}

/**
 * Your MaxQueue object will be instantiated and called as such:
 * MaxQueue obj = new MaxQueue();
 * int param_1 = obj.max_value();
 * obj.push_back(value);
 * int param_3 = obj.pop_front();
 */

在这里插入图片描述

单调栈

347. 前 K 个高频元素 - 2.8

347. 前 K 个高频元素

在这里插入图片描述


解析:

在这里插入图片描述


class Solution {
    public int[] topKFrequent(int[] nums, int k) {
        int[] result = new int[k];
        HashMap<Integer, Integer> map = new HashMap<>();
        //构建map映射
        for (int num : nums) {
            map.put(num, map.getOrDefault(num, 0) + 1);
        }
        //将map实体化
        Set<Map.Entry<Integer, Integer>> entries = map.entrySet();
        //根据map的value值正序排,相当于一个小顶堆
        PriorityQueue<Map.Entry<Integer, Integer>> queue = new PriorityQueue<>((o1, o2) -> o1.getValue() - o2.getValue());
        for (Map.Entry<Integer, Integer> entry : entries) {
            queue.offer(entry); //添加元素
            if (queue.size() > k) {
                queue.poll();
            }
        }
        for (int i = k - 1; i >= 0; i--) {
            //从后往前取出 频率前 k 高的元素
            result[i] = queue.poll().getKey();
        }
        return result;
    }
}

在这里插入图片描述

739. 每日温度 - 2.9

739. 每日温度

在这里插入图片描述


解析:单调递减栈

遍历整个数组,如果栈不空,且当前数字大于栈顶元素,那么如果直接入栈的话就不是 递减栈 ,所以需要取出栈顶元素,由于当前数字大于栈顶元素的数字,而且一定是第一个大于栈顶元素的数,直接求出下标差就是二者的距离。

继续看新的栈顶元素,直到当前数字小于等于栈顶元素停止,然后将数字入栈,这样就可以一直保持递减栈,且每个数字和第一个大于它的数的距离也可以算出来。

参考:LeetCode 图解 | 739.每日温度

在这里插入图片描述

class Solution {
    public int[] dailyTemperatures(int[] temperatures) {
        Stack<Integer> stack = new Stack<>();
        int len = temperatures.length;
        int[] res = new int[len];
        for(int i=0; i<len; i++){ //遍历整个数组
            //如果栈不空,且当前数字大于栈顶元素
            while(!stack.isEmpty() && temperatures[i] > temperatures[stack.peek()]){
                int preIndex = stack.pop(); //取出栈顶元素
                res[preIndex] = i - preIndex; //由于当前数字大于栈顶元素的数字,而且一定是第一个大于栈顶元素的数,直接求出下标差就是二者的距离。
            }
            stack.push(i); //入栈
        }
        return res;
    }
}

在这里插入图片描述

496. 下一个更大元素 I - 2.9

496. 下一个更大元素 I

在这里插入图片描述
在这里插入图片描述


解析:HashMap + 单调栈

class Solution {
    public int[] nextGreaterElement(int[] nums1, int[] nums2) {
        Stack<Integer> stack = new Stack<>();
        HashMap<Integer,Integer> map = new HashMap<>();
        int len1 = nums1.length;
        int len2 = nums2.length;
        for(int i=0; i<len2; i++){
            //如果栈不空,且当前数字大于栈顶元素,取出栈顶元素,再映射到map中,方便后面查找
            while(!stack.isEmpty() && nums2[i] > nums2[stack.peek()]){
                int index = stack.pop();
                map.put(nums2[index],nums2[i]); //此时 nums2[i] > nums2[index]
            }
            stack.push(i); //下标入栈
        }
        int[] res = new int[len1];
        for(int i=0; i<len1; i++){
            res[i] = map.getOrDefault(nums1[i], -1); //查找元素是否在num1中出现
        }
        return res;
        
    }
}

在这里插入图片描述

503. 下一个更大元素 II - 2.9

503. 下一个更大元素 II

在这里插入图片描述


解析:循环其实可以遍历的过程中模拟走了两边nums。

class Solution {
    public int[] nextGreaterElements(int[] nums) {
        //边界判断
        if(nums == null || nums.length <= 1){
            return new int[]{-1};
        }
        int len = nums.length; //数组长度
        int[] res = new int[len];
        Arrays.fill(res, -1); //默认全部初始化为-1
        Stack<Integer> stack = new Stack<>(); //栈中存放的是元素下标
        for(int i=0; i<2*len; i++){ // i小于两倍的数组长度
            while(!stack.isEmpty() && nums[i % len] > nums[stack.peek()]){
                int index = stack.pop();
                res[index] = nums[i % len]; // i取余,更新res
            }
            stack.push(i % len); //i取余后入栈
        }
        return res;
    }
}

在这里插入图片描述

NC119 最小的K个数 - 3.11

NC119 最小的K个数

在这里插入图片描述


解析:创建大顶堆,先将前k个数存入堆中,再比较后面的元素,如果当前元素小于堆顶元素,就将堆顶元素弹出,然后再添加当前元素进入堆。

import java.util.ArrayList;
import java.util.Arrays;
import java.util.PriorityQueue;

public class Solution {
    public ArrayList<Integer> GetLeastNumbers_Solution(int [] input, int k) {
        ArrayList<Integer> res = new ArrayList<>();
        if(input.length < k || k == 0){ //特殊处理
            return res;
        }
        //大顶堆
        PriorityQueue<Integer> pq = new PriorityQueue<>((o1,o2) -> o2-o1);
         
        //先将前k个数存入堆中
        for(int i=0; i<k; i++){
            pq.offer(input[i]);
        }
        
        //再比较后面的元素
        for(int i=k; i<input.length; i++){
            //如果当前元素小于堆顶元素,就将堆顶元素弹出,然后再添加当前元素进入堆
            if(input[i] < pq.peek()){
                pq.poll();
                pq.offer(input[i]);
            }
        }
        //用集合来接收
        for(int i=0; i<k; i++){
            res.add(pq.poll());
        }
        
        return res;
    }
}

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值