牛客编程题--必刷101之堆栈队列篇(下)

95 篇文章 0 订阅
56 篇文章 0 订阅

大家好,我是小曾哥,虽然不是首发用户,但是也来参与参与活动,希望能够得到大家都支持,目前牛客刷题已经到了下一个专题(堆、栈、队列),对应题目力扣应该也是可以搜到的!

文章目录

??一、最小的K个数

??1.题目描述

题目描述:给定一个长度为 n 的可能有重复值的数组,找出其中不去重的最小的 k 个数。例如数组元素是4,5,1,6,2,7,3,8这8个数字,则最小的4个数字是1,2,3,4(任意顺序皆可)。

示例
输入: [4,5,1,6,2,7,3,8],4
返回值:[1,2,3,4]
说明:返回最小的4个数即可,返回[1,3,2,4]也可以

??2.思路 + 代码解析

方法1:直接暴力解

利用数组的排序sort函数

思路:
1、对输入的数组进行从大到小排序
2、遍历数组,取前k个数字放进最后返回的arraylist

import java.util.*;

public class Solution {
    public ArrayList<Integer> GetLeastNumbers_Solution(int [] input, int k) {
        ArrayList<Integer> list = new ArrayList<>();
        if(input == null || input.length ==0 || k > input.length)
            return list;
        Arrays.sort(input);
        for(int i =0 ; i< k;i++){
            list.add(input[i]);
        }
        return list;
        
    }
}

方法2:使用最大堆

要保证堆的大小不能超过K,然后遍历数组,因为是最大堆,也就是堆顶元素是堆中最大的,如果遍历的元素小于堆顶元素,就把堆顶元素给移除,然后再把当前遍历的元素加入到堆中,最后在把堆中元素转化为数组即可。

 public ArrayList<Integer> GetLeastNumbers_Solution(int [] input, int k) {
        ArrayList<Integer> list = new ArrayList<>();
        if(input.length ==0 || k > input.length || k == 0) return list;
        // 创建大根堆
        PriorityQueue<Integer> q = new PriorityQueue<>((o1,o2) ->o2.compareTo(o1));
        // 创建一个k个大小的堆
        for(int i =0; i<k ;i++)
            q.offer(input[i]);
        for(int i =k; i< input.length;i++){
            // 较小元素入堆
            if(q.peek() > input[i]){
                q.poll();
                q.offer(input[i]);
            }
        }
        // 堆中元素取出入列表
        for(int i =0;i<k;i++) list.add(q.poll());
        return list;
    }

方法3:使用快排

对数组[l,r]进行快排切分后得到基准值的索引p,由此有三个区间[l, p), p, [p+1, r)。[l,p)为小于等于p的值
[p+1,r)为大于等于p的值。

若p==k-1,则说明在p之前的数就是前k大的数;
若p<k-1,则说明第k大的在p右边[p+1,r];
若p>k-1,则答案在左边[l,p]。

// 方法1
import java.util.*;
public class Solution {

    int partition(int [] input ,int left,int right){
        int pivot = input[left];
        while(left<right){
            while(left<right&&input[right]>=pivot)
                right--;
            input[left]=input[right];
            while(left<right&&input[left]<=pivot)
                left++;
            input[right]=input[left];
        }
        input[left] = pivot;
        return left;
    }

    public ArrayList<Integer> GetLeastNumbers_Solution(int [] input, int k) {
       ArrayList<Integer> list =new ArrayList();
       if(input.length==0||k>input.length)
           return list;
       int i=0,j=input.length-1;
        while(i<j){
            //切分
            int p  = partition(input,i,j);
            //当找到第k大的数,p+1==j是因为第k大的数可能在最后
            if(p==k-1||p+1==j){
                for(int x=0;x<k;x++)
                    list.add(input[x]);
                return list;
            }
            //当前的数在第k大的左边
            else if(p<k){
                i=p+1;
            }
            //第k大的在当前的数的左边
            else
                j=p;

        }
        return list;
    }
}

// 方法2
public ArrayList<Integer> GetLeastNumbers_Solution(int[] input, int k) {
    ArrayList<Integer> res = new ArrayList<>(k);
    //根据题意要求,如果K>数组的长度,返回一个空的数组
    if (k > input.length || k == 0)
        return res;
    quickSort(input, res, k, 0, input.length - 1);
    return res;
}
 
