Python技术迷

面完十五分钟就接 offer了,hr还打电话给我读了很长一段用心的面试评价,很幸运

十五分钟面完就接offer,HR还专门打电话念了一大段面试评价,这种剧情放招聘市场里,已经快算锦鲤附体了。

Image

你别说,这种反馈真挺稀有。现在多少面试,聊完像石头扔进水沟,连个响都没有。你这边刚起身,人家那边已经认真总结优点、顺手发录用,说明两件事:一是岗位确实急,二是你确实对味,HR估计一边打电话一边都怕你被别人抢走。

评论区肯定有人会说,“这公司是不是太缺人了”,也有人会酸一句“先别高兴太早”。这话也不算全错,入职前该问的还是得问,团队氛围、加班强度、试用期规则,一个都别漏。

但话说回来,能在一堆已读不回里碰上这种被认真对待的体验,真的会让人心里一松。找工作最烦的就是悬着,这种干脆劲儿,打工人谁不吃这一套。

算法题:Lisp 语法解析

(add 1 (mult 2 3)) 这种题,很多人第一反应是递归。真写起来,十有八九会在括号、空格、负数、作用域这几个地方翻车。

Lisp 语法解析这题,难点不在“会不会写 DFS”,而在你是不是把“表达式求值”和“变量作用域”分开想清楚了。let 最容易写歪:前面定义的变量,后面的表达式能用;同名变量还能覆盖上一层。这个地方我一般不太信一把梭的切字符串,后面很容易把自己绕进去。

先看个最小例子:

expr = "(let x 2 (mult x (let x 3 y 4 (add x y))))"

外层 x=2,内层又把 x 改成了 3,所以 (add x y) 算出来是 7,最后结果是 2 * 7 = 14。这题如果没有“环境栈”这个意识,基本写不稳。

我自己的处理习惯是两步:

第一步,先把当前表达式拆成 token,但这个拆不是直接 split(),因为括号里的子表达式不能拆散。 第二步,遇到 add、mult 就算两个子表达式,遇到 let 就按“成对赋值,最后一个是返回值”来处理。

代码不用写得花,稳最重要:

classSolution:
defevaluate(self, expression: str) -> int:
return self.eval_expr(expression, {})

defeval_expr(self, exp: str, env: dict) -> int:
if exp[0] != '(':
if exp[0] == '-'or exp[0].isdigit():
return int(exp)
return env[exp]

        inner = exp[1:-1]
        parts = self.parse(inner)

        op = parts[0]
if op == 'add':
return self.eval_expr(parts[1], env.copy()) + self.eval_expr(parts[2], env.copy())
if op == 'mult':
return self.eval_expr(parts[1], env.copy()) * self.eval_expr(parts[2], env.copy())

        scope = env.copy()
        i = 1
while i < len(parts) - 1:
if i == len(parts) - 2:
return self.eval_expr(parts[i], scope)
            name = parts[i]
            val = self.eval_expr(parts[i + 1], scope)
            scope[name] = val
            i += 2
return self.eval_expr(parts[-1], scope)

defparse(self, s: str):
        res = []
        bal = 0
        cur = []
for ch in s:
if ch == ' 'and bal == 0:
if cur:
                    res.append(''.join(cur))
                    cur = []
continue
if ch == '(':
                bal += 1
elif ch == ')':
                bal -= 1
            cur.append(ch)
if cur:
            res.append(''.join(cur))
return res

这里有两个细节挺关键。

一个是 parse() 里用 bal 记录括号层数。只有在 bal == 0 时,空格才是真分隔符。这个小地方不处理,(add 1 (mult 2 3)) 会被你切成一堆碎片。

另一个是 let 的求值顺序。比如:

"(let x 1 y (add x 2) (add x y))"

算 y 的时候,x 已经是 1 了,所以 y=3,最后结果是 4。这也是为什么我在 let 里不是先批量收集变量,再统一赋值,而是边算边塞进 scope。

这种题表面上是字符串题,实际上更像“解释器缩小版”。你要真把作用域、表达式拆分、递归返回值这三件事理顺了,代码不会很长,但会比较稳。反过来,上来就一顿 split,最后调半天,通常不是括号没对上,就是变量作用域串了。这个坑,我见得不少。