栈与队列part02
Java集合相关基础知识总结
1.逆波兰表达式求值
题目链接
逆波兰表达式=二叉树的后序遍历
逆波兰表达式主要有以下两个优点:
(1) 去掉括号后表达式无歧义,上式即便写成 1 2 + 3 4 + * 也可以依据次序计算出正确结果。
(2) 适合用栈操作运算:遇到数字则入栈;遇到运算符则取出栈顶两个数字进行计算,并将结果压入栈中。
✅ 易错点:
(1)Java中判断字符串相等需要用equals方法,所以可以直接用token.equals(“+”)来判断。也可以像我一样利用char ch=token.charAt(0)将字符串转换成char再判断,问题在于我一开始忽略了当token为负数时,token.charAt(0)也是’-‘,所以后来又加上了token.length()==1的条件。
(2)题目中明确要求“ 两个整数之间的除法总是 向零截断 ",Java和c++的整数除法都是向0截断,即int result2 = -7 / 3; 结果是-2而非向下截断的-3。但一些语言如python是向下截断的,所以需要做特殊处理。
(3)当ch等于’-‘或者’/'时,要想清楚谁是被除数和除数(num1和num2谁在前面)。
class Solution {
public int evalRPN(String[] tokens) {
LinkedList<Integer> stack=new LinkedList<>();
for(String token:tokens){
char ch=token.charAt(0);
//符号
if(token.length()==1 && (ch=='+' || ch=='-' || ch=='*' || ch=='/')){
int num1=stack.pop();
int num2=stack.pop();
switch(ch){
case '+':
stack.push(num1+num2);
break;
case '-':
stack.push(num2-num1);
break;
case '*':
stack.push(num1*num2);
break;
case '/':
stack.push(num2/num1);
break;
}
}
//数字
else{
stack.push(Integer.valueOf(token));
}
}
return stack.pop();
}
}
2.滑动窗口最大值
题目链接
使用单调队列的经典题目,自认为有点像最小栈,想到用一个单独的数据结构用于存储。此队列不能只存储最大值,还需要存储有可能成为最大值的元素,因为随着窗口的移动,元素会弹出。而又不能像普通单调队列一样,将窗口内元素从大到小排列,这样弹出时每次需要寻找该元素,使得问题更加复杂。那么如何存储维护有可能成为窗口里最大值的元素,同时保证队列里的元素数值是由大到小的呢?
✅最关键的一个原则就是:队列中无需保留位于当前元素左侧并且小于当前元素的数值(这些左侧的元素比当前元素弹出早,在当前元素弹出之前,最大值都会是当前元素,这些左侧元素不可能成为最大值)
由此推理出几个步骤:
1.处理右指针
向右扩展窗口,添加新元素时,从末端遍历,将队列中所有小于right元素都清除,然后加入right。
(如果新元素大于队列的头部元素,我们可以选择清空整个队列并只保留这个新元素。这个逻辑可以单独处理,其实从末端遍历效果也一样。)
2. 处理左指针
当窗口向右滑动,需要弹出左元素时。只需将left和队列最大值(head)进行对比,如果left小于最大值,说明最大值在left右侧,left不可能在队列中,无需进行任何操作。
class Solution {
LinkedList<Integer> list=new LinkedList<>();
public int[] maxSlidingWindow(int[] nums, int k) {
int[] result=new int[nums.length-k+1];
int i=0;
int left=0,right=0;
//先添加前k个元素
while(right<k){
push(nums[right]);
right++;
}
result[i++]=list.getFirst();
while(right<nums.length){
//处理左指针
if(nums[left]==list.getFirst()){
list.removeFirst();
}
left++;
push(nums[right]);
result[i++]=list.getFirst();
right++;
}
return result;
}
//处理右指针
public void push(int num){
if(list.isEmpty()){
list.addFirst(num);
}
else{
while(!list.isEmpty() && num>list.getLast()){
list.removeLast();
}
list.addLast(num);
}
}
}
✅ 易错点:
(1)当存在循环和自增时,一定要想清楚退出时变量的值(本题中就是由于先添加前k个元素后,退出循环时变量right实际上已经等于k,而不是我们可能直觉上认为的k-1)。先变量自增再进行逻辑处理和先进行逻辑处理再自增,对于循环条件会不同。
(2)Java函数传参时使用的是值传递的方式
基本数据类型:值传递(pass by value)是指在调用方法时将实参复制一份传递到方法中,这样当方法对形参进行修改时不会影响到实参。
引用类型:引用传递(pass by reference)是指在调用方法时将实参的地址直接传递到方法中,那么在方法中对形参所进行的修改,将影响到实参。
引用类型作为参数被传递时也是值传递,只不过“值”为对应的引用。
参考:CSDN:五分钟学Java:Java到底是值传递还是引用传递?
对于示例1:
class Solution {
LinkedList<Integer> list = new LinkedList<>();
public static void main(String[] args) {
Solution solution = new Solution();
solution.push(1);
}
public void push(int num) {
this.list.push(num);
}
}
示例2:
class Solution {
public static void main(String[] args) {
Solution solution = new Solution();
LinkedList<Integer> list = new LinkedList<>();
solution.push(list, 1);
}
public LinkedList<Integer> push(LinkedList<Integer> list, int num) {
list.push(num);
return list;
}
}
在LeetCode上,通常推荐使用示例2的方法,因为它提供了更好的灵活性和可测试性。但确实示例2的空间消耗更高,如果push方法被频繁调用,每次调用都会创建一个新的LinkedList实例。