校招算法笔面试 | 表达式求值

题目## 题目## 题目

题目链接

题目的主要信息:
  • 写一个支持+ - *三种符号的运算器,其中优先级+ - 是一级,*更高一级
  • 支持括号运算
举一反三:

BM44. 有效括号序列

方法:栈 + 递归(推荐使用)

知识点:栈

栈是一种仅支持在表尾进行插入和删除操作的线性表,这一端被称为栈顶,另一端被称为栈底。元素入栈指的是把新元素放到栈顶元素的上面,使之成为新的栈顶元素;元素出栈指的是从一个栈删除元素又称作出栈或退栈,它是把栈顶元素删除掉,使其相邻的元素成为新的栈顶元素。

思路:

对于上述两个要求,我们要考虑的是两点,一是处理运算优先级的问题,二是处理括号的问题。

处理优先级问题,那必定是乘号有着优先运算的权利,加号减号先一边看,我们甚至可以把减号看成加一个数的相反数,则这里只有乘法和加法,那我们优先处理乘法,遇到乘法,把前一个数和后一个数乘起来,遇到加法就把这些数字都暂时存起来,最后乘法处理完了,就剩余加法,把之前存起来的数字都相加就好了。

处理括号的问题,我们可以将括号中的部分看成一个新的表达式,即一个子问题,因此可以将新的表达式递归地求解,得到一个数字,再运算:

  • 终止条件: 每次遇到左括号意味着进入括号子问题进行计算,那么遇到右括号代表这个递归结束。
  • 返回值: 将括号内部的计算结果值返回。
  • 本级任务: 遍历括号里面的字符,进行计算。

具体做法:

  • step 1:使用栈辅助处理优先级,默认符号为加号。
  • step 2:遍历字符串,遇到数字,则将连续的数字字符部分转化为int型数字。
  • step 3:遇到左括号,则将括号后的部分送入递归,处理子问题;遇到右括号代表已经到了这个子问题的结尾,结束继续遍历字符串,将子问题的加法部分相加为一个数字,返回。
  • step 4:当遇到符号的时候如果是+,得到的数字正常入栈,如果是-,则将其相反数入栈,如果是*,则将栈中内容弹出与后一个元素相乘再入栈。
  • step 5:最后将栈中剩余的所有元素,进行一次全部相加。

图示:

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

Java实现代码:

import java.util.*;
public class Solution {
    public ArrayList<Integer> function(String s, int index){
        Stack<Integer> stack = new Stack<Integer>(); 
        int num = 0;
        char op = '+';
        int i;
        for(i = index; i < s.length(); i++){
            //数字转换成int数字
            //判断是否为数字
            if(s.charAt(i) >= '0' && s.charAt(i) <= '9'){ 
                num = num * 10 + s.charAt(i) - '0';
                if(i != s.length() - 1)
                    continue;
            }
            //碰到'('时,把整个括号内的当成一个数字处理
            if(s.charAt(i) == '('){
                //递归处理括号
                ArrayList<Integer> res = function(s, i + 1);
                num = res.get(0);
                i = res.get(1);
                if(i != s.length() - 1)
                    continue;
            }            
            switch(op){
            //加减号先入栈
            case '+': 
                stack.push(num);
                break;
            case '-':
                //相反数
                stack.push(-num);
                break;
            //优先计算乘号
            case '*':  
                int temp = stack.pop();
                stack.push(temp * num);
                break;
            }
            num = 0;
            //右括号结束递归
            if(s.charAt(i) == ')') 
                break; 
            else 
                op = s.charAt(i);
        }
        int sum = 0;
        //栈中元素相加
        while(!stack.isEmpty())  
            sum += stack.pop();
        ArrayList<Integer> temp = new ArrayList<Integer>();
        temp.add(sum);
        temp.add(i);
        return temp; 
    }
    public int solve (String s) {
        ArrayList<Integer> res = function(s, 0);
        return res.get(0);
    }
}

C++实现代码:

