054、牛客网算法面试必刷TOP101--堆/栈/队列(230509)

文章目录

  • 前言
  • 堆/栈/队列
    • 1、BM42 用两个栈实现队列
    • 2、BM43 包含min函数的栈
    • 3、BM44 有效括号序列
    • 4、BM45 滑动窗口的最大值
    • 5、BM46 最小的K个数
    • ==6、BM47 寻找第K大==
    • ==7、BM48 数据流中的中位数==
    • ==8、BM49 表达式求值==
  • 其它
    • 1、se基础


前言

提示:这里可以添加本文要记录的大概内容:

本文章记录自己刷牛客网算法面试必刷TOP101——堆/栈/队列,部分类容,参见牛客网地址:面试必刷TOP101——堆/栈/队列,总共有8道题目。


提示:以下是本篇文章正文内容

堆/栈/队列

1、BM42 用两个栈实现队列

题目:

  • 用两个栈来实现一个队列,使用n个元素来完成 n 次在队列尾部插入整数(push)和n次在队列头部删除整数(pop)的功能。
  • 队列中的元素为int类型。保证操作合法,即保证pop操作时队列内已有元素。
  • 数据范围: n≤1000;要求:存储n个元素的空间复杂度为 :O(n) ,插入与删除的时间复杂度都是 :O(1);

代码:

public class BM42 {
    Stack<Integer> stack1 = new Stack<Integer>();
    Stack<Integer> stack2 = new Stack<Integer>();

    public void push(int node) {
        stack1.push(node);
    }

    public int pop() {
        if (stack2.isEmpty()) {
            while (!stack1.isEmpty()){
                stack2.push(stack1.pop());
            }
        }
        return stack2.pop();
    }
}

2、BM43 包含min函数的栈

题目:

  • 定义栈的数据结构,请在该类型中实现一个能够得到栈中所含最小元素的 min 函数,输入操作时保证 pop、top 和 min 函数操作时,栈中一定有元素。
  • 此栈包含的方法有:
    • push(value):将value压入栈中;
    • pop():弹出栈顶元素;
    • top():获取栈顶元素;
    • min():获取栈中最小元素;
  • 数据范围:操作数量满足 :0≤n≤300 ,输入的元素满足 :∣val∣≤10000 ;
  • 进阶:栈的各个操作的时间复杂度是 :O(1) ,空间复杂度是 :O(n) ;

代码:

public class BM43 {
    // 利用两个栈
    Stack<Integer> stack1 = new Stack<>();
    Stack<Integer> stack2 = new Stack<>();

    public void push(int node) {
        stack1.push(node);
        if (stack2.isEmpty() || stack2.peek() >= node) {
            stack2.push(node);
        }
    }

    public void pop() {
        int pop = stack1.pop();
        if (stack2.peek() == pop) {
            stack2.pop();
        }
    }

    public int top() {
        return stack1.peek();
    }

    public int min() {
        return stack2.peek();
    }
}

3、BM44 有效括号序列

题目:

  • 给出一个仅包含字符’(‘,’)‘,’{‘,’}‘,’[‘和’]',的字符串,判断给出的字符串是否是合法的括号序列括号必须以正确的顺序关闭,"()“和”()[]{}“都是合法的括号序列,但”(]“和”([)]"不合法。
  • 数据范围:字符串长度 :0≤n≤10000;
  • 要求:空间复杂度 :O(n),时间复杂度 :O(n)。

代码:

public class BM44 {
    public boolean isValid(String s) {
        // write code here
        char[] arr = s.toCharArray();
        // 栈保存的是右括号
        Stack<Character> stack = new Stack<>();
        for (int i = 0; i < arr.length; i++) {
            char c = arr[i];
            if (c == '{') {
                stack.push('}');
            } else if (c == '(') {
                stack.push(')');
            } else if (c == '[') {
                stack.push(']');
            } else {
                if (stack.isEmpty()) {
                    return false;
                }
                // 来到了右括号,就需要判断栈顶的括号和遇到的右括号是否一致
                Character pop = stack.pop();
                if (!pop.equals(c)) {
                    return false;
                }
            }
        }

        return stack.isEmpty();
    }
}

4、BM45 滑动窗口的最大值

题目:

  • 给定一个长度为 n 的数组 num 和滑动窗口的大小 size ,找出所有滑动窗口里数值的最大值。
  • 窗口大于数组长度或窗口长度为0的时候,返回空。
  • 数据范围: 1≤n≤10000,0≤size≤10000,数组中每个元素的值满足 :∣val∣≤10000;
  • 要求:空间复杂度 :O(n),时间复杂度 :O(n)

代码:

public class BM45 {
    public static void main(String[] args) {
        BM45 demo = new BM45();
        int[] arr = {2, 3, 4, 2, 6, 2, 5, 1};
        demo.maxInWindows(arr, 3);
    }