private void quickSort(int[] input, ArrayList<Integer> res, int k, int left, int right) {
    //快排的实现方式有多种,我们选择了其中的一种
    int start = left;
    int end = right;
    while (left < right) {
        while (left < right && input[right] >= input[start]) {
            right--;
        }
        while (left < right && input[left] <= input[start]) {
            left++;
        }
        swap(input, left, right);
    }
    swap(input, left, start);
    //注意这里,start是数组中元素的下标。在start之前的元素都是比start指向的元素小,
    //后面的都是比他大。如果k==start,正好start之前的k个元素是我们要找的,也就是
    //数组中最小的k个,如果k>start,说明前k个元素不够,我们还要往后再找找。如果
    //k<start,说明前k个足够了,我们只需要在start之前找k个即可。
    if (left > k) {
        quickSort(input, res, k, start, left - 1);
    } else if (left < k) {
        quickSort(input, res, k, left + 1, end);
    } else {
        //取前面的k个即可
        for (int m = 0; m < k; ++m) {
            res.add(input[m]);
        }
    }
}
 
private void swap(int[] arr, int i, int j) {
    if (i == j)
        return;
    int temp = arr[i];
    arr[i] = arr[j];
    arr[j] = temp;
}

??二、寻找第k大

??1.题目描述

题目描述:有一个整数数组,请你根据快速排序的思路,找出数组中第 k 大的数。

给定一个整数数组 a ,同时给定它的大小n和要找的 k ,请返回第 k 大的数(包括重复的元素,不用去重),保证答案存在。

??2.思路+代码解析:快排

public class Solution {
    //常规的快排划分,但这次是大数在左 fast-template
    public int partion(int[] a, int low, int high){
        int temp = a[low];
        while(low < high){
            //小于标杆的在右
            while(low < high && a[high] <= temp)
                high--;
            if(low == high)
                break;
            else
                a[low] = a[high];
            //大于标杆的在左
            while(low < high && a[low] >= temp)
                low++;
            if(low == high)
                break;
            else
                a[high] = a[low];
        }
        a[low] = temp;
        return low;
    }
    public int quickSort(int[] a, int low, int high, int K){
        //先进行一轮划分,p下标左边的都比它大,下标右边都比它小
        int p = partion(a, low, high);
        //若p刚好是第K个点,则找到
        if(K == p - low + 1)
            return a[p];
        //从头到p超过k个数组,则目标在左边
        else if(p - low + 1 > K)
            //递归左边
            return quickSort(a, low, p - 1, K);
        else
            //否则,在右边,递归右边,但是需要减去左边更大的数字的数量
            return quickSort(a, p + 1, high, K - (p - low + 1));
    }
    public int findKth(int[] a, int n, int K) {
        return quickSort(a, 0, n - 1, K);}
}

??三、数据流中的中位数

??1.题目描述

题目描述:
如何得到一个数据流中的中位数?如果从数据流中读出奇数个数值,那么中位数就是所有数值排序之后位于中间的数值。如果从数据流中读出偶数个数值,那么中位数就是所有数值排序之后中间两个数的平均值。我们使用Insert()方法读取数据流,使用GetMedian()方法获取当前读取数据的中位数。

??2.思路 + 代码解析

方法1:无序数组

使用无序数组存储,然后需要对数组进行排序,然后就可以顺利找到中位数

无序数组
import java.util.*;
public class Solution {
    
    ArrayList<Integer> list = new ArrayList<>();

    public void Insert(Integer num) {
        list.add(num);
    }

    public Double GetMedian() {
        Collections.sort(list);
        int index = list.size()/2;
        if(list.size() %2 !=0){
            return (double)list.get(index);
        }
        return ((double)list.get(index-1) + (double)list.get(index))/2;
    }


}

方法2:二分法

public class Solution {
    
    ArrayList<Integer> list = new ArrayList<>();

    public void Insert(Integer num) {
        if(list.size()==0){
            list.add(num);
            return;
        }
        int first =0;
        int last = list.size()-1;
        int mid = 0;
        while(first <= last){
            mid = (first+last)/2;
            if(list.get(mid)> num)
                last = mid-1;
            else
                first = mid+1;
        }
        list.add(first,num);
        return;
    }

    public Double GetMedian() {
        int index = list.size()/2;
        if(list.size() %2 !=0){
            return (double)list.get(index);
        }
        return ((double)list.get(index-1) + (double)list.get(index))/2;
    }
}

??四、表达式求值

??1.题目描述

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

??2.思路:双栈

对于「任何表达式」而言,我们都使用两个栈 nums 和 ops:

  • nums : 存放所有的数字
  • ops :存放所有的数字以外的操作

然后从前往后做,对遍历到的字符做分情况讨论:

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

一些细节:

  • 由于第一个数可能是负数,为了减少边界判断。一个小技巧是先往 nums 添加一个 0
  • 为防止 ()内出现的首个字符为运算符,将所有的空格去掉,并将 (- 替换为 (0-,(+ 替换为(0+(当然也可以不进行这样的预处理,将这个处理逻辑放到循环里去做)
  • 从理论上分析,nums 最好存放的是 long,而不是 int。因为可能存在 大数 + 大数 + 大数 + … - 大数 - 大数 的表达式导致中间结果溢出,最终答案不溢出的情况

??3. 代码详解

import java.util.*;
 
public class Solution {
    // 使用 map 维护一个运算符优先级(其中加减法优先级相同,乘法有着更高的优先级)
    Map<Character, Integer> map = new HashMap<Character, Integer>(){{
        put('-', 1);
        put('+', 1);
        put('*', 2);
    }};
 
    public int solve(String s) {
        // 将所有的空格去掉
        s = s.replaceAll(" ", "");
 
        char[] cs = s.toCharArray();
        int n = s.length();
 
        // 存放所有的数字
        Deque<Integer> nums = new ArrayDeque<>();
        // 为了防止第一个数为负数,先往 nums 加个 0
        nums.addLast(0);
        // 存放所有「非数字以外」的操作
        Deque<Character> ops = new ArrayDeque<>();
 
        for (int i = 0; i < n; i++) {
            char c = cs[i];
            if (c == '(') {
                ops.addLast(c);
            } else if (c == ')') {
                // 计算到最近一个左括号为止
                while (!ops.isEmpty()) {
                    if (ops.peekLast() != '(') {
                        calc(nums, ops);
                    } else {
                        ops.pollLast();
                        break;
                    }
                }
            } else {
                if (isNumber(c)) {
                    int u = 0;
                    int j = i;
                    // 将从 i 位置开始后面的连续数字整体取出,加入 nums
                    while (j < n && isNumber(cs[j])) u = u * 10 + (cs[j++] - '0');
                    nums.addLast(u);
                    i = j - 1;
                } else {
                    if (i > 0 && (cs[i - 1] == '(' || cs[i - 1] == '+' || cs[i - 1] == '-')) {
                        nums.addLast(0);
                    }
                    // 有一个新操作要入栈时,先把栈内可以算的都算了
                    // 只有满足「栈内运算符」比「当前运算符」优先级高/同等,才进行运算
                    while (!ops.isEmpty() && ops.peekLast() != '(') {
                        char prev = ops.peekLast();
                        if (map.get(prev) >= map.get(c)) {
                            calc(nums, ops);
                        } else {
                            break;
                        }
                    }
                    ops.addLast(c);
                }
            }
        }
        // 将剩余的计算完
        while (!ops.isEmpty() && ops.peekLast() != '(') calc(nums, ops);
        return nums.peekLast();
    }
    // 计算逻辑:从 nums 中取出两个操作数,从 ops 中取出运算符,然后根据运算符进行计算即可
    void calc(Deque<Integer> nums, Deque<Character> ops) {
        if (nums.isEmpty() || nums.size() < 2) return;
        if (ops.isEmpty()) return;
        int b = nums.pollLast(), a = nums.pollLast();
        char op = ops.pollLast();
        int ans = 0;
        if (op == '+') ans = a + b;
        else if (op == '-') ans = a - b;
        else if (op == '*') ans = a * b;   
        nums.addLast(ans);
    }
    boolean isNumber(char c) {
        //判断字符c是不是数字形式的字符
        return Character.isDigit(c);
    }
}


import java.util.*;
public class Solution {
    public ArrayList<Integer> function(String s, int index){
        Stack<Integer> stack = new Stack<Integer>();
        int num = 0;
        char op = '+';
        int i;
        for(i = index; i < s.length(); i++){
            //数字转换成int数字 fast-template
            //判断是否为数字
            if(s.charAt(i) >= '0' && s.charAt(i) <= '9'){
                num = num * 10 + s.charAt(i) - '0';
                if(i != s.length() - 1)
                    continue;
            }
            //碰到'('时,把整个括号内的当成一个数字处理
            if(s.charAt(i) == '('){
                //递归处理括号
                ArrayList<Integer> res = function(s, i + 1);
                num = res.get(0);
                i = res.get(1);
                if(i != s.length() - 1)
                    continue;
            }
            switch(op){
            //加减号先入栈
            case '+':
                stack.push(num);
                break;
            case '-':
                //相反数
                stack.push(-num);
                break;
            //优先计算乘号
            case '*':
                int temp = stack.pop();
                stack.push(temp * num);
                break;
            }
            num = 0;
            //右括号结束递归
            if(s.charAt(i) == ')')
                break;
            else
                op = s.charAt(i);
        }
        int sum = 0;
        //栈中元素相加
        while(!stack.isEmpty())
            sum += stack.pop();
        ArrayList<Integer> temp = new ArrayList<Integer>();
        temp.add(sum);
        temp.add(i);
        return temp;
    }
    public int solve (String s) {
        ArrayList<Integer> res = function(s, 0);
        return res.get(0);}
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值