class Solution {
public:
    vector<int> function(string s, int index){
        stack<int> stack; 
        int num = 0;
        char op = '+';
        int i;
        for(i = index; i < s.length(); i++){
            //数字转换成int数字
            if(isdigit(s[i])){
                num = num * 10 + s[i] - '0';
                if(i != s.length() - 1)
                    continue;
            }
            //碰到'('时,把整个括号内的当成一个数字处理
            if(s[i] == '('){
                //递归处理括号
                vector<int> res = function(s, i + 1);
                num = res[0];
                i = res[1];
                if(i != s.length() - 1)
                    continue;
            }           
            switch(op){
            //加减号先入栈
            case '+': 
                stack.push(num);
                break;
            case '-':
                //相反数
                stack.push(-num);
                break;
            //优先计算乘号
            case '*':  
                int temp = stack.top();
                stack.pop();
                stack.push(temp * num);
                break;
            }
            num = 0;
            //右括号结束递归
            if(s[i] == ')')
                break; 
            else 
                op = s[i];
        }
        int sum = 0;
        //栈中元素相加
        while(!stack.empty()){  
            sum += stack.top();
            stack.pop();
        }
        return vector<int> {sum, i}; 
    }
    
    int solve(string s) {
        return function(s, 0)[0];
    }
};

Python代码实现:

class Solution:
    def solve(self , s ):
        s = s.strip()
        stack = []
        res = 0
        num = 0
        sign = '+' 
        index = 0
        while index < len(s):
            if s[index] == ' ':
                index += 1
                continue
            # 遇到左括号
            if s[index] == '(': 
                end = index + 1
                lens = 1
                while lens > 0: 
                    if s[end] == '(':
                        lens += 1
                    if s[end] == ')':
                        lens -= 1
                    end += 1
                #将括号视为子问题进入递归
                num = self.solve(s[index + 1: end - 1]) 
                index = end - 1
                continue
            #字符数字转换成int数字
            if '0' <= s[index] <= '9':
                num = num * 10 + int(s[index])
            #根据符号运算
            if not '0' <= s[index] <= '9' or index == len(s) - 1:
                #加
                if sign == '+': 
                    stack.append(num)
                #减,加相反数
                elif sign == '-': 
                    stack.append(-1 * num)
                #乘优先计算
                elif sign == '*': 
                    stack.append(stack.pop() * num) 
                num = 0
                sign = s[index]
            index += 1
        #栈中元素相加
        while stack: 
            res += stack.pop()
        return res

复杂度分析:

  • 时间复杂度: O ( n ) O(n) O(n) n n n为字符串长度,相当于遍历一遍字符串全部元素
  • 空间复杂度: O ( n ) O(n) O(n),辅助栈和递归栈的空间

题目链接

题目的主要信息:
  • 实现一个函数判断字符串是否表示数字
  • 包括科学记数法的数字:一个整数或小数,后接一个可选的大小写字母e,后接一个可正可负的整数
  • 小数,可选的正负号,小数点前后整数任意有一个即可
  • 整数,可选的正负号,加上后面的整数
  • 字符串可能包含前导、后导空格
举一反三:

学习完本题的思路你可以解决如下题目:

JZ67. 把字符串转换成整数(atoi)

方法一:遍历(推荐使用)

思路:

既然是字符串,那我们使用一个遍历字符串的全局变量作为下标,然后遍历字符串依次检查每个字符,根据字符属于的类型进行判断。

具体做法:

  • step 1:先判断空串的情况。
  • step 2:遍历字符前面的空格,将下标移到第一个不是空格的位置。遍历字符串后面的空格,将长度限制在最后一个空格。若是长度小于下标,说明全是空格。
  • step 3:剩余部分判断,开始找数字,判断是不是一个有符号的整数,优先判断符号,直到遇到非数字停止。
  • step 4:如果有小数点,那么开始判断小数点后是不是一个无符号的整数,也是遍历直到遇到非数字为止,出现小数点的话,小数点前和小数点后的数字任意有一即可。
  • step 5:若是出现字母e或者E,那么需要判断后面是不是一个有符号的整数,,也是遍历直到遇到非数字为止,e前后都要数字。
  • step 6:最后检查下标是不是遍历到了刚刚限制的长度,若是没有,说明后面还有非空格,不符合要求。

