目录
(四)用栈实现队列(Implement Queue using Stacks)
(五)用队列实现栈(Implement Stack using Queues)
(七)逆波兰表达式求值(Evaluate Reverse Polish Notation)
干货分享,感谢您的阅读!祝你逢考必过!
一、背景知识
栈(Stack)是一种线性数据结构,它遵循后进先出(Last-In-First-Out,LIFO)的原则,这意味着最近添加的元素最先被访问,而最先添加的元素最后被访问。这种特性使栈非常适合用于某些应用,比如函数调用、表达式求值、括号匹配、回溯等。
当我们需要保存一些数据,而且需要以特定的顺序进行访问时,栈就是一个非常有用的数据结构。它通常有以下三种基本操作:
- 入栈(Push):向栈中添加元素。元素会被添加到栈顶,也就是最后一个元素之后。
- 出栈(Pop):从栈中移除并返回栈顶元素。栈顶元素是最后一个入栈的元素。
- 查看栈顶元素(Peek):查看栈顶元素,但是不从栈中移除它。
另外,还有两个与栈相关的概念:栈底和栈顶。栈底是栈的第一个元素,而栈顶是最后一个元素。因为栈是后进先出的,所以最后进入栈的元素是在栈顶,而最先进入栈的元素是在栈底。
栈还有一些重要的特点,如下所示:
- 空栈:如果栈中没有任何元素,则称之为空栈。
- 栈的大小:栈的大小表示栈中可以存储多少元素。当栈的大小已满时,再次尝试将元素入栈会导致栈溢出。
- 动态栈:动态栈是一种特殊类型的栈,可以根据需要自动增加或减少大小。这种栈的大小不是固定的,而是根据需要动态变化的。
按照以上的背景,展示举例:在写程序的时候,会碰到函数 A 要调用函数 B,函数 B 要调用函数 C 这样连续的处理。这个时候,在函数 A 的上面是函数 B,函数 B 的上面是函数 C。(图来自图灵社区)
二、栈的应用
(一)在Spring或计算机科学中的应用举例
在 Spring 底层中,栈数据结构经常用于实现方法调用栈。Spring 框架的核心是 IoC 容器和 AOP 框架,而这些框架的实现都需要大量的方法调用。当我们使用 Spring 调用一个方法时,Spring 会将该方法及其参数压入一个方法调用栈中,然后执行该方法。如果该方法又调用了其他方法,则 Spring 会将这些方法和它们的参数也压入方法调用栈中,直到所有方法执行完毕并返回结果。
此外,在 Web 应用程序中,栈数据结构也经常用于处理 HTTP 请求。当一个请求到达服务器时,Web 服务器会将该请求添加到请求队列中,然后使用栈数据结构来处理请求。每个请求都可以看作是一个方法调用,因此服务器可以使用栈来实现方法调用栈,以便管理请求处理流程。
当然,栈数据结构在计算机科学中有许多其他应用,以下是一些例子:
- 编译器和解释器中,栈常用于存储编译或解释器上下文,以便实现程序的递归或迭代执行。
- 操作系统中,栈被用于存储程序调用栈、系统调用和中断处理。
- 数据库管理系统中,栈被用于实现查询操作的解析和执行。
- 网络路由器中,栈被用于存储路由表,以便实现路由选择算法。
- 图形学中,栈被用于存储矩阵堆栈,以便实现变换操作。
- 数学中,栈被用于实现逆波兰表达式和表达式求值等算法。
- 计算机图形学中,栈被用于实现渲染管道,以便对图像进行各种变换和处理。
总之,栈数据结构是计算机科学中非常常见和有用的数据结构,被广泛应用于许多领域,包括编程语言、操作系统、数据库管理系统、网络通信、图形学和数学等。
(二)实际开发中的应用举例
在实际开发中,栈数据结构的应用也非常广泛。以下是一些实际开发中常见的栈应用举例:
- 浏览器的前进和后退功能。在浏览器中,用户可以通过点击“后退”和“前进”按钮来浏览他们之前访问过的网页。这些网页的访问历史被存储在栈中,每当用户点击“后退”或“前进”按钮时,浏览器就会从栈中弹出(或推入)一个网页。
- 程序调用堆栈。在计算机程序中,方法调用通常被实现为栈结构。每当一个方法被调用时,它的参数和返回地址会被压入栈中,然后在该方法执行完毕后被弹出。
- 表达式求值。在编写编译器或计算器程序时,常常需要实现表达式求值算法。这些算法通常使用栈来实现中间计算结果的存储和操作。
- 括号匹配。在编写程序时,常常需要对括号进行匹配和检查。这个问题可以通过使用栈来解决,将左括号压入栈中,遇到右括号时弹出栈顶元素并检查是否匹配。
- 逆波兰表达式求值。逆波兰表达式是一种无括号的数学表达式,使用栈可以方便地对逆波兰表达式进行求值。
- 回文字符串检查。回文字符串是一种正反读都相同的字符串,例如“level”和“racecar”。这个问题可以通过使用栈来解决,将字符串的一半字符压入栈中,然后从栈中弹出元素并与剩余字符进行比较。
- 缓存回退。在一些应用程序中,用户可能需要在多个页面之间切换。为了提高性能,程序通常会使用栈来缓存用户的页面历史,以便快速回退到之前的页面。
三、相关编程练习
(一)有效的括号(Valid Parentheses)
题目描述:给定一个只包括括号的字符串 s ,判断字符串是否有效。
有效字符串需满足:
- 左括号必须用相同类型的右括号闭合。
- 左括号必须以正确的顺序闭合。
示例 1:输入:s = "()" 输出:true
示例 2:输入:s = "()[]{}" 输出:true
示例 3:输入:s = "(]" 输出:false
示例 4:输入:s = "([)]" 输出:false
示例 5:输入:s = "{[]}" 输出:true
提示:
- 1 <= s.length <= 104
- s 仅由括号 '()[]{}' 组成
解题思路
这道题的最优解是使用栈来解决。
我们可以遍历给定字符串,对于每个左括号,将其对应的右括号压入栈中,当遇到右括号时,弹出栈顶元素并判断是否与该右括号相同,若相同则继续遍历,若不相同则该字符串不合法。
具体代码展示
- package org.zyf.javabasic.letcode.stack;
-
- import java.util.Stack;
-
- /**
- * @author yanfengzhang
- * @description 给定一个只包括括号的字符串 s ,判断字符串是否有效。
- * 有效字符串需满足:左括号必须用相同类型的右括号闭合。
- * 左括号必须以正确的顺序闭合。
- * 示例 1:输入:s = "()" 输出:true
- * 示例 2:输入:s = "()[]{}" 输出:true
- * 示例 3:输入:s = "(]" 输出:false
- * 示例 4:输入:s = "([)]" 输出:false
- * 示例 5:输入:s = "{[]}" 输出:true
- *
- * 提示:
- * 1 <= s.length <= 104
- * s 仅由括号 '()[]{}' 组成
- * @date 2023/4/8 22:17
- */
- public class ValidStr {
-
- /**
- * 最优解是使用栈来解决。
- *
- * 我们可以遍历给定字符串,
- * 对于每个左括号,将其对应的右括号压入栈中,
- * 当遇到右括号时,弹出栈顶元素并判断是否与该右括号相同,
- * 若相同则继续遍历,若不相同则该字符串不合法。
- */
- public boolean isValid(String s) {
- Stack
stack = new Stack<>(); - for (int i = 0; i < s.length(); i++) {
- char c = s.charAt(i);
- if (c == '(' || c == '[' || c == '{') {
- stack.push(c);
- } else {
- if (stack.isEmpty()) {
- return false;
- }
- char top = stack.peek();
- if ((top == '(' && c == ')')
- || (top == '[' && c == ']')
- || (top == '{' && c == '}')) {
- stack.pop();
- } else {
- return false;
- }
- }
- }
- return stack.isEmpty();
- }
-
- public static void main(String[] args) {
- /*验证有效的括号代码*/
- String s = "()[]{}";
- boolean result = new ValidStr().isValid(s);
- System.out.println(result);
-
- s = "(]";
- result = new ValidStr().isValid(s);
- System.out.println(result);
-
- s = "([)]";
- result = new ValidStr().isValid(s);
- System.out.println(result);
-
- s = "{[]}";
- result = new ValidStr().isValid(s);
- System.out.println(result);
- }
- }
(二)最小栈(Min Stack)
题目描述:设计一个支持 push ,pop ,top 操作,并能在常数时间内检索到最小元素的栈。
- push(x) —— 将元素 x 推入栈中。
- pop() —— 删除栈顶的元素。
- top() —— 获取栈顶元素。
- getMin() —— 检索栈中的最小元素。
示例:
输入:["MinStack","push","push","push","getMin","pop","top","getMin"]
[[],[-2],[0],[-3],[],[],[],[]]
输出:[null,null,null,null,-3,null,0,-2]
解释:
MinStack minStack = new MinStack();
minStack.push(-2);
minStack.push(0);
minStack.push(-3);
minStack.getMin(); // 返回 -3
minStack.pop();
minStack.top(); // 返回 0
minStack.getMin(); // 返回 -2
提示:
- pop、top 和 getMin 操作总是在 非空栈 上调用。
解题思路
该问题的最优解是使用一个栈和一个辅助栈来实现。
在栈中存储元素,辅助栈中存储当前栈中的最小值。
每次压入元素时,将其压入栈中,同时比较该元素和辅助栈的栈顶元素,若小于等于辅助栈栈顶元素,则将其也压入辅助栈中。
在弹出元素时,若该元素等于辅助栈的栈顶元素,则同时弹出该元素和辅助栈的栈顶元素。
具体代码展示
- package org.zyf.javabasic.letcode.stack;
-
- import java.util.Stack;
-
- /**
- * @author yanfengzhang
- * @description 设计一个支持 push ,pop ,top 操作,并能在常数时间内检索到最小元素的栈。
- *
- * push(x) —— 将元素 x 推入栈中。
- * pop() —— 删除栈顶的元素。
- * top() —— 获取栈顶元素。
- * getMin() —— 检索栈中的最小元素。
- * @date 2023/4/8 22:29
- */
- public class MinStack {
- Stack
stack = new Stack<>(); - Stack
minStack = new Stack<>(); -
- public MinStack() {
- }
-
- public void push(int val) {
- stack.push(val);
- if (minStack.isEmpty() || val <= minStack.peek()) {
- minStack.push(val);
- }
- }
-
- public void pop() {
- if (stack.peek().equals(minStack.peek())) {
- minStack.pop();
- }
- stack.pop();
- }
-
- public int top() {
- return stack.peek();
- }
-
- public int getMin() {
- return minStack.peek();
- }
-
- public static void main(String[] args) {
- /*验证最优解*/
- MinStack minStack = new MinStack();
- minStack.push(-2);
- minStack.push(0);
- minStack.push(-3);
- /*-3*/
- System.out.println(minStack.getMin());
- minStack.pop();
- /*0*/
- System.out.println(minStack.top());
- /*-2*/
- System.out.println(minStack.getMin());
- }
- }
(三)每日温度(Daily Temperatures)
题目描述:给定一个数组 T,求出 T 中每个元素与后面第一个比它大的元素之间的距离。
如果不存在这样的元素,则对应位置的值为 0。
例如,给定数组 T = [73, 74, 75, 71, 69, 72, 76, 73],应返回 [1, 1, 4, 2, 1, 1, 0, 0]。
提示:每个温度值 T[i] 的范围是 [30, 100]。
解题思路
该问题可以使用单调栈来解决,单调栈的基本思路是维护一个栈,栈中的元素单调递减或递增。
对于本题,我们需要维护一个单调递减的栈,存储数组 T 中元素的下标,每次将一个元素压入栈中时,如果该元素比栈顶元素对应的元素大,则栈顶元素出栈,并且计算栈顶元素到该元素的距离,将距离存储到对应的结果数组中。直到该元素比栈顶元素小,将该元素入栈。
具体实现时,我们遍历数组 T,并将每个元素的下标入栈。当遇到一个比栈顶元素对应的元素大的元素时,我们可以通过栈顶元素的下标计算出距离,并将其存储到结果数组中。重复该过程直到遍历完整个数组。
时间复杂度为O(n),其中n是数组 T 的长度。空间复杂度为O(n)。
具体代码展示
- package org.zyf.javabasic.letcode.stack;
-
- import java.util.Arrays;
- import java.util.Deque;
- import java.util.LinkedList;
-
- /**
- * @author yanfengzhang
- * @description 给定一个数组 T,求出 T 中每个元素与后面第一个比它大的元素之间的距离。
- *
- * 如果不存在这样的元素,则对应位置的值为 0。
- * 例如,给定数组 T = [73, 74, 75, 71, 69, 72, 76, 73],应返回 [1, 1, 4, 2, 1, 1, 0, 0]。
- * 提示:每个温度值 T[i] 的范围是 [30, 100]。
- * @date 2023/4/8 22:39
- */
- public class DailyTemperatures {
-
- /**
- * 遍历数组 T,并将每个元素的下标入栈。
- * 当遇到一个比栈顶元素对应的元素大的元素时,
- * 我们可以通过栈顶元素的下标计算出距离,并将其存储到结果数组中。
- * 重复该过程直到遍历完整个数组。
- *
- * 时间复杂度为O(n),其中n是数组 T 的长度。空间复杂度为O(n)。
- */
- public int[] dailyTemperatures(int[] T) {
- int n = T.length;
- int[] res = new int[n];
- Deque
stack = new LinkedList<>(); - for (int i = 0; i < n; i++) {
- while (!stack.isEmpty() && T[i] > T[stack.peek()]) {
- int j = stack.pop();
- res[j] = i - j;
- }
- stack.push(i);
- }
- return res;
- }
-
- public static void main(String[] args) {
- int[] T = {73, 74, 75, 71, 69, 72, 76, 73};
- int[] res = new DailyTemperatures().dailyTemperatures(T);
- /*输出 [1, 1, 4, 2, 1, 1, 0, 0]*/
- System.out.println(Arrays.toString(res));
- }
- }
(四)用栈实现队列(Implement Queue using Stacks)
题目描述:使用栈实现队列,使得队列的操作 push、pop、peek、empty 的时间复杂度均为 O(1)
解题思路
两个栈实现队列的思路如下:
我们用两个栈stack1和stack2来模拟队列的操作。stack1负责入队列,stack2负责出队列。当我们需要入队列时,我们将元素压入stack1中;当需要出队列时,如果stack2为空,我们将stack1中的元素逐一弹出并压入stack2中,这样stack2中的元素就是按照队列先进先出的顺序排列。我们从stack2中弹出栈顶元素即可。
实现过程中,我们需要注意两点:
- 当stack2不为空时,不能将元素从stack1中压入stack2中,否则会打乱先进先出的顺序。
- 当stack1和stack2都为空时,队列为空,无法执行出队列操作,需要进行特殊处理。
综上所述,我们可以用两个栈来实现队列,时间复杂度为O(1)。
具体代码展示
- package org.zyf.javabasic.letcode.stack;
-
- import java.util.Stack;
-
- /**
- * @author yanfengzhang
- * @description 使用栈实现队列,
- * 使得队列的操作 push、pop、peek、empty 的时间复杂度均为 O(1)
- * @date 2023/4/8 22:55
- */
- public class ImplementQueueUseStacks {
- static class MyQueue {
- private Stack
stack1; - private Stack
stack2; -
- /**
- * Initialize your data structure here.
- */
- public MyQueue() {
- stack1 = new Stack<>();
- stack2 = new Stack<>();
- }
-
- /**
- * Push element x to the back of queue.
- */
- public void push(int x) {
- stack1.push(x);
- }
-
- /**
- * Removes the element from in front of queue and returns that element.
- */
- public int pop() {
- if (stack2.isEmpty()) {
- while (!stack1.isEmpty()) {
- stack2.push(stack1.pop());
- }
- }
- return stack2.pop();
- }
-
- /**
- * Get the front element.
- */
- public int peek() {
- if (stack2.isEmpty()) {
- while (!stack1.isEmpty()) {
- stack2.push(stack1.pop());
- }
- }
- return stack2.peek();
- }
-
- /**
- * Returns whether the queue is empty.
- */
- public boolean empty() {
- return stack1.isEmpty() && stack2.isEmpty();
- }
- }
-
- public static void main(String[] args) {
- MyQueue queue = new MyQueue();
-
- queue.push(1);
- queue.push(2);
- /*输出 1 */
- System.out.println(queue.peek());
- /*输出 1 */
- System.out.println(queue.pop());
- /*输出 false */
- System.out.println(queue.empty());
- }
- }
(五)用队列实现栈(Implement Stack using Queues)
题目描述:使用队列实现栈的下列操作:
- push(x) -- 元素 x 入栈
- pop() -- 移除栈顶元素
- top() -- 获取栈顶元素
- empty() -- 返回栈是否为空
注意:
- 你只能使用队列的基本操作-- 也就是 push to back, peek/pop from front, size, 和 is empty 这些操作是合法的。
- 你所使用的语言也许不支持队列。 你可以使用 list 或者 deque(双端队列)来模拟一个队列,只要是标准的队列操作即可。
解题思路
使用两个队列实现栈,需要对其中一个队列进行逆序操作,这样每次出栈时都可以直接从逆序的队列中获取到栈顶元素,从而实现了栈的功能。
具体实现过程如下:
- 定义两个队列,用来模拟栈的操作;
- 入栈时,将元素插入队列1中;
- 出栈时,将队列1中的元素依次取出并插入到队列2中,直到队列1中只剩下一个元素,然后将这个元素出队并返回;
- 获取栈顶元素时,同样先将队列1中的元素依次取出并插入到队列2中,直到队列1中只剩下一个元素,然后返回这个元素;
- 判断栈是否为空,即判断队列1和队列2是否都为空。
具体代码展示
- package org.zyf.javabasic.letcode.stack;
-
- import java.util.LinkedList;
- import java.util.Queue;
-
- /**
- * @author yanfengzhang
- * @description 使用队列实现栈的下列操作:
- *
- * push(x) -- 元素 x 入栈
- * pop() -- 移除栈顶元素
- * top() -- 获取栈顶元素
- * empty() -- 返回栈是否为空
- * 注意:
- * 你只能使用队列的基本操作-- 也就是 push to back, peek/pop from front, size, 和 is empty 这些操作是合法的。
- * 你所使用的语言也许不支持队列。 你可以使用 list 或者 deque(双端队列)来模拟一个队列,只要是标准的队列操作即可。
- * @date 2023/4/8 23:30
- */
- public class ImplementStackUseQueues {
-
- static class MyStack {
- private Queue
q1; - private Queue
q2; -
- /**
- * Initialize your data structure here.
- */
- public MyStack() {
- q1 = new LinkedList<>();
- q2 = new LinkedList<>();
- }
-
- /**
- * Push element x onto stack.
- */
- public void push(int x) {
- q1.offer(x);
- while (!q2.isEmpty()) {
- q1.offer(q2.poll());
- }
- Queue
tmp = q1; - q1 = q2;
- q2 = tmp;
- }
-
- /**
- * Removes the element on top of the stack and returns that element.
- */
- public int pop() {
- return q2.poll();
- }
-
- /**
- * Get the top element.
- */
- public int top() {
- return q2.peek();
- }
-
- /**
- * Returns whether the stack is empty.
- */
- public boolean empty() {
- return q2.isEmpty();
- }
- }
-
-
- public static void main(String[] args) {
- MyStack myStack = new MyStack();
-
- myStack.push(1);
- myStack.push(2);
- myStack.push(3);
-
- /*3 */
- System.out.println(myStack.pop());
- /*2*/
- System.out.println(myStack.top());
- /*false*/
- System.out.println(myStack.empty());
-
- myStack.pop();
- myStack.pop();
- /*true*/
- System.out.println(myStack.empty());
- }
- }
(六)接雨水(Trapping Rain Water)
题目描述:给定 n 个非负整数表示每个宽度为 1 的柱子的高度图,计算按此排列的柱子,下雨之后能够捕获多少雨水。
解题思路
以下是使用栈实现的思路:
- 定义一个栈,并初始化为空。
- 遍历数组,如果当前元素小于等于栈顶元素,则将其下标压入栈中。
- 如果当前元素大于栈顶元素,则弹出栈顶元素,并计算雨水量:以弹出元素为高度,当前元素和栈顶元素所在位置之间的距离为宽度,宽度和高度求乘积得到雨水量。
- 计算完雨水量后,将当前元素下标入栈,重复步骤2和3,直到遍历完数组。
- 返回雨水总量。
具体代码展示
- package org.zyf.javabasic.letcode.stack;
-
- import java.util.Stack;
-
- /**
- * @author yanfengzhang
- * @description 给定 n 个非负整数表示每个宽度为 1 的柱子的高度图,
- * 计算按此排列的柱子,下雨之后能够捕获多少雨水
- * @date 2023/4/8 23:48
- */
- public class TrappingRainWater {
-
- /**
- * 在这个实现中,我们首先定义了一个空栈和一个雨水总量的变量 water。
- * 然后,我们遍历高度数组。对于每个高度,我们在栈不为空且当前高度大于栈顶元素高度的情况下,执行以下操作:
- * 取出栈顶元素,作为坑洼的底部。
- * 如果此时栈为空,则无法存储雨水,跳出循环。
- * 计算坑洼的左右两侧边界。
- * 根据左右两侧边界以及底部高度计算雨水量,并将其加入 water 变量中。
- *
- * 在每次处理完高度后,我们将当前下标压入栈中,以便后续计算。最后,我们返回 water 变量即可。
- */
- public int trap(int[] height) {
- Stack
stack = new Stack<>(); - int water = 0;
- for (int i = 0; i < height.length; i++) {
- while (!stack.isEmpty() && height[i] > height[stack.peek()]) {
- /*取出栈顶元素作为坑洼的底部*/
- int bottom = stack.pop();
- /*如果栈为空,那么无法存储雨水,跳出循环*/
- if (stack.isEmpty()) {
- break;
- }
- /*计算坑洼的左右两侧边界*/
- int left = stack.peek();
- int right = i;
- /*根据左右两侧边界以及底部高度计算雨水量*/
- int width = right - left - 1;
- int heightDiff = Math.min(height[left], height[right]) - height[bottom];
- water += width * heightDiff;
- }
- /*将当前下标压入栈中*/
- stack.push(i);
- }
- return water;
- }
-
- /**
- * 在这个示例中,我们将给定的高度数组设置为 height = {0,1,0,2,1,0,1,3,2,1,2,1} ,
- * 期望的结果是能够计算出接到的雨水的总量。
- * 调用 trap(height) 方法并将返回结果打印到控制台上,预期输出结果应该是 6。
- */
- public static void main(String[] args) {
- int[] height = {0, 1, 0, 2, 1, 0, 1, 3, 2, 1, 2, 1};
- int res = new TrappingRainWater().trap(height);
- /*输出结果应为6*/
- System.out.println(res);
- }
-
-
- }
(七)逆波兰表达式求值(Evaluate Reverse Polish Notation)
题目描述:逆波兰表达式(Reverse Polish Notation,简称RPN),也叫后缀表达式,是一种数学表达式的写法,其中运算符在操作数的后面。例如,将中缀表达式 3 + 4 * 2 / (1 - 5) 转换为逆波兰表达式的过程如下:3 4 2 * 1 5 - / +。
给定一个包含正整数、加减乘除符号以及空格的字符串 tokens,请你利用栈的思想,计算该表达式的值。
注意:
- 整数除法仅保留整数部分。
- 给定的 tokens 数组中,除数不为 0。
- tokens 数组长度不超过 1000。
原题目出处:LeetCode链接
解题思路
该题的最优解法是使用栈数据结构,遍历表达式数组 tokens,遇到数字则将其压入栈中,遇到运算符则从栈中取出两个数字进行运算,然后将运算结果再次压入栈中。遍历完表达式数组后,栈顶元素即为表达式的计算结果。
具体算法如下:
- 初始化一个空栈 stack。
- 遍历表达式数组 tokens,对于每一个元素 token:
- 如果 token 是数字,则将其转换为整数并压入栈中。
- 如果 token 是运算符,则从栈中取出两个数字进行运算,然后将运算结果再次压入栈中。
- 遍历完表达式数组后,栈顶元素即为表达式的计算结果。
具体代码展示
- package org.zyf.javabasic.letcode.stack;
-
- import java.util.Stack;
-
- /**
- * @author yanfengzhang
- * @description 逆波兰表达式(Reverse Polish Notation,简称RPN),也叫后缀表达式,
- * 是一种数学表达式的写法,其中运算符在操作数的后面。
- * 例如,将中缀表达式 3 + 4 * 2 / (1 - 5) 转换为逆波兰表达式的过程如下:3 4 2 * 1 5 - / +。
- *
- * 给定一个包含正整数、加减乘除符号以及空格的字符串 tokens,请你利用栈的思想,计算该表达式的值。
- * @date 2023/4/8 23:56
- */
- public class EvaluateReversePolishNotation {
-
- /**
- * 最优解法是使用栈数据结构,遍历表达式数组 tokens,
- * 遇到数字则将其压入栈中,遇到运算符则从栈中取出两个数字进行运算,
- * 然后将运算结果再次压入栈中。遍历完表达式数组后,栈顶元素即为表达式的计算结果。
- *
- * 具体算法如下:
- * 初始化一个空栈 stack。
- * 遍历表达式数组 tokens,对于每一个元素 token:
- * 如果 token 是数字,则将其转换为整数并压入栈中。
- * 如果 token 是运算符,则从栈中取出两个数字进行运算,然后将运算结果再次压入栈中。
- * 遍历完表达式数组后,栈顶元素即为表达式的计算结果。
- */
- public int evalRPN(String[] tokens) {
- /*初始化一个空栈*/
- Stack
stack = new Stack<>(); - /*遍历表达式数组 tokens*/
- for (String token : tokens) {
- /*如果 token 是加号*/
- if (token.equals("+")) {
- /*从栈中取出第二个数字*/
- int num2 = stack.pop();
- /*从栈中取出第一个数字*/
- int num1 = stack.pop();
- /*将两个数字相加,并将运算结果压入栈中*/
- stack.push(num1 + num2);
- }
- /*如果 token 是减号*/
- else if (token.equals("-")) {
- /*从栈中取出第二个数字*/
- int num2 = stack.pop();
- /*从栈中取出第一个数字*/
- int num1 = stack.pop();
- /*将第一个数字减去第二个数字,并将运算结果压入栈中*/
- stack.push(num1 - num2);
- }
- /*如果 token 是乘号*/
- else if (token.equals("*")) {
- /*从栈中取出第二个数字*/
- int num2 = stack.pop();
- /*从栈中取出第一个数字*/
- int num1 = stack.pop();
- /*将两个数字相乘,并将运算结果压入栈中*/
- stack.push(num1 * num2);
- }
- /*如果 token 是除号*/
- else if (token.equals("/")) {
- /*从栈中取出第二个数字*/
- int num2 = stack.pop();
- /*从栈中取出第一个数字*/
- int num1 = stack.pop();
- /*将第一个数字除以第二个数字,并将运算结果压入栈中*/
- stack.push(num1 / num2);
- }
- /*如果 token 是数字*/
- else {
- /*将其转换为整数并压入栈中*/
- stack.push(Integer.parseInt(token));
- }
- }
- /*遍历完表达式数组后,栈顶元素即为表达式的计算结果*/
- return stack.pop();
- }
-
- public static void main(String[] args) {
- String[] tokens1 = {"2", "1", "+", "3", "*"};
- String[] tokens2 = {"4", "13", "5", "/", "+"};
- String[] tokens3 = {"10", "6", "9", "3", "+", "-11", "*", "/", "*", "17", "+", "5", "+"};
- /*expected output: 9*/
- System.out.println(new EvaluateReversePolishNotation().evalRPN(tokens1));
- /*expected output: 6*/
- System.out.println(new EvaluateReversePolishNotation().evalRPN(tokens2));
- /*expected output: 22*/
- System.out.println(new EvaluateReversePolishNotation().evalRPN(tokens3));
- }
- }
(八)基本计算器(Basic Calculator)
题目描述:实现一个基本的计算器来计算一个简单的字符串表达式的值。
表达式字符串可能包含左括号 ( 和右括号 ),加号 + 和减号 -,非负整数和空格。
示例 1:输入: "1 + 1". 输出: 2
示例 2:输入: " 2-1 + 2 " 输出: 3
示例 3:输入: "(1+(4+5+2)-3)+(6+8)". 输出: 23
注意:
你可以假设所给定的表达式都是有效的。
请不要使用内置的库函数 eval。
解题思路
使用栈存储数字和运算符,遇到括号则递归计算括号内的表达式。
- 初始化一个栈 stack 存储数字和运算符,和一个变量 res 存储结果,一个变量 sign 存储运算符的正负。
- 遍历表达式 s,如果当前字符是数字,用一个 while 循环读取整个数字并将其压入栈中;如果是加号,则将正号压入栈中,并将 sign 设为正;如果是减号,则将负号压入栈中,并将 sign 设为负;如果是左括号,则递归计算括号内的表达式,并将结果压入栈中;如果是右括号,则将栈顶的运算符弹出并计算到左括号,弹出左括号。
- 遍历完表达式后,将栈中剩余的数字和运算符依次弹出并计算结果。
时间复杂度:O(n),其中n是表达式的长度。空间复杂度:O(n)。
具体代码展示
- package org.zyf.javabasic.letcode.stack;
-
- import java.util.Stack;
-
- /**
- * @author yanfengzhang
- * @description
- * @date 2023/4/9 00:05
- */
- public class BasicCalculator {
-
- /**
- * 使用栈存储数字和运算符,遇到括号则递归计算括号内的表达式。
- *
- * 1 初始化一个栈 stack 存储数字和运算符,和一个变量 res 存储结果,一个变量 sign 存储运算符的正负。
- * 2 遍历表达式 s,如果当前字符是数字,用一个 while 循环读取整个数字并将其压入栈中;
- * 如果是加号,则将正号压入栈中,并将 sign 设为正;
- * 如果是减号,则将负号压入栈中,并将 sign 设为负;
- * 如果是左括号,则递归计算括号内的表达式,并将结果压入栈中;
- * 如果是右括号,则将栈顶的运算符弹出并计算到左括号,弹出左括号。
- * 3 遍历完表达式后,将栈中剩余的数字和运算符依次弹出并计算结果。
- */
- public int calculate(String s) {
- /*数字栈*/
- Stack
stack = new Stack<>(); - /*当前数字*/
- int num = 0;
- /*当前结果*/
- int result = 0;
- /*符号,1表示加,-1表示减*/
- int sign = 1;
- for (char c : s.toCharArray()) {
- /*如果当前字符是数字*/
- if (Character.isDigit(c)) {
- /*将数字拼接起来*/
- num = num * 10 + (c - '0');
- }
- /*如果当前字符是加号*/
- else if (c == '+') {
- /*先将之前的数字和符号加入结果中*/
- result += sign * num;
- /*重置当前数字*/
- num = 0;
- /*设置符号为加*/
- sign = 1;
- }
- /*如果当前字符是减号*/
- else if (c == '-') {
- /*先将之前的数字和符号加入结果中*/
- result += sign * num;
- /*重置当前数字*/
- num = 0;
- /*设置符号为减*/
- sign = -1;
- }
- /*如果当前字符是左括号*/
- else if (c == '(') {
- /*将当前结果入栈*/
- stack.push(result);
- /*将当前符号入栈*/
- stack.push(sign);
- /*重置当前结果*/
- result = 0;
- /*重置当前符号*/
- sign = 1;
- }
- /*如果当前字符是右括号*/
- else if (c == ')') {
- /*先将当前数字加入结果中*/
- result += sign * num;
- /*重置当前数字*/
- num = 0;
- /*取出栈顶符号乘以当前结果*/
- result *= stack.pop();
- /*加上栈顶数字*/
- result += stack.pop();
- }
- }
- /*处理最后一个数字*/
- if (num != 0) {
- result += sign * num;
- }
- return result;
- }
-
- public static void main(String[] args) {
- String s = "1 + (2 - 3) + 4";
- int result = new BasicCalculator().calculate(s);
- /*输出4*/
- System.out.println(result);
- }
-
- }
(九)简化路径(Simplify Path)
题目描述:给定一个字符串路径,返回其简化的绝对路径。
例如,path = "/home/", => "/home";path = "/../", => "/";path = "/home//foo/", => "/home/foo";path = "/a/./b/../../c/", => "/c"
在简化路径时,要考虑以下规则:
1.路径是由斜杠'/'分隔的一个或多个目录或文件名组成的。
2.在Unix风格的文件系统中,'.'表示当前目录,'..'表示上一级目录。
3.如果路径中包含多个连续斜杠'/',则应将它们替换为一个单独的斜杠'/'。
4.路径应以斜杠'/'开头,并且在路径末尾不应该有斜杠'/'。
请注意,返回的简化路径必须始终以斜杠'/'开头,并且在路径末尾不能有斜杠'/'。
解题思路
最优解法是使用栈。具体做法如下:
- 以“/”作为分隔符,将输入路径拆分成若干个部分。
- 遍历这些部分:
- 如果部分为空或为“.””,则不做处理,继续遍历下一个部分。
- 如果部分为“..”,则将栈顶元素弹出,表示返回上级目录。
- 否则,将部分压入栈中,表示进入下一级目录。
- 遍历完所有部分后,将栈中的元素按顺序拼接起来,得到简化后的路径。
具体代码展示
- package org.zyf.javabasic.letcode.stack;
-
- import java.util.Stack;
-
- /**
- * @author yanfengzhang
- * @description 给定一个字符串路径,返回其简化的绝对路径。
- * 例如,path = "/home/", => "/home";path = "/../", => "/";
- * path = "/home//foo/", => "/home/foo";
- * path = "/a/./b/../../c/", => "/c"
- * @date 2023/4/9 00:17
- */
- public class SimplifyPath {
-
- /**
- * 最优解法是使用栈。具体做法如下:
- *
- * 1 以“/”作为分隔符,将输入路径拆分成若干个部分。
- * 遍历这些部分:
- * 如果部分为空或为“.””,则不做处理,继续遍历下一个部分。
- * 如果部分为“..”,则将栈顶元素弹出,表示返回上级目录。
- * 否则,将部分压入栈中,表示进入下一级目录。
- * 2 遍历完所有部分后,将栈中的元素按顺序拼接起来,得到简化后的路径。
- */
- public String simplifyPath(String path) {
- Stack
stack = new Stack<>(); - String[] arr = path.split("/");
- for (String s : arr) {
- if (s.equals(".") || s.equals("")) {
- /*对于"."和空字符串,不做任何操作*/
- } else if (s.equals("..")) {
- /*如果是"..", 则需要返回上一级目录*/
- if (!stack.isEmpty()) {
- stack.pop();
- }
- } else {
- /*否则就是一个普通的路径名,需要入栈*/
- stack.push(s);
- }
- }
-
- /*构造简化后的路径*/
- StringBuilder sb = new StringBuilder();
- for (String s : stack) {
- sb.append("/").append(s);
- }
-
- /*如果栈为空,说明简化后的路径是根路径"/" */
- if (stack.isEmpty()) {
- return "/";
- }
-
- return sb.toString();
- }
-
- public static void main(String[] args) {
- String path = "/a//b/../../c/";
- /*输出:/c */
- System.out.println(new SimplifyPath().simplifyPath(path));
- }
- }
(十) 岛屿数量
题目描述:给你一个由 '1'(陆地)和 '0'(水)组成的的二维网格,请你计算网格中岛屿的数量。
岛屿总是被水包围,并且每座岛屿只能由水平方向和/或竖直方向上相邻的陆地连接形成。
此外,你可以假设该网格的四条边均被水包围。
提示:
- m == grid.length
- n == grid[i].length
- 1 <= m, n <= 300
- grid[i][j] 的值为 '0' 或 '1'
来源:力扣(LeetCode)
链接:https://leetcode.cn/problems/number-of-islands
解法思路
使用栈的解法可以通过深度优先搜索(DFS)来实现。遍历矩阵中每一个元素,对于每一个为1的元素,将其作为起点,进行DFS遍历并将经过的元素标记为0,统计遍历的次数即为岛屿的数量。具体实现思路如下:
- 遍历矩阵中的每个元素,当元素为1时,进入DFS遍历。
- 进入DFS遍历时,将当前元素标记为0,表示已经遍历过。
- 遍历当前元素的四个方向(上、下、左、右),如果该方向上的元素为1,则进入该方向继续DFS遍历。
- 当所有与当前元素相连通的元素都被遍历过后,回到上一个节点继续遍历。
- 统计遍历的次数即为岛屿的数量。
这种解法的时间复杂度为O(mn),其中m和n分别为矩阵的行数和列数。空间复杂度为O(mn),因为需要使用一个二维数组来标记每个元素是否已经遍历过。
具体代码展示
- package org.zyf.javabasic.letcode.stack;
-
- import java.util.Stack;
-
- /**
- * @author yanfengzhang
- * @description 给你一个由'1'(陆地)和 '0'(水)组成的的二维网格,请你计算网格中岛屿的数量。
- * 岛屿总是被水包围,并且每座岛屿只能由水平方向和/或竖直方向上相邻的陆地连接形成。
- * 此外,你可以假设该网格的四条边均被水包围。
- * @date 2023/4/9 15:35
- */
- public class NumIslands {
-
- /**
- * 具体实现思路如下:
- *
- * 遍历矩阵中的每个元素,当元素为1时,进入DFS遍历。
- * 进入DFS遍历时,将当前元素标记为0,表示已经遍历过。
- * 遍历当前元素的四个方向(上、下、左、右),如果该方向上的元素为1,则进入该方向继续DFS遍历。
- * 当所有与当前元素相连通的元素都被遍历过后,回到上一个节点继续遍历。
- * 统计遍历的次数即为岛屿的数量。
- */
- public int numIslands(char[][] grid) {
- /*记录岛屿数量*/
- int count = 0;
- int m = grid.length;
- if (m == 0) {
- return 0;
- }
- int n = grid[0].length;
- /*方向数组,用于四个方向的遍历*/
- int[] dx = {0, 0, 1, -1};
- int[] dy = {1, -1, 0, 0};
- /*记录当前位置是否已经被访问*/
- boolean[][] visited = new boolean[m][n];
- /*用栈来模拟深度优先搜索*/
- Stack<int[]> stack = new Stack<>();
- for (int i = 0; i < m; i++) {
- for (int j = 0; j < n; j++) {
- /*如果当前位置是陆地且没有被访问过*/
- if (grid[i][j] == '1' && !visited[i][j]) {
- /*岛屿数量+1*/
- count++;
- /*将当前位置入栈*/
- stack.push(new int[]{i, j});
- /*标记当前位置为已访问*/
- visited[i][j] = true;
- /*栈不为空时继续搜索*/
- while (!stack.empty()) {
- /*取出栈顶元素作为当前位置*/
- int[] cur = stack.pop();
- /*四个方向遍历*/
- for (int k = 0; k < 4; k++) {
- int x = cur[0] + dx[k];
- int y = cur[1] + dy[k];
- /*如果满足条件,则入栈并标记为已访问*/
- if (x >= 0 && x < m && y >= 0
- && y < n
- && !visited[x][y]
- && grid[x][y] == '1') {
- stack.push(new int[]{x, y});
- visited[x][y] = true;
- }
- }
- }
- }
- }
- }
- return count;
- }
-
- public static void main(String[] args) {
- char[][] grid = {
- {'1', '1', '0', '0', '0'},
- {'1', '1', '0', '0', '0'},
- {'0', '0', '1', '0', '0'},
- {'0', '0', '0', '1', '1'}
- };
- int numIslands = new NumIslands().numIslands(grid);
- /*输出 3*/
- System.out.println(numIslands);
- }
-
- }
扩展:其他最优解法展示
思路
该问题可以通过深度优先搜索(DFS)或广度优先搜索(BFS)来解决。下面以DFS为例,介绍最优解法思路:
- 遍历整个二维网格,当发现一个为 '1' 的点时,从这个点开始进行 DFS。在 DFS 过程中,将访问到的 '1' 置为 '0',防止重复访问。同时,沿着当前点的上下左右四个方向继续深入搜索。
- 深度优先搜索结束后,岛屿的数量加1。继续遍历网格,如果发现下一个点为 '1',则进行新一轮 DFS。
- 遍历完整个网格后,就可以得到岛屿的数量。
可以用递归或显式的栈来实现 DFS,具体实现方式因人而异。
时间复杂度为 O(mn),其中 m 和 n 分别为网格的行数和列数。
代码展示
以下是基于DFS实现的Java代码,其中用 visited 数组记录每个点是否已经被访问过,从一个点开始搜索时,将其 visited 数组值设为 true,表示已经访问过。在 DFS 中,每次向四个方向递归搜索时,需要先检查坐标是否合法,并且当前点是否为 '1'。如果坐标合法且当前点为 '1',则将其 visited 数组值设为 true,并继续向四个方向递归搜索。
- public int numIslands(char[][] grid) {
- /*边界条件判断*/
- if (grid == null || grid.length == 0) {
- return 0;
- }
- int m = grid.length;
- int n = grid[0].length;
- /*记录每个点是否已经被访问过*/
- boolean[][] visited = new boolean[m][n];
- /*岛屿数量计数器*/
- int count = 0;
- /*遍历整个网格*/
- for (int i = 0; i < m; i++) {
- for (int j = 0; j < n; j++) {
- /*如果当前点为 '1' 且未被访问过,说明发现一个新的岛屿,需要进行 DFS 搜索*/
- if (!visited[i][j] && grid[i][j] == '1') {
- /*岛屿数量加1*/
- count++;
- /*从当前点开始进行 DFS 搜索*/
- dfs(grid, visited, i, j);
- }
- }
- }
- /*返回岛屿数量*/
- return count;
- }
-
- private void dfs(char[][] grid, boolean[][] visited, int i, int j) {
- /*如果坐标不合法,或当前点已被访问过,或当前点为 '0',则直接返回*/
- if (i < 0 || i >= grid.length || j < 0 || j >= grid[0].length || visited[i][j] || grid[i][j] == '0') {
- return;
- }
- /*将当前点标记为已访问*/
- visited[i][j] = true;
- /*分别向上、下、左、右四个方向递归搜索*/
- /*上*/
- dfs(grid, visited, i - 1, j);
- /*下*/
- dfs(grid, visited, i + 1, j);
- /*左*/
- dfs(grid, visited, i, j - 1);
- /*右*/
- dfs(grid, visited, i, j + 1);
- }
(十一)用数组实现一个栈
题目描述:设计一个栈数据结构,使用数组来实现。需要支持入栈、出栈、获取栈顶元素和判断栈是否为空的操作。
解题思路
基本解题思路如下:
- 创建一个数组 stack 用于存储栈中的元素。
- 维护一个变量 top 表示栈顶的索引位置,初始值为 -1。
- 入栈操作:将要入栈的元素放入 stack 数组中 top+1 的位置,然后将 top 的值加 1。
- 出栈操作:从 stack 数组中取出 top 位置的元素,然后将 top 的值减 1。
- 获取栈顶元素操作:返回 stack 数组中 top 位置的元素。
- 判断栈是否为空操作:当 top 的值为 -1 时,表示栈为空。
具体代码展示
- package org.zyf.javabasic.letcode.stack;
-
- /**
- * @author yanfengzhang
- * @description 设计一个栈数据结构,使用数组来实现。
- * 需要支持入栈、出栈、获取栈顶元素和判断栈是否为空的操作。
- *
- * 解题思路:
- * 1. 创建一个数组 stack 用于存储栈中的元素。
- * 2. 维护一个变量 top 表示栈顶的索引位置,初始值为 -1。
- * 3. 入栈操作:将要入栈的元素放入 stack 数组中 top+1 的位置,然后将 top 的值加 1。
- * 4. 出栈操作:从 stack 数组中取出 top 位置的元素,然后将 top 的值减 1。
- * 5. 获取栈顶元素操作:返回 stack 数组中 top 位置的元素。
- * 6. 判断栈是否为空操作:当 top 的值为 -1 时,表示栈为空。
- * @date 2023/6/14 23:28
- */
- public class ImplementStackUseArray {
- private int[] stack;
- private int top;
-
- public ImplementStackUseArray(int capacity) {
- stack = new int[capacity];
- /*将 top 初始化为 -1*/
- top = -1;
- }
-
- /**
- * push(int element) 方法用于将元素入栈,
- * 它首先检查栈是否已满,
- * 然后将元素放入 top+1 的位置,
- * 最后将 top 的值加 1。
- * @param element
- */
- public void push(int element) {
- if (top == stack.length - 1) {
- throw new IllegalStateException("Stack is full");
- }
- stack[++top] = element;
- }
-
- /**
- * pop() 方法用于出栈,
- * 它首先检查栈是否为空,
- * 然后返回 top 位置的元素,
- * 最后将 top 的值减 1。
- * @return
- */
- public int pop() {
- if (isEmpty()) {
- throw new IllegalStateException("Stack is empty");
- }
- return stack[top--];
- }
-
- /**
- * peek() 方法用于获取栈顶元素,
- * 它首先检查栈是否为空,然后返回 top 位置的元素。
- * @return
- */
- public int peek() {
- if (isEmpty()) {
- throw new IllegalStateException("Stack is empty");
- }
- return stack[top];
- }
-
- /**
- * isEmpty() 方法用于判断栈是否为空,
- * 它通过判断 top 的值是否为 -1 来确定
- * @return
- */
- public boolean isEmpty() {
- return top == -1;
- }
-
- public static void main(String[] args) {
- ImplementStackUseArray stack = new ImplementStackUseArray(5);
-
- stack.push(10);
- stack.push(20);
- stack.push(30);
-
- // 输出 Top element: 30
- System.out.println("Top element: " + stack.peek());
-
- // 输出 Popped element: 30
- System.out.println("Popped element: " + stack.pop());
-
- // 输出 Top element after pop: 20
- System.out.println("Top element after pop: " + stack.peek());
-
- // 输出 Is stack empty? false
- System.out.println("Is stack empty? " + stack.isEmpty());
- }
- }
(十二)基本数学运算表达式求值
题目描述:给定一个包含加法、减法、乘法和除法的算术表达式字符串,设计一个程序来计算其结果。也就是说给个1+2*5-6\2字符串,最后计算出来就行
解题思路
要计算一个包含加减乘除的算术表达式,可以使用栈(Stack)数据结构来辅助计算。以下是一种常见的解题思路:
- 创建两个栈,一个用于存储操作数(Operand Stack),一个用于存储运算符(Operator Stack)。
- 从左到右依次遍历表达式的每个字符。
- 如果当前字符是数字,则将其解析为操作数,并将操作数入栈到 Operand Stack。
- 如果当前字符是运算符,则执行以下步骤:a. 如果 Operator Stack 为空,或者栈顶运算符是左括号 ‘(’,则将当前运算符入栈到 Operator Stack。 b. 如果当前运算符优先级大于栈顶运算符优先级,或者栈顶运算符是左括号 ‘(’,则将当前运算符入栈到 Operator Stack。 c. 否则,从 Operator Stack 弹出栈顶运算符,从 Operand Stack 弹出两个操作数,进行相应的计算,并将结果入栈到 Operand Stack。重复该步骤直到满足条件 b,然后将当前运算符入栈到 Operator Stack。注意:在比较运算符优先级时,可以使用一定的约定,例如将加法和减法设定为优先级较低,乘法和除法设定为优先级较高。
- 如果当前字符是左括号 ‘(’,则将其入栈到 Operator Stack。
- 如果当前字符是右括号 ‘)’,则执行以下步骤: 从 Operator Stack 弹出栈顶运算符,从 Operand Stack 弹出两个操作数,进行相应的计算,并将结果入栈到 Operand Stack。重复该步骤直到遇到左括号 ‘(’,将其从 Operator Stack 弹出并丢弃。
- 遍历完表达式后,执行以下步骤:a. 如果 Operator Stack 不为空,则从 Operator Stack 弹出栈顶运算符,从 Operand Stack 弹出两个操作数,进行相应的计算,并将结果入栈到 Operand Stack。重复该步骤直到 Operator Stack 为空。b. 最后 Operand Stack 中剩下的唯一元素就是表达式的最终结果。
具体代码展示
- package org.zyf.javabasic.letcode.stack;
-
- import java.util.Stack;
-
- /**
- * @author yanfengzhang
- * @description 给定一个包含加法、减法、乘法和除法的算术表达式字符串,设计一个程序来计算其结果。
- * @date 2023/6/13 23:29
- */
- public class ArithmeticExpressionCalculator {
-
- /**
- *
- * 要计算一个包含加减乘除的算术表达式,可以使用栈(Stack)数据结构来辅助计算。以下是一种常见的解题思路:
- * 1. 创建两个栈,一个用于存储操作数(Operand Stack),一个用于存储运算符(Operator Stack)。
- * 2. 从左到右依次遍历表达式的每个字符。
- * 3. 如果当前字符是数字,则将其解析为操作数,并将操作数入栈到 Operand Stack。
- * 4. 如果当前字符是运算符,则执行以下步骤:
- * • a. 如果 Operator Stack 为空,或者栈顶运算符是左括号 ‘(’,则将当前运算符入栈到 Operator Stack。
- * • b. 如果当前运算符优先级大于栈顶运算符优先级,或者栈顶运算符是左括号 ‘(’,则将当前运算符入栈到 Operator Stack。
- * • c. 否则,从 Operator Stack 弹出栈顶运算符,从 Operand Stack 弹出两个操作数,进行相应的计算,并将结果入栈到 Operand Stack。重复该步骤直到满足条件 b,然后将当前运算符入栈到 Operator Stack。
- * • 注意:在比较运算符优先级时,可以使用一定的约定,例如将加法和减法设定为优先级较低,乘法和除法设定为优先级较高。
- * 5. 如果当前字符是左括号 ‘(’,则将其入栈到 Operator Stack。
- * 6. 如果当前字符是右括号 ‘)’,则执行以下步骤:
- * • a. 从 Operator Stack 弹出栈顶运算符,从 Operand Stack 弹出两个操作数,进行相应的计算,并将结果入栈到 Operand Stack。重复该步骤直到遇到左括号 ‘(’,将其从 Operator Stack 弹出并丢弃。
- * 7. 遍历完表达式后,执行以下步骤:
- * • a. 如果 Operator Stack 不为空,则从 Operator Stack 弹出栈顶运算符,从 Operand Stack 弹出两个操作数,进行相应的计算,并将结果入栈到 Operand Stack。重复该步骤直到 Operator Stack 为空。
- * • b. 最后 Operand Stack 中剩下的唯一元素就是表达式的最终结果。
- * @param expression
- * @return
- */
- public static int calculate(String expression) {
- Stack
operandStack = new Stack<>(); - Stack
operatorStack = new Stack<>(); -
- for (int i = 0; i < expression.length(); i++) {
- char ch = expression.charAt(i);
- if (Character.isDigit(ch)) {
- // 当前字符是数字,将其解析为操作数并入栈
- // 将字符转换为整数
- int operand = ch - '0';
- while (i + 1 < expression.length()
- && Character.isDigit(expression.charAt(i + 1))) {
- // 继续读取多位数字
- operand = operand * 10 + (expression.charAt(i + 1) - '0');
- i++;
- }
- operandStack.push(operand);
- } else if (ch == '(') {
- // 当前字符是左括号,入栈到运算符栈
- operatorStack.push(ch);
- } else if (ch == ')') {
- // 当前字符是右括号,执行计算直到遇到左括号
- while (!operatorStack.isEmpty() && operatorStack.peek() != '(') {
- calculateTopOperation(operandStack, operatorStack);
- }
- // 弹出左括号 '('
- if (!operatorStack.isEmpty() && operatorStack.peek() == '(') {
- operatorStack.pop();
- }
- } else if (ch == '+' || ch == '-' || ch == '*' || ch == '/') {
- // 当前字符是运算符
- while (!operatorStack.isEmpty()
- && shouldEvaluateTopOperation(ch, operatorStack.peek())) {
- calculateTopOperation(operandStack, operatorStack);
- }
- operatorStack.push(ch);
- }
- }
-
- // 执行剩余的计算
- while (!operatorStack.isEmpty()) {
- calculateTopOperation(operandStack, operatorStack);
- }
-
- // 最后 Operand Stack 中剩下的唯一元素就是表达式的最终结果
- return operandStack.pop();
- }
-
- private static boolean shouldEvaluateTopOperation(char currentOperator, char topOperator) {
- if (topOperator == '(' || topOperator == ')') {
- return false;
- }
- if ((currentOperator == '*' || currentOperator == '/')
- && (topOperator == '+' || topOperator == '-')) {
- return false;
- }
- return true;
- }
-
- private static void calculateTopOperation(Stack
operandStack, Stack operatorStack) { - char operator = operatorStack.pop();
- int operand2 = operandStack.pop();
- int operand1 = operandStack.pop();
- int result = 0;
- switch (operator) {
- case '+':
- result = operand1 + operand2;
- break;
- case '-':
- result = operand1 - operand2;
- break;
- case '*':
- result = operand1 * operand2;
- break;
- case '/':
- result = operand1 / operand2;
- break;
- }
- operandStack.push(result);
- }
-
- public static void main(String[] args) {
- String expression1 = "1+2*5-6/2";
- int result1 = calculate(expression1);
- System.out.println(expression1 + "=" + result1);
-
- String expression2 = "1 + (2 - 3) + 4";
- int result2 = calculate(expression2);
- System.out.println(expression2 + "=" + result2);
- }
- }
(十三) IP 范围判断
题目描述:给定一个二叉树,每个节点包含一个 IP 段的范围,其中 ip1 和 ip2 分别表示左子树和右子树的 IP 范围。现在给定一个 IP 地址,判断该 IP 是否在任意节点的 IP 范围内。
解题思路
为了判断一个 IP 地址是否在二叉树的某个节点的 IP 范围内,我们可以采用非递归的方式进行树的遍历,并在遍历过程中进行比较。以下是一种可能的解法:
- 定义一个栈 stack,用于存储待遍历的节点。
- 将根节点入栈。
- 循环遍历栈,直到栈为空:• 弹出栈顶节点 node。• 判断当前 IP 地址是否在 node 的 IP 范围内。如果在范围内,则返回 true。• 如果 node 的左子节点不为空且当前 IP 小于等于左子节点的 IP 范围的最大值,将左子节点入栈。• 如果 node 的右子节点不为空且当前 IP 大于等于右子节点的 IP 范围的最小值,将右子节点入栈。
- 如果遍历完整个树,仍未找到匹配的 IP 范围,则返回 false。
通过以上方法,我们可以判断给定的 IP 地址是否在二叉树的任意节点的 IP 范围内。该算法的时间复杂度取决于树的深度,最坏情况下为 O(n),其中 n 是树中节点的个数。
具体代码展示
先初始化定义IP树节点如下:
- package org.zyf.javabasic.letcode.stack.base;
-
- /**
- * @author yanfengzhang
- * @description
- * @date 2023/6/11 23:54
- */
- public class IPTreeNode {
- public String ip1;
- public String ip2;
- public IPTreeNode left;
- public IPTreeNode right;
-
- public IPTreeNode(String ip1, String ip2) {
- this.ip1 = ip1;
- this.ip2 = ip2;
- }
- }
具体代码分析展示如下:
- package org.zyf.javabasic.letcode.stack;
-
- import org.zyf.javabasic.letcode.stack.base.IPTreeNode;
-
- import java.util.Stack;
-
- /**
- * @author yanfengzhang
- * @description 给定一个二叉树,每个节点包含一个 IP 段的范围,其中 ip1 和 ip2 分别表示左子树和右子树的 IP 范围。
- * 现在给定一个 IP 地址,判断该 IP 是否在任意节点的 IP 范围内。
- * @date 2023/6/11 00:09
- */
- public class IPAddressRange {
- public boolean isIPInRange(IPTreeNode root, String ip) {
- if (root == null || ip == null) {
- return false;
- }
-
- Stack
stack = new Stack<>(); - stack.push(root);
-
- while (!stack.isEmpty()) {
- IPTreeNode node = stack.pop();
-
- if (isIPInRange(ip, node.ip1, node.ip2)) {
- return true;
- }
-
- if (node.left != null) {
- stack.push(node.left);
- }
-
- if (node.right != null) {
- stack.push(node.right);
- }
- }
-
- return false;
- }
-
- private boolean isIPInRange(String ip, String startIP, String endIP) {
- long ipValue = ipToLong(ip);
- long startValue = ipToLong(startIP);
- long endValue = ipToLong(endIP);
-
- return ipValue >= startValue && ipValue <= endValue;
- }
-
- private long ipToLong(String ip) {
- String[] parts = ip.split("\\.");
- long result = 0;
-
- for (int i = 0; i < 4; i++) {
- result = result * 256 + Long.parseLong(parts[i]);
- }
-
- return result;
- }
-
- public static void main(String[] args) {
- IPTreeNode root = new IPTreeNode("192.168.0.1", "192.168.0.100");
- root.left = new IPTreeNode("10.0.0.1", "10.0.0.10");
- root.right = new IPTreeNode("172.16.0.1", "172.16.0.50");
-
- IPAddressRange solution = new IPAddressRange();
- String ip = "10.0.0.5";
- boolean isInRange = solution.isIPInRange(root, ip);
- // 输出 10.0.0.5 is in range: true
- System.out.println(ip + " is in range: " + isInRange);
- }
- }
评论记录:
回复评论: