6.1 栈的应用
面试题36:后缀表达式
题目:后缀表达式是一种算数表达式,它的操作符在操作数的后面,输入一个用字符串数组表示的后缀表达式,请输出该后缀表达式计算的结果。
注:关于中缀表达式转后缀表达式可见:复杂四则运算的中缀表达式转后缀表达式(逆波兰表达式)Java版
public int calculate(ArrayList<String> rpn){
Stack<Integer> numStack = new Stack<>();
for (String s : rpn) {
if(!isOperator(s)){
numStack.push(Integer.parseInt(s));
} else{
int num1 = numStack.pop();
int num2 = numStack.pop();
switch (s){
// 计算的结果重新入栈
case "+":
numStack.push(num2 + num1);
continue;
case "-":
numStack.push(num2 - num1);
continue;
case "*":
numStack.push(num2 * num1);
continue;
case "/":
numStack.push(num2 / num1);
continue;
}
}
}
return numStack.pop();
}
public boolean isOperator(String str){
// 判断是否是操作符
return "+".equals(str) || "-".equals(str) || "*".equals(str) || "/".equals(str);
}
- 表达式四则运算,包含中缀表达式转后缀表达式:
public int calculate(String exp){
// 获取逆波兰表达式
ArrayList<String> rpn = getRPN(exp);
Stack<Integer> numStack = new Stack<>();
for (String s : rpn) {
if(!isOperator(s)){
numStack.push(Integer.parseInt(s));
} else{
int num1 = numStack.pop();
int num2 = numStack.pop();
int temp = 0;
switch (s){
case "+":
temp = num2 + num1;
break;
case "-":
temp = num2 - num1;
break;
case "*":
temp = num2 * num1;
break;
case "/":
temp = num2 / num1;
break;
}
numStack.push(temp);
}
}
return numStack.pop();
}
private ArrayList<String> getRPN(String exp) {
// 中缀表达式转后缀表达式
ArrayList<String> lex = lexical(exp);
ArrayList<String> result = new ArrayList<>(lex.size());
Stack<String> stack = new Stack<>();
for (String s : lex) {
if(!isOperator(s)){
result.add(s);
}else{
if("(".equals(s)){
stack.push(s);
} else if(")".equals(s)){
// 弹出"(" 和 ")"之间所有的运算符,但没弹出"("
while(!"(".equals(stack.peek())){
result.add(stack.pop());
}
// 弹出"("
stack.pop();
} else if(!stack.isEmpty() && priority(s) > priority(stack.peek())){
// 比较运算符优先级
stack.push(s);
} else{
// 优先级低,则将之前优先级高的运算符全部弹出,先计算
while(!stack.isEmpty() && priority(s) <= priority(stack.peek())){
result.add(stack.pop());
}
stack.push(s);
}
}
}
while(!stack.isEmpty()){
result.add(stack.pop());
}
return result;
}
public boolean isOperator(String str){
// 判断是否是操作符
return "+".equals(str) || "-".equals(str) || "*".equals(str) || "/".equals(str) || "(".equals(str) || ")".equals(str);
}
public int priority(String str){
// 获得运算符优先级
// 左右括号入栈后优先级降到最低
switch (str){
case "*":
case "/":
return 2;
case "+":
case "-":
return 1;
default:
return 0;
}
}
private ArrayList<String> lexical(String exp) {
// 词法分析器
// 表达式预处理,去处空格,括号统一转变为小括号
exp = exp.replace(" ", "").replace("{", "(").replace("}", ")")
.replace("[", "(").replace("]", ")");
ArrayList<String> result = new ArrayList<>();
String token = "";
for(int i = 0; i < exp.length(); i++){
char c = exp.charAt(i);
token += c;
if(Character.isDigit(c)){
if(i == exp.length() - 1 || !Character.isDigit(exp.charAt(i + 1))){
result.add(token);
token = "";
}
} else if(c == '-' && (i == 0 || exp.charAt(i - 1) == '(')){
continue;
} else {
result.add(c + "");
token = "";
}
}
return result;
}
面试题37:小行星碰撞
题目:输入一个表示小行星的数组,数组中每个数字的绝对值表示小行星的大小,数字的正负号表示小行星的运动方向,正号表示小行星向右飞行,负号表示小行星向左飞行。乳沟两颗小行星相撞,那么体积较小的小行星将会爆炸消失。体积大的小行星不收影响。如果相撞的两颗小行星大小相同,那么他们都会爆炸消失。飞行方向相同的小行星永远不会相撞。求最终剩下的小行星。
例如:有6颗小行星[4, 5, -6, 4, 8, -5],飞行图如下所示,它们相撞之后最终剩下三颗小行星[-6, 4, 8]
public int[] asteroidCollision(int[] asteroids){
Stack<Integer> stack = new Stack<>();
for (int asteroid : asteroids) {
// 小行星的值大于0,则直接入栈
if(asteroid >= 0){
stack.push(asteroid);
} else {
// 小行星的值小于0,需要和栈顶元素进行比较
// 栈为空或者栈顶元素小于0,则可以直接入栈
// 栈顶元素大于0,则会发生碰撞,需要解决碰撞
while(!stack.isEmpty() && (stack.peek() > 0 && stack.peek() + asteroid < 0)){
stack.pop();
}
if(!stack.isEmpty() && (stack.peek() > 0 && stack.peek() + asteroid == 0)){
stack.pop();
continue;
}
if(!stack.isEmpty() && (stack.peek() > 0 && stack.peek() + asteroid > 0)){
continue;
}
stack.push(asteroid);
}
}
return stack.stream().mapToInt(i -> i).toArray();
}
面试题38:每日温度
题目:输入一个数组,它的每个数字是某天的温度,请计算每天需要等几天才会出现更高的温度。例如,如果输入数组[35, 31, 33, 36, 34],那么输出为[3, 1, 1, 0, 0]。由于第一天的温度是35°C,要等三天才会出现更高的温度36°C,因此对应的输出为3。第四天的温度是36°C,后面没有更高的温度,它对应的输出为0。其他的以此类推。
注:此题本质上就是找数组中下一个比它大的数,并计算下标差
public int[] findNextMore(int[] dailyTemperature){
// 用栈,栈中压入当天的天数下标
// 用栈顶的元素那天的温度与当天温度作比较,决定是否弹栈,若弹栈,通过下标计算隔了多少天,存入结果集
Stack<Integer> stack = new Stack<>();
int[] result = new int[dailyTemperature.length];
for (int i = 0; i < dailyTemperature.length; i++) {
int temperature = dailyTemperature[i];
while(!stack.isEmpty() && temperature > dailyTemperature[stack.peek()]){
result[stack.peek()] = i - stack.peek();
stack.pop();
}
stack.push(i);
}
return result;
}
面试题39:直方图最大矩形面积
题目:直方图是由排列在同一基线上的相邻柱子组成的图形。输入一个由非负数组成的数组,数组中的数字是直方图中柱子的高。求直方图中最大的句型面积。假设直方图柱子的宽都为1。例如,输入数组[3, 2, 5, 4, 6, 1, 4, 2],其中对应的直方图如下图所示,最大矩形面积为12。
此题有多重解法:
- 暴力法:双循环,外层循环遍历每个坐标,内层循环计算从当前坐标开始的最大面积
- 二分法:找到低的高度,比较以最低高度计算的面积和最低高度两侧计算结果,取最大值
- 栈:栈中保存坐标;遍历每个坐标,若当前高度小于栈顶坐标对应的高度,则将栈顶坐标出栈,计算以此栈顶坐标对应的高度的矩形面积,直到栈顶坐标的高度小于当前高度,则压入当前高度的坐标。保证栈中的坐标对应的高度是单调递增的。关键点:刚开始给站压入一个哨兵坐标-1,可以避免很多栈空的判断
public int getMaxArea(int[] histogram){
// 第三种方法
Stack<Integer> stack = new Stack<>();
// 哨兵坐标,简化判断
stack.push(-1);
int result = 0;
for (int i = 0; i < histogram.length; i++) {
int temp = histogram[i];
while(stack.peek() != -1 && temp < histogram[stack.peek()]){
int prevHeight = histogram[stack.pop()];
int prevMaxArea = prevHeight * (i - stack.peek() - 1);
result = Math.max(result, prevMaxArea);
}
stack.push(i);
}
while(stack.peek() != -1){
int tempHeight = histogram[stack.pop()];
int tempMaxArea = tempHeight * (histogram.length - stack.peek() - 1);
result = Math.max(result, tempMaxArea);
}
return result;
}
面试题40:矩阵中的最大矩形
题目:请在一个由0,1组成的矩阵中找到最大只包含1的矩形,并输出他的面积。如下图所示,最大的只包含1的矩阵面积是6。
思路:转化为直方图求面积。以每一行为基准线,将值为1的列看做直方图的柱子。这样能构造4个直方图。然后求这四个直方图最大矩形的面积(面试题39)。
public int maxRectangleInMatrix(int[][] matrix){
if(matrix.length == 0 || matrix[0].length == 0){
return 0;
}
int maxArea = 0;
int[] height = new int[matrix[0].length];
for (int[] row : matrix) {
for(int i = 0; i < row.length; i++){
if(row[i] == 0){
height[i] = 0;
} else {
height[i]++;
}
}
maxArea = Math.max(maxArea, getMaxArea(height));
}
return maxArea;
}
public int getMaxArea(int[] histogram){
Stack<Integer> stack = new Stack<>();
// 哨兵坐标,简化判断
stack.push(-1);
int result = 0;
for (int i = 0; i < histogram.length; i++) {
int temp = histogram[i];
while(stack.peek() != -1 && temp < histogram[stack.peek()]){
int prevHeight = histogram[stack.pop()];
int prevMaxArea = prevHeight * (i - stack.peek() - 1);
result = Math.max(result, prevMaxArea);
}
stack.push(i);
}
while(stack.peek() != -1){
int tempHeight = histogram[stack.pop()];
int tempMaxArea = tempHeight * (histogram.length - stack.peek() - 1);
result = Math.max(result, tempMaxArea);
}
return result;
}