算法练习-栈和队列(二)
文章目录
1.计算器
链接:https://leetcode.cn/problems/calculator-lcci
1.1 题目
给定一个包含正整数、加(+)、减(-)、乘(*)、除(/)的算数表达式(括号除外),计算其结果。
表达式仅包含非负整数,+, - ,*,/ 四种运算符和空格 。 整数除法仅保留整数部分。
示例 1:
输入: “3+2*2”
输出: 7
示例 2:
输入: " 3/2 "
输出: 1
示例 3:
输入: " 3+5 / 2 "
输出: 5
1.2 题解
创建了两个栈,一个存储运算数,另一个存储运算符
如何进行入栈和出栈,见prior
class Solution {
public int calculate(String s) {
Stack<Integer> nums = new Stack<>();
Stack<Character> ops = new Stack<>();
int i = 0;
int n = s.length();
while (i < n) {
char c = s.charAt(i);
if (c == ' ') {
i++;
} else if (isDigit(c)) {
int num = 0;
while (i < n && isDigit(s.charAt(i))) {
num = num * 10 + (s.charAt(i) - '0');
i++;
}
nums.push(num);
} else {
if (ops.isEmpty() || prior(c, ops.peek())) {
ops.push(c);
} else {
while (!ops.isEmpty() && !prior(c, ops.peek())) {
fetchAndCal(nums, ops);
}
ops.push(c);
}
i++;
}
}
while(!ops.isEmpty()) {
fetchAndCal(nums, ops);
}
return nums.pop();
}
public boolean prior(char a, char b) {
if ((a == '*' || a == '/') && (b == '+' || b == '-')) {
return true;
}
return false;
}
public int cal(char op, int num1, int num2) {
switch(op) {
case '+' : return num1 + num2;
case '-' : return num1 - num2;
case '*' : return num1 * num2;
case '/' : return num1 / num2;
}
return -1;
}
public boolean isDigit(char c) {
return c >= '0' && c <= '9';
}
public void fetchAndCal(Stack<Integer> nums, Stack<Character> ops) {
int num2 = nums.pop();
int num1 = nums.pop();
char op = ops.pop();
int res = cal(op, num1, num2);
nums.push(res);
}
}
2. 删除字符串中所有相邻重复项
链接:https://leetcode.cn/problems/remove-all-adjacent-duplicates-in-string
2.1 题目
给出由小写字母组成的字符串 S,重复项删除操作会选择两个相邻且相同的字母,并删除它们。
在 S 上反复执行重复项删除操作,直到无法继续删除。
在完成所有重复项删除操作后返回最终的字符串。答案保证唯一。
示例:
输入:“abbaca”
输出:“ca”
解释:
例如,在 “abbaca” 中,我们可以删除 “bb” 由于两字母相邻且相同,这是此时唯一可以执行删除操作的重复项。之后我们得到字符串 “aaca”,其中又只有 “aa” 可以执行重复项删除操作,所以最后的字符串为 “ca”。
2.2 题解
class Solution {
public String removeDuplicates(String s) {
Deque<Character> deque = new LinkedList<>();
int n = 0;
int num = s.length();
while (n < num) {
char c = s.charAt(n);
if (deque.isEmpty() || deque.peekLast() != c) {
deque.addLast(c);
} else {
deque.pollLast();
}
n++;
}
StringBuilder sb = new StringBuilder();
while (!deque.isEmpty()) {
sb.append(deque.pollFirst());
}
return sb.toString();
}
}
3 栈的压入、弹出序列
3.1 题目
链接:https://leetcode.cn/problems/zhan-de-ya-ru-dan-chu-xu-lie-lcof
输入两个整数序列,第一个序列表示栈的压入顺序,请判断第二个序列是否为该栈的弹出顺序。假设压入栈的所有数字均不相等。例如,序列 {1,2,3,4,5} 是某栈的压栈序列,序列 {4,5,3,2,1} 是该压栈序列对应的一个弹出序列,但 {4,3,5,1,2} 就不可能是该压栈序列的弹出序列。
示例 1:
输入:pushed = [1,2,3,4,5], popped = [4,5,3,2,1]
输出:true
解释:我们可以按以下顺序执行:
push(1), push(2), push(3), push(4), pop() -> 4,
push(5), pop() -> 5, pop() -> 3, pop() -> 2, pop() -> 1
示例 2:
输入:pushed = [1,2,3,4,5], popped = [4,3,5,1,2]
输出:false
解释:1 不能在 2 之前弹出。
3.2 题解
按照给定的入栈和出栈的元素进行操作,如果不能正常入栈和出栈,则序列错误
class Solution {
public boolean validateStackSequences(int[] pushed, int[] popped) {
Stack<Integer> stack = new Stack<>();
int i = 0;
int j = 0;
int k = 0;
while (k < pushed.length + popped.length) {
k++;
// 如果栈不为空并且出栈序列有元素,栈顶元素等于出栈序列的元素
// 出栈
if (!stack.isEmpty() && j < popped.length && stack.peek() == popped[j]) {
stack.pop();
j++;
continue;
}
// 入栈
if (i < pushed.length) {
stack.push(pushed[i]);
i++;
continue;
}
return false;
}
return true;
}
}
4 每日温度
链接:https://leetcode.cn/problems/daily-temperatures
4.1 题目
给定一个整数数组 temperatures ,表示每天的温度,返回一个数组 answer ,其中 answer[i] 是指对于第 i 天,下一个更高温度出现在几天后。如果气温在这之后都不会升高,请在该位置用 0 来代替。
示例 1:
输入: temperatures = [73,74,75,71,69,72,76,73]
输出: [1,1,4,2,1,1,0,0]
示例 2:
输入: temperatures = [30,40,50,60]
输出: [1,1,1,0]
示例 3:
输入: temperatures = [30,60,90]
输出: [1,1,0]
4.2 题解
4.2.1 暴力解法(超出时间限制)
class Solution {
public int[] dailyTemperatures(int[] temperatures) {
int n = temperatures.length;
int[] result = new int[n];
for (int i = 0; i < n; ++i) {
for (int j = i + 1; j < n; j++) {
if (temperatures[j] > temperatures[i]) {
result[i] = j - i;
break;
}
}
}
return result;
}
}
4.2.2单调栈
class Solution {
public int[] dailyTemperatures(int[] temperatures) {
int n = temperatures.length;
int result[] = new int[n];
Stack<Integer> stack = new Stack<>();
for (int i = 0; i < n; i++) {
while (!stack.isEmpty() && temperatures[stack.peek()] < temperatures[i]) {
int idx = stack.peek();
result[idx] = i - idx;
stack.pop();
}
stack.push(i);
}
return result;
}
}
5 接雨水(hard)
链接:https://leetcode.cn/problems/trapping-rain-water
5.1 题目
给定 n 个非负整数表示每个宽度为 1 的柱子的高度图,计算按此排列的柱子,下雨之后能接多少雨水。
示例 1:
输入:height = [0,1,0,2,1,0,1,3,2,1,2,1]
输出:6
解释:上面是由数组 [0,1,0,2,1,0,1,3,2,1,2,1] 表示的高度图,在这种情况下,可以接 6 个单位的雨水(蓝色部分表示雨水)。
示例 2:
输入:height = [4,2,0,3,2,5]
输出:9
5.2 题解
5.2.1 暴力解法
每个主子上的承载水量=min(左侧最高柱子lh,右侧最高柱子rh)-这个柱子高度h的总接水量=每个柱子之上的承载水量总和
class Solution {
public int trap(int[] height) {
int n = height.length;
int result = 0;
// 遍历每个柱子,找到左面最高的柱子和右面最高的柱子
for (int i = 0; i < n - 1; ++i) {
int lh = 0;
for (int j = 0; j < i; j++) {
if (height[j] > lh) {
lh = height[j];
}
}
int rh = 0;
for (int j = i + 1; j < n; j++) {
if (height[j] > rh) {
rh = height[j];
}
}
int carry = Math.min(lh, rh) - height[i];
if (carry < 0) {
carry = 0;
}
result += carry;
}
return result;
}
}
5.2.2 前缀/后缀统计解法
利用空间换时间
创建两个数组,分别存储当前位置左侧最大高度和右侧最大高度
先循环填充两个数组
class Solution {
public int trap(int[] height) {
int n = height.length;
// 前缀max
int[] lmax = new int[n];
int max = 0;
for (int i = 0; i < n; ++i) {
lmax[i] = Math.max(max, height[i]);
max = lmax[i];
}
// 后缀max
int[] rmax = new int[n];
max = 0;
for (int i = n - 1; i >= 0; --i) {
rmax[i] = Math.max(max, height[i]);
max = rmax[i];
}
int result = 0;
for (int i = 0; i < n - 1; i++) {
result += Math.min(lmax[i], rmax[i]) - height[i];
}
return result;
}
}
5.2.3 单调栈
前面两种方法按照垂直计算,该方法水平计算,一层一层计算存水量
class Solution {
public int trap(int[] height) {
int n = height.length;
int result = 0;
Stack<Integer> stack = new Stack<>();
for (int i = 0; i < n; i++) {
if (stack.isEmpty()) {
stack.push(i); // 存下标
continue;
}
while (!stack.isEmpty()) {
int top = stack.peek();
if (height[top] >= height[i]) {
stack.push(i);
break;
} else {
// 找到凹槽了
top = stack.pop();
if (stack.isEmpty()) {
stack.push(i);
break;
}
int left = stack.peek();
int h = Math.min(height[left], height[i]) - height[top];
int w = i - left - 1;
result += h * w;
}
}
}
return result;
}
}