图示:

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

Java实现代码:

import java.util.*;
public class Solution {
    //遍历字符串的下标
    private int index = 0;
    //有符号判断
    private boolean integer(String s){
        if(index < s.length() && (s.charAt(index) == '-' || s.charAt(index) == '+'))
            index++;
        return unsigned_integer(s);
    }
    //无符号数判断
    private boolean unsigned_integer(String s){
        int temp = index;
        while(index < s.length() && (s.charAt(index) >= '0' && s.charAt(index) <= '9'))
            index++;
        return index > temp;
    }
    public boolean isNumeric (String str) {
        //先判断空串
        if(str == null || str.length() == 0)
            return false;
        //去除前面的空格
        while(index < str.length() && str.charAt(index) == ' ')
            index++;
        int n = str.length() - 1;
        //去除字符串后面的空格
        while(n >= 0 && str.charAt(n) == ' ')
            n--;
        //限制的长度比下标1
        n++;
        //全是空格情况
        if(n < index)
            return false;
        //判断前面的字符是否是有符号的整数
        boolean flag = integer(str);
        //如果有小数点
        if(index < n && str.charAt(index) == '.'){
            index++;
            //小数点前后有无数字可选
            flag = unsigned_integer(str) || flag; 
        }
        //如果有e
        if(index < n && (str.charAt(index) == 'e' || str.charAt(index) == 'E')){
            index++;
            //e后面必须全是整数
            flag = flag && integer(str);
        }
        //是否字符串遍历结束
        return flag && (index == n);
    }
}

C++实现代码:

class Solution {
public:
    //遍历字符串的下标
    int index = 0;
    //有符号判断
    bool integer(string& s){
        if(index < s.length() && (s[index] == '-' || s[index] == '+'))
            index++;
        return unsigned_integer(s);
    }
    //无符号数判断
    bool unsigned_integer(string& s){
        int temp = index;
        while(index < s.length() && (s[index] >= '0' && s[index] <= '9'))
            index++;
        return index > temp;
    }
    bool isNumeric(string str) {
        //先判断空串
        if(str.length() == 0)
            return false;
        //去除前面的空格
        while(index < str.length() && str[index] == ' ')
            index++;
        int n = str.length() - 1;
        //去除字符串后面的空格
        while(n >= 0 && str[n] == ' ')
            n--;
        //限制的长度比下标1
        n++;
        //全是空格情况
        if(n < index)
            return false;
        //判断前面的字符是否是有符号的整数
        bool flag = integer(str);
        //如果有小数点
        if(index < n && str[index] == '.'){
            index++;
            //小数点前后有无数字可选
            flag = unsigned_integer(str) || flag; 
        }
        //如果有e
        if(index < n && (str[index] == 'e' || str[index] == 'E')){
            index++;
            //e后面必须全是整数
            flag = flag && integer(str);
        }
        //是否字符串遍历结束
        return flag && (index == n);
    }
};

Python实现代码:

class Solution:
    def __init__(self):
        #遍历字符串的下标
        self.index = 0
    #有符号判断
    def integer(self, s: str) -> bool:
        if self.index < len(s) and (s[self.index] == '-' or s[self.index] == '+'):
            self.index += 1
        return self.unsigned_integer(s)
    #无符号数判断
    def unsigned_integer(self, s: str) -> bool:
        temp = self.index
        while self.index < len(s) and (s[self.index] >= '0' and s[self.index] <= '9'):
            self.index += 1
        return self.index > temp
    
    def isNumeric(self , str: str) -> bool:
        #先判断空串
        if len(str) == 0:
            return False
        #去除前面的空格
        while self.index < len(str) and str[self.index] == ' ':
            self.index += 1
        n = len(str) - 1
        #去除字符串后面的空格
        while n >= 0 and str[n] == ' ':
            n -= 1
        #限制的长度比下标1
        n += 1
        #全是空格情况
        if n < self.index:
            return False
        #判断前面的字符是否是有符号的整数
        flag = self.integer(str)
        #如果有小数点
        if self.index < n and str[self.index] == '.':
            self.index += 1
            #小数点前后有无数字可选
            flag = self.unsigned_integer(str) or flag
        #如果有e
        if self.index < n and (str[self.index] == 'e' or str[self.index] == 'E'):
            self.index += 1
            #e后面必须全是整数
            flag = flag and self.integer(str)
        #是否字符串遍历结束
        return flag and (self.index == n)

复杂度分析:

  • 时间复杂度: O ( n ) O(n) O(n),其中 n n n为字符串长度,最坏遍历字符串每个字符一次
  • 空间复杂度: O ( 1 ) O(1) O(1),常数级变量,无额外辅助空间
方法二:正则表达式(扩展思路)

思路:

可以使用正则表示匹配直接匹配字符串是否表示数字。其中C++和Java中使用双斜杠,Python使用单斜杠。

具体做法:

  • step 1:用\s表示匹配空格,*表示匹配0个或多个,写在正则表达式首尾。
  • step 2:正负号可选,因此使用?匹配正负号。
  • step 3:整数用\d匹配,小数点前有无数字可以用|表示:小数点前有数字,匹配小数点,后面的数字可有可无;小数前没有数字,匹配小数点后必须有数字。
  • step 4:匹配大小写字母[Ee],以及后面可有可无的正负号,后面必须匹配一个整数。

Java实现代码:

import java.util.regex.Pattern;
public class Solution {

    public boolean isNumeric (String str) {
       //正则表达式匹配
        String pattern = "(\\s)*[+-]?((\\d+(\\.(\\d+)?)?)|(\\.\\d+))([Ee][+-]?\\d+)?(\\s)*";
        //根据匹配值返回
        return Pattern.matches(pattern, str);
    }
}

C++实现代码:

#include<regex>
class Solution {
public:
    bool isNumeric(string str) {
        //正则表达式匹配
        string pattern = "(\\s)*[+-]?((\\d+(\\.(\\d+)?)?)|(\\.\\d+))([Ee][+-]?\\d+)?(\\s)*";
        regex re(pattern);
        //根据匹配值返回
        return regex_match(str, re);
    }
};

Python实现代码:

import re
class Solution:
    def isNumeric(self , str: str) -> bool:
        #正则表达式匹配
        pattern = "^(\s)*[+-]?((\d+(\.(\d+)?)?)|(\.\d+))([Ee][+-]?\d+)?(\s)*$"
        #根据匹配值返回
        if re.match(pattern, str):
            return True
        else:
            return False

复杂度分析:

  • 时间复杂度: O ( n ) O(n) O(n),其中 n n n为字符串长度,正则表达式最坏遍历每个字符一次
  • 空间复杂度: O ( 1 ) O(1) O(1),常数级空间

题目链接

题目主要信息:
  • 题目给定一个 n ∗ m n*m nm的矩阵,需要将其螺旋输出
举一反三:

学习完本题的思路你可以解决类似的矩阵遍历的问题。

方法:边界模拟法(推荐使用)

思路:

这道题就是一个简单的模拟,我们想象有一个矩阵,从第一个元素开始,往右到底后再往下到底后再往左到底后再往上,结束这一圈,进入下一圈螺旋。

具体做法:

  • step 1:首先排除特殊情况,即矩阵为空的情况。
  • step 2:设置矩阵的四个边界值,开始准备螺旋遍历矩阵,遍历的截止点是左右边界或者上下边界重合。
  • step 3:首先对最上面一排从左到右进行遍历输出,到达最右边后第一排就输出完了,上边界相应就往下一行,要判断上下边界是否相遇相交。
  • step 4:然后输出到了右边,正好就对最右边一列从上到下输出,到底后最右边一列已经输出完了,右边界就相应往左一列,要判断左右边界是否相遇相交。
  • step 5:然后对最下面一排从右到左进行遍历输出,到达最左边后最下一排就输出完了,下边界相应就往上一行,要判断上下边界是否相遇相交。
  • step 6:然后输出到了左边,正好就对最左边一列从下到上输出,到顶后最左边一列已经输出完了,左边界就相应往右一列,要判断左右边界是否相遇相交。
  • step 7:重复上述3-6步骤直到循环结束。

