【LeetCode 随笔】面试经典 150 题【中等+困难】持续更新中。。。

在这里插入图片描述

🌈你好呀!我是 山顶风景独好
💕欢迎来到我的博客,很高兴能够在这里和您见面!
💕希望您在这里可以感受到一份轻松愉快的氛围!
💕这里不仅可以获得有趣的内容和知识,也可以畅所欲言、分享您的想法和见解。
🚀 欢迎一起踏上探险之旅,挖掘无限可能,共同成长!

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 中的每个字符串并进行上述操作即可。在所有的操作完成后,我们将从栈底到栈顶的字符串用 /进行连接,再在最前面加上 /表示根目录,就可以得到简化后的规范路径。

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() 获取堆栈中的最小元素。
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

解题思路
逆波兰表达式严格遵循「从左到右」的运算。计算逆波兰表达式的值时,使用一个栈存储操作数,从左到右遍历逆波兰表达式,进行如下操作:

  • 如果遇到操作数,则将操作数入栈;
  • 如果遇到运算符,则将两个操作数出栈,其中先出栈的是右操作数,后出栈的是左操作数,使用运算符对两个操作数进行运算,将运算得到的新操作数入栈。

整个逆波兰表达式遍历完毕之后,栈内只有一个元素,该元素即为逆波兰表达式的值。

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));  
    }  
}

性能最好

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
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]
/**  
 * 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等你哦!我的主页😍

在这里插入图片描述

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值