150.逆波兰表达式求值
逆波兰式转为中缀,就是压在栈里,每两个数遇见一个运算符,就进行并且将运算结果再压栈。
所以思路开始,首先,我们怎么判断其是运算符并且可以进行对应的计算呢,判断是运算符和数字,可以利用Character.isDigit//但是isDigital不可以判断正负数和小数,所以我们可以反着判断,只要不是“=-*/”里面的其他的都是数字
当判断非字符并且取两个数出来(取不出来两个数的话这个逆波兰式本身就是错误的),进行运算并将结果压栈。所以我们只需要去拿两个数,这个运算符是什么我们不必知道,另外封装一个函数当黑盒,我们只需要将栈顶两个元素拿进去运算就行。
class Solution {
/**
* 逆波兰式求值
*
* @param tokens
* @return
*/
public int evalRPN(String[] tokens) {
Stack<String> stack = new Stack<>();
for (int i = 0; i < tokens.length; i++) {
if (!isDigital(tokens[i])) {
String temp1 = stack.peek();
stack.pop();
String temp2 = stack.peek();
stack.pop();
String ansk = String.valueOf(calculateWith(tokens[i], temp1, temp2));
stack.push(ansk);
} else {
stack.push(tokens[i]);
}
}
return Integer.valueOf(stack.peek());
}
/**
* 判断是否为数字
*
* @param str
* @return
*/
public boolean isDigital(String str) {
if (str.equals("+") || str.equals("-") || str.equals("*") || str.equals("/")) {
return false;
}
return true;
}
/**
* b s a
*
* @param s------>运算符
* @param a------>弹出来的第一个数
* @param b------>弹出来的第二个数
* @return
*/
public int calculateWith(String s, String a, String b) {
int ans = 0;
switch (s) {
case "*":
ans = Integer.valueOf(b) * Integer.valueOf(a);
break;
case "+":
ans = Integer.valueOf(b) + Integer.valueOf(a);
break;
case "-":
ans = Integer.valueOf(b) - Integer.valueOf(a);
break;
case "/":
ans = Integer.valueOf(b) / Integer.valueOf(a);
break;
}
return ans;
}
}
感觉再重要一点就是String和int的转换,其他没啥了。
239.滑动窗口最大值
大小为k的滑动数组,返回滑动数组里面的最大值。
用一个队列一直维护右向最大值,自己实现一个单调队列,当然这边就有个问题是要判断队列里面的元素是不是k个,是的话加下一个进来的话,就要把之前的那个pop掉。如果不是k个,只加进来元素
其实队列没有必要维护窗口里的所有元素,只需要维护有可能成为窗口里最大值的元素就可以了,同时保证队里里的元素数值是由大到小的。
那么这个维护元素单调递减的队列就叫做单调队列,即单调递减或单调递增的队列。
不要以为实现的单调队列就是 对窗口里面的数进行排序,如果排序的话,那和优先级队列又有什么区别了呢。
右向最大值:从左向右遍历的话,队列出口处永远保存着的是最大值。
设计单调队列的时候,pop,和push操作要保持如下规则:
- pop(value):如果窗口移除的元素value等于单调队列的出口元素,那么队列弹出元素,否则不用任何操作
- push(value):如果push的元素value大于入口元素的数值,那么就将队列入口的元素弹出,直到push元素的数值小于等于队列入口元素的数值为止
保持如上规则,每次窗口移动的时候,只要问que.front()就可以返回当前窗口的最大值
首先实现一个单调队列myqueue,在poll元素的时候,去判断他是否跟我们的队首元素相同(不要担心会有重复元素,因为我们push的时候可以控制相等状态),push元素的时候,如果这个数>之前的元素,把他之前比他小的元素全部都remove,直到他到比他小的元素。
比如,队列现在是423我要加入4,队列变成44.如果队列是423,我要加入5,前面的都会被干掉,队列只变成5
/**
* 单调队列,单调递减,永远保证最大值在最前面
*/
class Myqueue {
Deque<Integer> queue;
Myqueue() {
queue = new ArrayDeque<>();
}
public void push(int val) {//把val值加进来,把他之前比他小的数全部kill掉
while (!queue.isEmpty() && queue.peekLast() < val) {
queue.pollLast();
}
queue.addLast(val);
}
public void pop(int val) {//把值弹出去,检查,是不是相等,就是队头最大元素,是的话弹出去,不是的话就在push的时候被kill了
if (queue.peekFirst().equals(val)) {
queue.pollFirst();
}
}
public int getMaxvalue() {
return queue.peekFirst();
}
}
class Solution {
/**
* 滑动窗口返回最大数
*
* @param nums
* @param k
* @return
*/
public int[] maxSlidingWindow(int[] nums, int k) {
ArrayList<Integer> ans = new ArrayList<>();
if (nums.length == 1) {
return new int[]{nums[0]};
}
Myqueue queue = new Myqueue();
for (int i = 0; i < k; i++) {
queue.push(nums[i]);
}
ans.add(queue.getMaxvalue());
for (int i = k; i < nums.length; i++) {
queue.pop(nums[i - k]);
queue.push(nums[i]);
ans.add(queue.getMaxvalue());
}
return ans.stream().mapToInt(Integer::intValue).toArray();
}
}
347.前K个高频元素
找出每个数出现的频率,对频率进行排序,输出前k个频率下的数
第一反应,hoxy是用优先级队列吗,然后算上一个排序,然后再一个一个pop出来,用hashmap去统计他们的一个频率值就是出现的次数
然后再细想,要是排序的话,主要需要对map里面根据频率来排序,之后看题解,大概有了了解。应该会在时间复杂度上面更优算是优化。所以面试的话其实要说出来的point就是优先级队列
什么是优先级队列呢?
其实就是一个披着队列外衣的堆,因为优先级队列对外接口只是从队头取元素,从队尾添加元素,再无其他取元素的方式,看起来就是一个队列。
而且优先级队列内部元素是自动依照元素的权值排列。那么它是如何有序排列的呢?
缺省情况下priority_queue利用max-heap(大顶堆)完成对元素的排序,这个大顶堆是以vector为表现形式的complete binary tree(完全二叉树)。
什么是堆呢?
堆是一棵完全二叉树,树中每个结点的值都不小于(或不大于)其左右孩子的值。 如果父亲结点是大于等于左右孩子就是大顶堆,小于等于左右孩子就是小顶堆。
所以大家经常说的大顶堆(堆头是最大元素),小顶堆(堆头是最小元素),如果懒得自己实现的话,就直接用priority_queue(优先级队列)就可以了,底层实现都是一样的,从小到大排就是小顶堆,从大到小排就是大顶堆。
本题我们就要使用优先级队列来对部分频率进行排序。
为什么不用快排呢, 使用快排要将map转换为vector的结构,然后对整个数组进行排序, 而这种场景下,我们其实只需要维护k个有序的序列就可以了,所以使用优先级队列是最优的。
用小顶堆,因为要统计最大前k个元素,只有小顶堆每次将最小的元素弹出,最后小顶堆里积累的才是前k个最大元素。
小顶堆能够保证队头元素是最小的,当queue里面的元素个数超过k并且当前要加的元素,比队头元素的值要大,就把队头元素弹出,并且把这个数加进queue里面,优先队列会自动帮你排序,不需要担心顺序问题
/*Comparator接口说明:
* 返回负数,形参中第一个参数排在前面;返回正数,形参中第二个参数排在前面
* 对于队列:排在前面意味着往队头靠
* 对于堆(使用PriorityQueue实现):从队头到队尾按从小到大排就是最小堆(小顶堆),
* 从队头到队尾按从大到小排就是最大堆(大顶堆)--->队头元素相当于堆的根节点
* */
topK问题是指:从海量数据中寻找最大的前k个数据,比如从1亿个数据中,寻找最大的1万个数。
使用优先队列,能够很好的解决这个问题。先使用前1万个数构建最小优先队列,以后每取
一个数,都与队头元素进行比较,若大于队头元素,就将队头元素删除,并将该元素添加到
优先队列中;若小于队头元素,则将该元素丢弃掉。如此往复,直至所有元素都访问完。最
后优先队列中的1万个元素就是最大的1万个元素。
例1: 给定整数数组 nums 和整数 k,请返回数组中第 k 个最大的元素。你需要找的是数组
排序后的第 k 个最大的元素,而不是第 k 个不同的元素。
class Solution {
/**
* 输出前k个高频元素
*
* @param nums
* @param k
* @return
*/
public int[] topKFrequent(int[] nums, int k) {
HashMap<Integer, Integer> hashMap = new HashMap<>();
for (int i = 0; i < nums.length; i++) {
if (!hashMap.containsKey(nums[i])) {
hashMap.put(nums[i], 1);
} else {
int cnt = hashMap.get(nums[i]) + 1;
hashMap.put(nums[i], cnt);
}
}
ArrayList<Integer> ans = new ArrayList<>();
//将map里面的排序---->pair1是数组对象,pair1和pair2对比是根据频率值来比的
PriorityQueue<int[]> pq = new PriorityQueue<>((pair1, pair2) -> (pair1[1] - pair2[1]));
for (Map.Entry<Integer, Integer> entry : hashMap.entrySet()) {
if (pq.size() < k) {//长度不足k的时候,直接加进去
pq.add(new int[]{entry.getKey(), entry.getValue()});
} else {
if (pq.peek()[1] < entry.getValue()) {//栈顶对象数组的[1]位是频率
pq.poll();
pq.add(new int[]{entry.getKey(), entry.getValue()});
}
}
}
//最后把queue里面的值弹出来
while (!pq.isEmpty()) {
ans.add(0, pq.poll()[0]);
}
return ans.stream().mapToInt(Integer::intValue).toArray();
}
}
2022.10.13补