文章目录
- 71.【中等】简化路径
- 155.【中等】最小栈
- 150.【中等】逆波兰表达式求值
- 224.【困难】基本计算器
- 2.【中等】两数相加
🌈你好呀!我是 山顶风景独好
💕欢迎来到我的博客,很高兴能够在这里和您见面!
💕希望您在这里可以感受到一份轻松愉快的氛围!
💕这里不仅可以获得有趣的内容和知识,也可以畅所欲言、分享您的想法和见解。
🚀 欢迎一起踏上探险之旅,挖掘无限可能,共同成长!
71.【中等】简化路径
- 给你一个字符串 path ,表示指向某一文件或目录的 Unix 风格 绝对路径 (以 ‘/’ 开头),请你将其转化为更加简洁的规范路径。
- 在 Unix 风格的文件系统中,一个点(.)表示当前目录本身;此外,两个点 (…) 表示将目录切换到上一级(指向父目录);两者都可以是复杂相对路径的组成部分。任意多个连续的斜杠(即,‘//’)都被视为单个斜杠 ‘/’ 。 对于此问题,任何其他格式的点(例如,‘…’)均被视为文件/目录名称。
- 请注意,返回的 规范路径 必须遵循下述格式:
- 始终以斜杠 ‘/’ 开头。
两个目录名之间必须只有一个斜杠 ‘/’ 。
最后一个目录名(如果存在)不能 以 ‘/’ 结尾。
此外,路径仅包含从根目录到目标文件或目录的路径上的目录(即,不含 ‘.’ 或 ‘…’)。
返回简化后得到的 规范路径 。
示例 1:
- 输入:path = “/home/”
- 输出:“/home”
- 解释:注意,最后一个目录名后面没有斜杠。
示例 2:
- 输入:path = “/…/”
- 输出:“/”
- 解释:从根目录向上一级是不可行的,因为根目录是你可以到达的最高级。
示例 3:
- 输入:path = “/home//foo/”
- 输出:“/home/foo”
- 解释:在规范路径中,多个连续斜杠需要用一个斜杠替换。
示例 4:- 输入:path = “/a/./b/…/…/c/”
- 输出:“/c”
解题思路:
思路与算法
我们首先将给定的字符串 path\textit{path}path 根据 /\texttt{/}/ 分割成一个由若干字符串组成的列表,记为 names\textit{names}names。根据题目中规定的「规范路径的下述格式」,names\textit{names}names 中包含的字符串只能为以下几种:
- 空字符串。例如当出现多个连续的 /\texttt{/}/,就会分割出空字符串;
- 一个点 .
- 两个点 . .
- 只包含英文字母、数字或 \texttt{_} 的目录名。
对于「空字符串」以及「一个点」,我们实际上无需对它们进行处理,因为「空字符串」没有任何含义,而「一个点」表示当前目录本身,我们无需切换目录。
对于「两个点」或者「目录名」,我们则可以用一个栈来维护路径中的每一个目录名。当我们遇到「两个点」时,需要将目录切换到上一级,因此只要栈不为空,我们就弹出栈顶的目录。当我们遇到「目录名」时,就把它放入栈。
这样一来,我们只需要遍历 names 中的每个字符串并进行上述操作即可。在所有的操作完成后,我们将从栈底到栈顶的字符串用 /进行连接,再在最前面加上 /表示根目录,就可以得到简化后的规范路径。
java">class Solution { // 简化文件路径的方法 public String simplifyPath(String path) { // 使用"/"作为分隔符将路径分割成多个部分 String[] names = path.split("/"); // 使用双端队列(Deque)来模拟栈,存储路径中的目录名 Deque<String> stack = new ArrayDeque<String>(); // 遍历分割后的每个部分 for (String name : names) { // 如果当前部分表示上级目录("..") if ("..".equals(name)) { // 如果栈不为空,则弹出栈顶元素(表示进入上一级目录) if (!stack.isEmpty()) { stack.pollLast(); } // 如果当前部分长度大于0且不是当前目录(".") } else if (name.length() > 0 && !".".equals(name)) { // 将当前部分压入栈中(表示进入下一级目录) stack.offerLast(name); } } // 创建一个StringBuffer对象来构建简化后的路径 StringBuffer ans = new StringBuffer(); // 如果栈为空(即原始路径只包含"/"或空字符串),则简化后的路径应为"/" if (stack.isEmpty()) { ans.append('/'); } else { // 如果栈不为空,则从栈底到栈顶依次弹出元素,并添加到StringBuffer中 // 以此构建从根目录开始的简化路径 while (!stack.isEmpty()) { ans.append('/'); ans.append(stack.pollFirst()); } } // 将StringBuffer转换为String并返回 return ans.toString(); }
}
155.【中等】最小栈
设计一个支持 push ,pop ,top 操作,并能在常数时间内检索到最小元素的栈。
实现 MinStack 类:
- MinStack() 初始化堆栈对象。
- void push(int val) 将元素val推入堆栈。
- void pop() 删除堆栈顶部的元素。
- int top() 获取堆栈顶部的元素。
- int getMin() 获取堆栈中的最小元素。
java">class MinStack { // 链表栈,用于存储每个元素与未压入该元素时栈中最小元素的差值 LinkedList<Long> stack; // 当前已压入栈中元素的最小值 private long min; // 构造函数,初始化栈 public MinStack() { stack = new LinkedList<>(); // 创建一个空的LinkedList实例作为栈 } // 将元素压入栈 public void push(int val) { // 如果栈为空 if(stack.isEmpty()){ // 第一个元素压入时,min直接赋值为该元素 min = val; // 压入一个0,因为此时没有与min的差值(因为是第一个元素) stack.addFirst(0L); return; } // 栈不为空时,计算当前元素与min的差值,并压入差值 stack.addFirst((long)val-min); // 压入差值,因为addFirst是头插法,所以这里是从链表头部插入 // 更新min为当前元素与min的较小值 min = Math.min((long)val,min); } // 弹出栈顶元素 public void pop() { long pop = stack.removeFirst(); // 弹出栈顶元素(差值) // 如果弹出的差值小于0,说明弹出的是当前栈中的最小值 if(pop<0){ // 原来的min减去差值就是新的min(因为pop是之前的最小值与当前弹出元素的差值) long lastMin = min; min = lastMin - pop; } // 若差值大于等于0,说明弹出的不是最小值,min不变 } // 获取栈顶元素 public int top() { long peek = stack.getFirst(); // 获取栈顶元素(差值),但不出栈 // 如果栈顶元素小于等于0,说明栈顶元素就是当前最小值 if(peek<=0) return (int)min; // 否则,当前元素的值是min加上差值 return (int)(min+peek); } // 获取栈中当前的最小值 public int getMin() { return (int)min; }
}
150.【中等】逆波兰表达式求值
给你一个字符串数组 tokens ,表示一个根据 逆波兰表示法 表示的算术表达式。
请你计算该表达式。返回一个表示表达式值的整数。
注意:
- 有效的算符为 ‘+’、‘-’、‘*’ 和 ‘/’ 。
- 每个操作数(运算对象)都可以是一个整数或者另一个表达式。
- 两个整数之间的除法总是 向零截断 。
- 表达式中不含除零运算。
- 输入是一个根据逆波兰表示法表示的算术表达式。
- 答案及所有中间计算结果可以用 32 位 整数表示。
示例 1:
- 输入:tokens = [“2”,“1”,“+”,“3”,“*”]
- 输出:9
- 解释:该算式转化为常见的中缀算术表达式为:((2 + 1) * 3) = 9
示例 2:
- 输入:tokens = [“4”,“13”,“5”,“/”,“+”]
- 输出:6
- 解释:该算式转化为常见的中缀算术表达式为:(4 + (13 / 5)) = 6
解题思路
逆波兰表达式严格遵循「从左到右」的运算。计算逆波兰表达式的值时,使用一个栈存储操作数,从左到右遍历逆波兰表达式,进行如下操作:
- 如果遇到操作数,则将操作数入栈;
- 如果遇到运算符,则将两个操作数出栈,其中先出栈的是右操作数,后出栈的是左操作数,使用运算符对两个操作数进行运算,将运算得到的新操作数入栈。
整个逆波兰表达式遍历完毕之后,栈内只有一个元素,该元素即为逆波兰表达式的值。
java">class Solution { // 根据逆波兰表达式(RPN)计算表达式的值 public int evalRPN(String[] tokens) { // 使用双端队列(Deque)模拟栈,存储整数 Deque<Integer> stack = new LinkedList<Integer>(); // 表达式中token的数量 int n = tokens.length; // 遍历表达式中的每个token for (int i = 0; i < n; i++) { // 获取当前token String token = tokens[i]; // 如果token是数字 if (isNumber(token)) { // 将数字转换为整数并压入栈中 stack.push(Integer.parseInt(token)); } else { // 如果token是运算符,则弹出两个数字 int num2 = stack.pop(); // 弹出第二个数字(右操作数) int num1 = stack.pop(); // 弹出第一个数字(左操作数) // 根据运算符进行相应的计算 switch (token) { case "+": // 加法运算 stack.push(num1 + num2); break; case "-": // 减法运算 stack.push(num1 - num2); break; case "*": // 乘法运算 stack.push(num1 * num2); break; case "/": // 除法运算(注意:这里没有处理除数为0的情况) stack.push(num1 / num2); break; default: // 如果遇到非法的运算符,这里默认没有处理,实际中可能需要抛出异常 } } } // 最终栈中剩下的元素即为表达式的值 return stack.pop(); } // 判断一个token是否是数字 public boolean isNumber(String token) { // 如果token不是运算符,则认为是数字 return !("+".equals(token) || "-".equals(token) || "*".equals(token) || "/".equals(token)); }
}
性能最好
java">class Solution { // 声明一个全局索引变量,用于迭代访问tokens数组 public int index = 0; // evalRPN方法:根据逆波兰表达式(RPN)计算表达式的值 public int evalRPN(String[] tokens) { // 将index初始化为tokens数组的最后一个元素的索引 index = tokens.length - 1; // 调用iter方法进行递归计算 return iter(tokens); } // iter方法:递归计算逆波兰表达式的值 public int iter(String[] tokens) { // 从tokens数组中取出当前索引对应的元素,并将索引减一 String item = tokens[index--]; // 如果当前元素是运算符 if (item.equals("+") || item.equals("-") || item.equals("*") || item.equals("/")) { // 递归调用iter方法计算第二个操作数 int num2 = iter(tokens); // 递归调用iter方法计算第一个操作数(注意此时index已经指向了第一个操作数) int num1 = iter(tokens); // 根据运算符进行相应的计算 switch (item) { case "+": // 返回加法结果 return num1 + num2; case "-": // 返回减法结果 return num1 - num2; case "*": // 返回乘法结果 return num1 * num2; case "/": // 返回除法结果(注意:这里没有处理除数为0的情况) return num1 / num2; // 如果遇到非法的运算符(实际上这里不会进入,因为前面已经判断过了) } } // 如果当前元素是数字,则直接返回其整数值 return Integer.parseInt(item); } // isNumber方法:判断一个字符串是否是数字(注意:此方法在类中是静态的,但在当前解法中并未被使用) public static boolean isNumber(String s) { // 如果字符串不是运算符,则认为是数字 return !(s.equals("+") || s.equals("-") || s.equals("*") || s.equals("/")); }
}
224.【困难】基本计算器
题目描述
给你一个字符串表达式 s ,请你实现一个基本计算器来计算并返回它的值。
注意:不允许使用任何将字符串作为数学表达式计算的内置函数,比如 eval() 。
示例 1:
- 输入:s = “1 + 1”
- 输出:2
示例 2:
- 输入:s = “(1+(4+5+2)-3)+(6+8)”
- 输出:23
java">class Solution { // 私有变量index,用于记录当前处理的字符索引,初始化为-1 private int index = -1; // 私有变量chars,用于存储输入的字符串转化为字符数组后的数据 private char[] chars; // 私有变量len,用于记录输入字符串的长度 private int len; // 公开方法calculate,接收一个字符串s作为输入,并返回计算结果 public int calculate(String s) { // 将输入的字符串s转化为字符数组并赋值给chars chars = s.toCharArray(); // 获取字符数组的长度并赋值给len len = chars.length; // 调用深度优先搜索方法dfs进行计算,并返回结果 return dfs(); } // 私有方法dfs,用于执行深度优先搜索并计算结果 private int dfs() { // num用于存储最终的计算结果 int num = 0; // sign用于记录当前的符号,默认为正号1 int sign = 1; // cur用于构建多位数 int cur = 0; // 当索引index小于字符串长度且当前字符不是')'时,进行循环 while (++index < len && chars[index] != ')') { // 获取当前字符 char c = chars[index]; // 如果当前字符是'(',则递归调用dfs计算括号内的表达式 if (c == '(') { cur = dfs(); } // 如果当前字符是'+'或'-',则更新num和cur,并设置新的sign else if (c == '+' || c == '-') { // 将之前的计算结果和符号加到num中 num += sign * cur; // 重置cur为0,准备构建下一个数字 cur = 0; // 设置新的sign sign = c == '+' ? 1 : -1; } // 如果当前字符不是空格,则将其加入到cur中构建多位数 else if (c != ' ') { // 将字符c转化为数字并加到cur的末尾 cur = c - '0' + cur * 10; } } // 返回最终的计算结果,即将最后一个数字cur和最后一个符号sign加到num中 return num + sign * cur; }
}
2.【中等】两数相加
- 给你两个 非空 的链表,表示两个非负的整数。它们每位数字都是按照 逆序 的方式存储的,并且每个节点只能存储 一位 数字。
- 请你将两个数相加,并以相同形式返回一个表示和的链表。
- 你可以假设除了数字 0 之外,这两个数都不会以 0 开头。
示例 1:
- 输入:l1 = [2,4,3], l2 = [5,6,4]
- 输出:[7,0,8]
- 解释:342 + 465 = 807.
示例 2:
- 输入:l1 = [0], l2 = [0]
- 输出:[0]
示例 3:
- 输入:l1 = [9,9,9,9,9,9,9], l2 = [9,9,9,9]
- 输出:[8,9,9,9,0,0,0,1]
java">/** * Definition for singly-linked list. * public class ListNode { * int val; * ListNode next; * ListNode(int x) { val = x; } * } */
class Solution { // 定义一个方法,用于将两个链表表示的数字相加 public ListNode addTwoNumbers(ListNode l1, ListNode l2) { // 创建一个哑节点(dummy node),也叫作哨兵节点,用于简化边界情况的处理 ListNode pre = new ListNode(0); // cur 指针用于遍历新链表 ListNode cur = pre; // carry 变量用于记录进位 int carry = 0; // 当两个链表都不为空时,进行循环 while(l1 != null || l2 != null) { // 如果链表l1为空,则取值为0,否则取l1当前节点的值 int x = l1 == null ? 0 : l1.val; // 如果链表l2为空,则取值为0,否则取l2当前节点的值 int y = l2 == null ? 0 : l2.val; // 计算当前位的和(包括进位) int sum = x + y + carry; // 更新进位 carry = sum / 10; // 更新当前位的值(0-9) sum = sum % 10; // 在新链表的当前位置创建一个新节点,值为sum cur.next = new ListNode(sum); // 移动cur指针到下一个位置 cur = cur.next; // 如果链表l1不为空,则移动到下一个节点 if(l1 != null) l1 = l1.next; // 如果链表l2不为空,则移动到下一个节点 if(l2 != null) l2 = l2.next; } // 如果循环结束后还有进位,则在新链表的末尾添加一个节点,值为进位 if(carry == 1) { cur.next = new ListNode(carry); } // 返回新链表的头节点(即哑节点的下一个节点) return pre.next; }
}
✨ 这就是今天要分享给大家的全部内容了,我们下期再见!😊
🏠 我在CSDN等你哦!我的主页😍
–