基本计算器 IV

标签: 递归 哈希表 数学 字符串

难度: Hard

给定一个表达式如 expression = "e + 8 - a + 5" 和一个求值映射,如 {"e": 1}(给定的形式为 evalvars = ["e"] 和 evalints = [1]),返回表示简化表达式的标记列表,例如 ["-1*a","14"]

  • 表达式交替使用块和符号,每个块和符号之间有一个空格。
  • 块要么是括号中的表达式,要么是变量,要么是非负整数。
  • 变量是一个由小写字母组成的字符串(不包括数字)。请注意,变量可以是多个字母,并注意变量从不具有像 "2x" 或 "-x" 这样的前导系数或一元运算符 。

表达式按通常顺序进行求值:先是括号,然后求乘法,再计算加法和减法。

  • 例如,expression = "1 + 2 * 3" 的答案是 ["7"]

输出格式如下:

  • 对于系数非零的每个自变量项,我们按字典排序的顺序将自变量写在一个项中。
    • 例如,我们永远不会写像 “b*a*c” 这样的项,只写 “a*b*c”
  • 项的次数等于被乘的自变量的数目,并计算重复项。我们先写出答案的最大次数项,用字典顺序打破关系,此时忽略词的前导系数。
    • 例如,"a*a*b*c" 的次数为 4。
  • 项的前导系数直接放在左边,用星号将它与变量分隔开(如果存在的话)。前导系数 1 仍然要打印出来。
  • 格式良好的一个示例答案是 ["-2*a*a*a", "3*a*a*b", "3*b*b", "4*a", "5*c", "-6"] 。
  • 系数为 0 的项(包括常数项)不包括在内。
    • 例如,“0” 的表达式输出为 [] 。

注意:你可以假设给定的表达式均有效。所有中间结果都在区间 [-231, 231 - 1] 内。

示例 1:

输入:expression = "e + 8 - a + 5", evalvars = ["e"], evalints = [1]
输出:["-1*a","14"]

示例 2:

输入:expression = "e - 8 + temperature - pressure",
evalvars = ["e", "temperature"], evalints = [1, 12]
输出:["-1*pressure","5"]

示例 3:

输入:expression = "(e + 8) * (e - 8)", evalvars = [], evalints = []
输出:["1*e*e","-64"]

提示:

  • 1 <= expression.length <= 250
  • expression 由小写英文字母,数字 '+''-''*''('')'' ' 组成
  • expression 不包含任何前空格或后空格
  • expression 中的所有符号都用一个空格隔开
  • 0 <= evalvars.length <= 100
  • 1 <= evalvars[i].length <= 20
  • evalvars[i] 由小写英文字母组成
  • evalints.length == evalvars.length
  • -100 <= evalints[i] <= 100

Submission

运行时间: 34 ms

内存: 16.4 MB

class Poly(collections.Counter):
    def __add__(self, other):
        self.update(other)
        return self

    def __sub__(self, other):
        self.update({k: -v for k, v in other.items()})
        return self

    def __mul__(self, other):
        ans = Poly()
        for k1, v1 in self.items():
            for k2, v2 in other.items():
                ans.update({tuple(sorted(k1 + k2)): v1 * v2})
        return ans

    def evaluate(self, evalmap):
        ans = Poly()
        for k, c in self.items():
            free = []
            for token in k:
                if token in evalmap:
                    c *= evalmap[token]
                else:
                    free.append(token)
            ans[tuple(free)] += c
        return ans

    def to_list(self):
        p = sorted(self.items(), key=lambda kv: (-len(kv[0]), kv[0], kv[1]))
        return ['*'.join((str(v),) + k) for k, v in p if v]

class Solution(object):
    def basicCalculatorIV(self, expression, evalvars, evalints):
        evalmap = dict(zip(evalvars, evalints))

        def combine(left, right, symbol):
            if symbol == '+': return left + right
            if symbol == '-': return left - right
            if symbol == '*': return left * right
            raise

        def make(expr):
            ans = Poly()
            if expr.isdigit():
                ans.update({(): int(expr)})
            else:
                ans[(expr,)] += 1
            return ans

        def parse(expr):
            bucket = []
            symbols = []
            i = 0
            while i < len(expr):
                if expr[i] == '(':
                    bal = 0
                    for j in range(i, len(expr)):
                        if expr[j] == '(': bal += 1
                        if expr[j] == ')': bal -= 1
                        if bal == 0: break
                    bucket.append(parse(expr[i+1:j]))
                    i = j
                elif expr[i].isalnum():
                    for j in range(i, len(expr)):
                        if expr[j] == ' ':
                            bucket.append(make(expr[i:j]))
                            break
                    else:
                        bucket.append(make(expr[i:]))
                    i = j
                elif expr[i] in '+-*':
                    symbols.append(expr[i])
                i += 1

            for i in range(len(symbols) - 1, -1, -1):
                if symbols[i] == '*':
                    bucket[i] = combine(bucket[i], bucket.pop(i+1),
                                        symbols.pop(i))

            if not bucket: return Poly()
            ans = bucket[0]
            for i, symbol in enumerate(symbols, 1):
                ans = combine(ans, bucket[i], symbol)

            return ans

        P = parse(expression).evaluate(evalmap)
        return P.to_list()

Explain

