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