每日一刷经验分享:力扣736困难. Lisp 语法解析

125 阅读4分钟

736困难. Lisp 语法解析

题意

给你一个类似 Lisp 语句的字符串表达式 expression,求出其计算结果。

表达式语法如下所示:

表达式可以为整数,let 表达式,add 表达式,mult 表达式,或赋值的变量。表达式的结果总是一个整数。 (整数可以是正整数、负整数、0) let 表达式采用 "(let v1 e1 v2 e2 ... vn en expr)" 的形式,其中 let 总是以字符串 "let"来表示,接下来会跟随一对或多对交替的变量和表达式,也就是说,第一个变量 v1被分配为表达式 e1 的值,第二个变量 v2 被分配为表达式 e2 的值,依次类推;最终 let 表达式的值为 expr表达式的值。 add 表达式表示为 "(add e1 e2)" ,其中 add 总是以字符串 "add" 来表示,该表达式总是包含两个表达式 e1、e2 ,最终结果是 e1 表达式的值与 e2 表达式的值之 和 。 mult 表达式表示为 "(mult e1 e2)" ,其中 mult 总是以字符串 "mult" 表示,该表达式总是包含两个表达式 e1、e2,最终结果是 e1 表达式的值与 e2 表达式的值之 积 。 在该题目中,变量名以小写字符开始,之后跟随 0 个或多个小写字符或数字。为了方便,"add" ,"let" ,"mult" 会被定义为 "关键字" ,不会用作变量名。 最后,要说一下作用域的概念。计算变量名所对应的表达式时,在计算上下文中,首先检查最内层作用域(按括号计),然后按顺序依次检查外部作用域。测试用例中每一个表达式都是合法的。有关作用域的更多详细信息,请参阅示例。   示例 1:

输入:expression = "(let x 2 (mult x (let x 3 y 4 (add x y))))" 输出:14 解释: 计算表达式 (add x y), 在检查变量 x 值时, 在变量的上下文中由最内层作用域依次向外检查。 首先找到 x = 3, 所以此处的 x 值是 3 。 示例 2:

输入:expression = "(let x 3 x 2 x)" 输出:2 解释:let 语句中的赋值运算按顺序处理即可。 示例 3:

输入:expression = "(let x 1 y 2 x (add x y) (add x y))" 输出:5 解释: 第一个 (add x y) 计算结果是 3,并且将此值赋给了 x 。 第二个 (add x y) 计算结果是 3 + 2 = 5 。   提示:

1 <= expression.length <= 2000 exprssion 中不含前导和尾随空格 expressoin 中的不同部分(token)之间用单个空格进行分隔 答案和所有中间计算结果都符合 32-bit 整数范围 测试用例中的表达式均为合法的且最终结果为整数

AC代码

class Solution {
    public int evaluate(String expression) {
        return calculate(new HashMap<>(),expression);        
    }
    int solve(Map<String,Integer> map,String s){
        //计算在外层变量赋值为map的情况下,求s的表达式值,本方法计算的都是开头有let的式子
        //先把映射复制一份:
        Map<String,Integer> newMap=new HashMap<>();
        for(String t:map.keySet()){newMap.put(t,map.get(t));}
        //先把结果的表达式摘出来:
        int r=nextSpace(s,s.length()-1,-1); 
        String ans=s.substring(r+1,s.length()-1);//存储结果的表达式      
        //此时的r应该是赋值操作后的那个空格的位置,也即是赋值表达式终止的位置
        //下边处理赋值的若干表达式:
        for(int i=4;i<r;){
            int j=i;
            i=nextSpace(s,i,1);
            String a=s.substring(j+1,i);
            j=i;
            i=nextSpace(s,i,1);
            String b=s.substring(j+1,i);
            newMap.put(a,calculate(newMap,b));
        }
        return calculate(newMap,ans);
    }
    int calculate(Map<String,Integer> map,String s){
        if(s.charAt(0)!='('){
            if(map.containsKey(s)){return map.get(s);}
            return Integer.parseInt(s);
        }
        char c=s.charAt(1);
        if(c=='l'){return solve(map,s);}
        //add:加,mult:乘,,首先开头有括号括着的肯定是这两种运算
        int i=s.indexOf(' ');//定位在要处理的两个变量之前的空格;
        int j=i;
        i=nextSpace(s,i,1);      
        int a=calculate(map,s.substring(j+1,i)),b=calculate(map,s.substring(i+1,s.length()-1));
        return c=='a'?a+b:a*b;
    }
    int nextSpace(String s,int k,int plus){
        //在此时k定位某式子之前的时候,找到跳过下边完整表达式的下一个空格的位置
        k+=plus;
        char c=s.charAt(k);
        if(c=='('||c==')'){
            //第一个是表达式
            int d=0;
            for(;;k+=plus){
                c=s.charAt(k);
                if(c=='('){d+=plus;}
                else if(c==')'){d-=plus;}
                if(d==0){
                    k+=plus;
                    break;
                }
            }
        }
        else{while(s.charAt(k)!=' '){k+=plus;}}
        return k;
    }
}

分析

无脑办法,此方法的复杂度可以到O(n^2),但是很好理解,。

  1. 首先表达式的可能形式为let+赋值+最后要求的表达式,也有可能没有let赋值这一步,但是最后一定有一个结果表达式;
  2. 对于开头没有let的字符串,特殊处理一下直接输出答案即可,踩坑示例:"(add 1 2)"
  3. 对于常规的字符串,也就是开头存在let,那么需要首先从后往前先截出结果表达式,之后在let后,以及最后的表达式之前,两个完整的表达式一组,形成映射关系,存入本层map;
  4. 为什么我在上一条中说到map是“本层的”呢?因为在本题中可能遇到表达式嵌套的情况,某变量,假如在外层赋予了一个值,在内层又赋予了一个值,那么结果就是,在内层计算的时候,用的是内层的赋值,跳回到外层后,赋值有变了回来,特别的,内层没赋值的变量,用的值直接就是外层的,踩坑样例:"(let x 2 (mult x (let x 3 y 4 (add x y))))""(let var 78 b 77 (let c 33 (add c (mult var 66))))"

总结

说实话这种题就是考你的字符串处理能力,具体的题意也要具体分析,因为每种的处理方法不一样。像是kmp这种方法最好还是学一下,特别是next数组的精髓,技多不压身。

image.png 我正在参与掘金技术社区创作者签约计划招募活动,点击链接报名投稿