    public ArrayList<Integer> maxInWindows(int[] num, int size) {
        int length = num.length;
        // 结果集
        ArrayList<Integer> ans = new ArrayList<>(length - size + 1);
        // base case
        if (size <= 0 || size > length) {
            return ans;
        }
        // 利用一个双端队列,维护滑动窗口
        // 双端队列中存储的是数组元素的下标(单调队列是从大到小)
        Deque<Integer> deque = new LinkedList<>();
        // 初始化
        for (int i = 0; i < size; i++) {
            push(deque, num, i);
        }
        // 第一个元素结果
        ans.add(num[deque.peekFirst()]);
        // 开始队列弹出
        for (int i = size; i < num.length; i++) {
            // 判断是否需要将第一个元素弹出
            if (deque.peekFirst() == i - size) {
                deque.pollFirst();
            }
            push(deque, num, i);
            ans.add(num[deque.peekFirst()]);
        }
        return ans;
    }

    // 加入元素下标到队列中
    public void push(Deque<Integer> deque, int[] num, int index) {
        if (deque.isEmpty()) {
            deque.addLast(index);
        } else {
            while (!deque.isEmpty() && num[deque.peekLast()] <= num[index]) {
                deque.pollLast();
            }
            deque.addLast(index);
        }
    }
    
}

5、BM46 最小的K个数

题目:

  • 给定一个长度为 n 的可能有重复值的数组,找出其中不去重的最小的 k 个数。
  • 数据范围:0≤k,n≤10000,数组中每个数的大小:0≤val≤1000。
  • 要求:空间复杂度 :O(n) ,时间复杂度 :O(nlogk)。

代码:

public class BM46 {
    public ArrayList<Integer> GetLeastNumbers_Solution(int[] input, int k) {
        // 采用优先级队列
        PriorityQueue<Integer> queue = new PriorityQueue<Integer>((a, b) -> {
            return b - a;
        });
        // 结果集
        ArrayList<Integer> ans = new ArrayList<>();
        if (k <= 0 || k > input.length) {
            return ans;
        }

        for (int i = 0; i < k; i++) {
            queue.offer(input[i]);
        }
        for (int i = k; i < input.length; i++) {
            queue.offer(input[i]);
            queue.poll();
        }
        for (int i = 0; i < k; i++) {
            ans.add(queue.poll());
        }
        return ans;
    }
}

6、BM47 寻找第K大

题目:

  • 有一个整数数组,请你根据快速排序的思路,找出数组中第 k 大的数。
  • 给定一个整数数组 a ,同时给定它的大小n和要找的 k ,请返回第 k 大的数(包括重复的元素,不用去重),保证答案存在。
  • 要求:时间复杂度 :O(nlogn),空间复杂度 :O(1)。

思路:

  • 根据时间复杂度,可以联想到快速排序,每次进行partition都可以知道,排序后基准值在数组中的下标位置;

代码:

public class BM47 {
    public int findKth(int[] a, int n, int K) {
        // write code here
        return findK(a, 0, n - 1, K);
    }

    // 找到第K个大的元素
    public int findK(int[] arr, int left, int right, int K) {
        if (left <= right) {
            int index = partition(arr, left, right);
            // condition
            if (index == K - 1) {
                return arr[index];
            } else if (index < K - 1) {
                return findK(arr, index + 1, right, K);
            } else {
                return findK(arr, left, index - 1, K);
            }
        }
        return -1;
    }

    // 采用快速排序,每次partition都可以将数组中的元素分组,返回排序后基准值在数组中的下标
    // 寻找第K大,就将数组按照从大到小的顺序排序
    public int partition(int[] arr, int left, int right) {
        // 基准值以数组中最左元素
        int pivot = arr[left];
        // 开始分组
        while (left < right) {
            while (left < right && arr[right] <= pivot) {
                right--;
            }
            // 交换
            arr[left] = arr[right];
            while (left < right && arr[left] >= pivot) {
                left++;
            }
            arr[right] = arr[left];
        }
        // 此时是left==right的情况
        arr[left] = pivot;
        return left;
    }

}

7、BM48 数据流中的中位数

题目:

  • 如何得到一个数据流中的中位数?
  • 如果从数据流中读出奇数个数值,那么中位数就是所有数值排序之后位于中间的数值。
  • 如果从数据流中读出偶数个数值,那么中位数就是所有数值排序之后中间两个数的平均值。
  • 我们使用Insert()方法读取数据流,使用GetMedian()方法获取当前读取数据的中位数。
  • 进阶: 空间复杂度 :O(n) , 时间复杂度 :O(nlogn) 。

思路:

  • 维护两个数据结构,一个是大根堆一个是小根堆;

代码:

public class BM48 {

    // 维护一个优先级队列
    // 小的优先级队列
    PriorityQueue<Integer> small = new PriorityQueue<>();
    // 大的优先级队列
    PriorityQueue<Integer> big = new PriorityQueue<>((a, b) -> {
        return b - a;
    });
    // 定义一个变量记录数据流中的元素个数
    int size = 0;