题解使用了一种多项式处理方法,通过自定义的Poly类(基于collections.Counter),来表示和操作多项式。每个Poly实例表示一个多项式,其中键是变量组合的元组,值是相应的系数。 1. __add__ 和 __sub__ 方法重载了加法和减法运算符来合并和减去多项式。 2. __mul__ 方法通过嵌套循环计算两个多项式的乘积,组合项的所有可能的变量组合,并更新结果多项式。 3. evaluate 方法通过给定的变量映射替换变量值,并合并多项式中相同的项。 4. to_list 方法用于将多项式转换为规定格式的字符串列表,按照项的次数(降序)、变量的字典序排列。 解析表达式的过程(parse)使用递归来处理括号和操作符,生成相应的多项式对象。该方法首先分离出各个组成部分(数字、变量或括号内的表达式),并根据运算符适当组合它们。

时间复杂度: O(n + m^2),其中n是表达式的长度,m是操作数中项的数量

空间复杂度: O(m^2)

class Poly(collections.Counter):
    # 初始化多项式,继承Counter以方便统计和操作
    def __add__(self, other):
        # 加法操作,更新当前多项式的每个项
        self.update(other)
        return self

    def __sub__(self, other):
        # 减法操作,更新当前多项式,为other的每个项系数取反
        self.update({k: -v for k, v in other.items()})
        return self

    def __mul__(self, other):
        # 乘法操作,计算当前多项式与另一多项式的所有项的乘积
        ans = Poly()
        for k1, v1 in self.items():
            for k2, v2 in other.items():
                ans.update({tuple(sorted(k1 + k2)): v1 * v2})
        return ans

    def evaluate(self, evalmap):
        # 计算多项式的值,替换变量为具体数值
        ans = Poly()
        for k, c in self.items():
            free = []
            for token in k:
                if token in evalmap:
                    c *= evalmap[token]
                else:
                    free.append(token)
            ans[tuple(free)] += c
        return ans

    def to_list(self):
        # 转换多项式为规定格式的字符串列表
        p = sorted(self.items(), key=lambda kv: (-len(kv[0]), kv[0], kv[1]))
        return ['*'.join((str(v),) + k) for k, v in p if v]

class Solution(object):
    def basicCalculatorIV(self, expression, evalvars, evalints):
        evalmap = dict(zip(evalvars, evalints))

        def combine(left, right, symbol):
            # 根据符号执行相应的多项式运算
            if symbol == '+': return left + right
            if symbol == '-': return left - right
            if symbol == '*': return left * right
            raise

        def make(expr):
            # 生成多项式对象,将表达式转换为多项式
            ans = Poly()
            if expr.isdigit():
                ans.update({(): int(expr)})
            else:
                ans[(expr,)] += 1
            return ans

        def parse(expr):
            # 解析表达式,处理括号和操作符,递归构造多项式
            bucket = []
            symbols = []
            i = 0
            while i < len(expr):
                if expr[i] == '(': 
                    bal = 0
                    for j in range(i, len(expr)):
                        if expr[j] == '(': bal += 1
                        if expr[j] == ')': bal -= 1
                        if bal == 0: break
                    bucket.append(parse(expr[i+1:j]))
                    i = j
                elif expr[i].isalnum():
                    for j in range(i, len(expr)):
                        if expr[j] == ' ':
                            bucket.append(make(expr[i:j]))
                            break
                    else:
                        bucket.append(make(expr[i:]))
                    i = j
                elif expr[i] in '+-*':
                    symbols.append(expr[i])
                i += 1

            for i in range(len(symbols) - 1, -1, -1):
                if symbols[i] == '*':
                    bucket[i] = combine(bucket[i], bucket.pop(i+1),
                                        symbols.pop(i))

            if not bucket: return Poly()
            ans = bucket[0]
            for i, symbol in enumerate(symbols, 1):
                ans = combine(ans, bucket[i], symbol)

            return ans

        P = parse(expression).evaluate(evalmap)
        return P.to_list()

Explore

在多项式乘法中,使用`tuple(sorted(k1 + k2))`来表示项的组合确保不同顺序的变量组合被视为同一项。例如,'x*y'和'y*x'应被视为同一项。通过排序,我们统一了项的表示方式,从而能正确地合并同类项。这种方法确实引入了额外的计算成本,主要是因为每次乘法都需要对变量组合进行排序。排序操作的时间复杂度为O(n log n),其中n是变量的数量。尽管引入了额外成本,但这是保证多项式正确性的必要步骤。

在`evaluate`函数中,每个多项式项的变量被替换为具体数值(如果在`evalmap`中定义)。若变量未在映射中定义,则保留为自由变量。计算过程中,相同的自由变量组合会自动合并,因为`Poly`类继承自`collections.Counter`,这保证了相同键的项(即自由变量组合)的系数会被累加。这种方法避免了重复计算和遗漏项,因为每个变量组合都被唯一确定,并且Counter类的特性自动处理了系数的累加,确保计算的正确性。

递归方法在处理复杂或深层嵌套的表达式时可能效率较低,主要是因为每层递归都需要进行函数调用,这会消耗额外的时间和空间。深层嵌套的表达式可能导致递归调用的层数过多,从而存在栈溢出的风险。虽然现代编程语言和环境通常提供了较大的栈空间,但在极端情况下仍可能遇到栈溢出问题。为了避免这种情况,可以考虑使用迭代方法或将递归转化为迭代,这可能需要额外的数据结构来显式维护调用栈。