图示:
外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

Java代码实现:

import java.util.ArrayList;

public class Solution {
    public ArrayList<Integer> spiralOrder(int[][] matrix) {
        ArrayList<Integer> res = new ArrayList<>();
         //先排除特殊情况
        if(matrix.length == 0) {
            return res;
        }
        //左边界
        int left = 0; 
        //右边界
        int right = matrix[0].length - 1; 
        //上边界
        int up = 0; 
        //下边界
        int down = matrix.length - 1; 
        //直到边界重合
        while(left <= right && up <= down){ 
            //上边界的从左到右
            for(int i = left; i <= right; i++) 
                res.add(matrix[up][i]); 
            //上边界向下
            up++; 
            if(up > down)
                break;
            //右边界的从上到下
            for(int i = up; i <= down; i++) 
                res.add(matrix[i][right]);
            //右边界向左
            right--; 
            if(left > right)
                break;
            //下边界的从右到左
            for(int i = right; i >= left; i--) 
                res.add(matrix[down][i]);
            //下边界向上
            down--; 
            if(up > down)
                break; 
            //左边界的从下到上
            for(int i = down; i >= up; i--) 
                res.add(matrix[i][left]);
            //左边界向右
            left++; 
            if(left > right)
                break;
        }
        return res;
    }
}

C++代码实现

class Solution {
public:
    vector<int> spiralOrder(vector<vector<int> > &matrix) {
        vector<int> res;
        int n = matrix.size();
        //先排除特殊情况
        if(n == 0) 
            return res;
        //左边界
        int left = 0; 
        //右边界
        int right = matrix[0].size() - 1; 
        //上边界
        int up = 0; 
        //下边界
        int down = n - 1; 
        //直到边界重合
        while(left <= right && up <= down){ 
            //上边界的从左到右
            for(int i = left; i <= right; i++) 
                res.push_back(matrix[up][i]); 
            //上边界向下
            up++; 
            if(up > down)
                break;
            //右边界的从上到下
            for(int i = up; i <= down; i++) 
                res.push_back(matrix[i][right]);
            //右边界向左
            right--; 
            if(left > right)
                break;
            //下边界的从右到左
            for(int i = right; i >= left; i--) 
                res.push_back(matrix[down][i]);
            //下边界向上
            down--; 
            if(up > down)
                break; 
            //左边界的从下到上
            for(int i = down; i >= up; i--) 
                res.push_back(matrix[i][left]);
            //左边界向右
            left++; 
            if(left > right)
                break;
        }
        return res;
    }
};

Python实现代码:

class Solution:
    def spiralOrder(self , matrix: List[List[int]]) -> List[int]:
        res = list()
        n = len(matrix)
        #先排除特殊情况
        if n == 0: 
            return res
        #左边界
        left = 0 
        #右边界
        right = len(matrix[0]) - 1 
        #上边界
        up = 0 
        #下边界
        down = n - 1 
        #直到边界重合
        while left <= right and up <= down: 
            #上边界的从左到右
            for i in range(left, right+1): 
                res.append(matrix[up][i])
            #上边界向下
            up += 1 
            if up > down:
                break
            #右边界的从上到下
            for i in range(up,down+1): 
                res.append(matrix[i][right])
            #右边界向左
            right -= 1 
            if left > right:
                break
            i = right
            #下边界的从右到左
            while i >= left: 
                res.append(matrix[down][i])
                i -= 1
            #下边界向上
            down -= 1 
            if up > down:
                break
            i = down
            #左边界的从下到上
            while i >= up: 
                res.append(matrix[i][left])
                i -= 1
            #左边界向右
            left += 1 
            if left > right:
                break
        return res

复杂度分析:

  • 时间复杂度: O ( m n ) O(mn) O(mn),相当于遍历整个矩阵
  • 空间复杂度: O ( 1 ) O(1) O(1),res属于必要空间,没有使用额外辅助空间
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值