    public void Insert(Integer num) {
        if (size % 2 == 0) {
            // 如果数据流中的元素个数是偶数的话
            // 将元素加入到大根堆中,并将大根堆的最后一个元素放入小根堆
            big.add(num);
            small.offer(big.poll());
        } else {
            // 如果数据流中的元素个数是奇数的话
            small.offer(num);
            big.offer(small.poll());
        }
        size++;
    }

    public Double GetMedian() {
        if (size % 2 == 0) {
            // 偶数
            return (double) (small.peek() + big.peek()) / 2;
        } else {
            return (double) small.peek();
        }
    }
}

8、BM49 表达式求值

题目:

  • 请写一个整数计算器,支持加减乘三种运算和括号。
  • 数据范围:0≤∣s∣≤100,保证计算结果始终在整型范围内。
  • 要求:空间复杂度: O(n),时间复杂度 :O(n)。

思路:

  • 关于表达式求值的问题,采用两个栈结构,一个为数栈(专门存放数字),一个为符号栈(专门存放表达式运算符);
  • 空格 : 跳过。
  • ( : 直接加入 ops 中,等待与之匹配的 )。
  • ) : 使用现有的 nums 和 ops 进行计算,直到遇到左边最近的一个左括号为止,计算结果放到 nums
    数字 : 从当前位置开始继续往后取,将整一个连续数字整体取出,加入 nums。
  • “+ - *” : 需要将操作放入 ops 中。在放入之前先把栈内可以算的都算掉(只有「栈内运算符」比「当前运算符」优先级高/同等,才进行运算),使用现有的 nums 和 ops 进行计算,直到没有操作或者遇到左括号,计算结果放到 nums。

代码:

public class BM49 {
    public int solve(String s) {
        // write code here
        // 消除字符串中的全部空格
        s = s.replaceAll(" ", "");
        char[] charArray = s.toCharArray();
        // 栈结构
        Stack<Integer> nums = new Stack<>();// 数栈
        Stack<Character> operations = new Stack<>();// 操作符栈
        // 首先给数栈中添加一个元素
        nums.push(0);


        // 开始遍历
        for (int i = 0; i < charArray.length; i++) {
            // 当前的char字符
            char c = charArray[i];
            // 判断条件
            if ('(' == c) {
                // 如果是这种括号,就直接加入到符号栈中
                operations.push(c);
            } else if (')' == c) {
                // 如果是 这种括号,就需要进行计算
                while (!operations.isEmpty()) {
                    if ('(' == operations.peek()) {
                        operations.pop();
                        break;
                    } else {
                        calculate(nums, operations);
                    }
                }
            } else {
                // 要么是数字要么是“+ - *”的符号了
                if (isNumber(c)) {
                    // 如果是数字,找到数字加入到集合中
                    int left = i;
                    int num = 0;
                    while (left < charArray.length && isNumber(charArray[left])) {
                        num = num * 10 + (charArray[left] - '0');
                        left++;
                    }
                    i = left - 1;
                    nums.push(num);
                } else {
                    // 如果是符号
                    if (i > 0 && ('(' == charArray[i - 1] || '+' == charArray[i - 1] || '-' == charArray[i - 1])) {
                        nums.push(0);
                    }
                    // 只有当,当前的运算符优先级大于或等于符号栈的栈顶符号优先级时,才会运算
                    while (!operations.isEmpty() && operations.peek() != '(') {
                        // 运算
                        char peek = operations.peek();
                        if (map.get(peek) >= map.get(c)) {
                            calculate(nums, operations);
                        } else {
                            break;
                        }
                    }
                    operations.push(c);
                }
            }
        }

        // 最后计算
        while (!operations.isEmpty()&&operations.peek() !='('){
            calculate(nums,operations);
        }
        return nums.peek();
    }

    // 判断是否为数字
    public boolean isNumber(char c) {
        return Character.isDigit(c);
    }

    // 创建一个hashMap 存储操作符的优先级
    Map<Character, Integer> map = new HashMap() {
        {
            put('+', 1);
            put('-', 1);
            put('*', 2);
        }
    };


    // 操作数栈和符号栈,进行运算
    public void calculate(Stack<Integer> nums, Stack<Character> operations) {
        if (nums.size() < 2 || operations.isEmpty()) {
            return;
        }
        int b = nums.pop();
        int a = nums.pop();
        char operation = operations.pop();
        int ans;
        if ('-' == operation) {
            ans = a - b;
        } else if ('+' == operation) {
            ans = a + b;
        } else {
            ans = a * b;
        }
        nums.push(ans);
    }
}

其它

1、se基础

关于try-catch-finally的知识点:

一旦在finally块中使用了return或throw语句,将会导致try块,catch块中的return,throw语句失效 。

关于类方法的知识点:

类的方法,所谓类的方法就是指类中用static 修饰的方法(非static 为实例方法),比如main 方法,那么可以以main方法为例,可直接调用其他类方法,必须通过实例调用实例方法,this 关键字不是这么用的 。


你可能感兴趣的:(算法,面试,java)