LeetCode 282 Expression Add Operators 题解

题意简述:给定一个数字串和一个目标值,允许在数字串中任意两个相邻数字之间插入+、-和*来组成表达式,求出所有这样的表达式,表达式求出的值恰好等于目标值。
输入:一个只由数字组成的字符串num,一个整数target。
输出:所有计算值为目标值的表达式。
示例:输入为(”105”,5),输出为[“1*0+5”,”10-5”]


题解:
这应该是条搜索题,搜索所有可能的表达式并分别计算结果,储存所有结果等于目标值的表达式。
要怎么搜索呢?首先想到的是按运算符来搜索,实际上数字之间不插入运算符也是一种运算,我把它称之为“合并”(以符号”|”表示),例如示例中的10-5可以看成1|0-5,而且合并的优先级比乘法更高。因此我想到的一种搜索方法是优先级高到低的枚举,以”105”为例的搜索过程如下图(因为加法与减法的优先级相同,因此这里略去对减法的搜索):
这里写图片描述

后来在开始构思实现细节的时候,发现了两个因为搜索顺序而引起的比较大的问题:

  • 因为对运算符的搜索并没有严格按照某个方向的顺序进行(搜索是按优先级进行的),导致在表达式右边的运算符可能先被搜索,将产生一些中间结果。例如”12345”经过两次搜索变成”1 2|3 4|5”,23和45并没有出现在原表达式中,需要为这些中间结果储存运算符串,表示怎么通过运算得到这些中间结果,每一层的搜索都需要将所有中间结果及其运算符串向下传递,空间规模的增长不可控制。
  • “合并”运算不好定义,因为会产生中间结果,中间结果的位数不确定,因此下不了代数定义,只能用字符串拼接实现。

空间问题相对来说更加严重,因此需要优先考虑解决方案,既然问题出自可能先搜索后面的运算符,那么我可以令运算符的搜索严格从左到右,这样运算符串也是自左向右增加的,不会突然出现在中间。但是每一层的搜索的运算符串仍然需要向下传递?空间问题没有解决?一旦我将遍历定在自左向右,我就可以使用深度优先搜索。这时候在同一次递归中,所有进入下层递归的运算符串具有相同的前缀,向下的搜索不会改变之前的运算符,因此整个深度优先搜索过程只需要共用一个运算符串就可以。
另外使用深度优先搜索恰好也解决了第二个问题,从左到右搜索,|运算的右运算数永远是一位数,所以可以定义a|b=a*10+b。
于是问题只剩下,如何在从左到右搜索的过程中准确地算出表达式的值。毕竟,总不能把2+3*5算成(2+3)*5的吧。

解决方案是,引入累计器概念来保证运算完全按优先级进行。

  • 4种运算共有3个优先级(|最高,*次之,+-最低),为每个优先级单独设置一个累计器,累计器的值实质是运算的左运算数。
  • 在进行一个运算时,要先把更高优先级的运算结算完成,再把更高优先级的累计器的值参与到运算中(也即先进行高优先级运算再进行低优先级运算),最后结果存放在该运算的累计器中,更高优先级的累计器清空。例如,在搜索到*号时,要先把之前出现的|运算算出结果,跟存放在乘法累计器的值相乘,此时乘法累计器的值才是这个乘法真正的左运算数。
  • 乘法运算的累计器清空默认值为1,这是因为乘法的单位元是1,其余两个累计器的清空默认值是0。

深度优先搜索中累计器的操作:
记|运算的累计器值为ac,乘法的累计器值为am,加减法的累计器值为as,当前搜索位的数字是k。(k=num[index])
在任意时刻,搜索得到的表达式的值为(ac*10+k)*am+as

  • |运算:由于是最高优先级的运算,因此只需要修改ac。需要注意不允许1+0|9这种情况(数的最高位不能为0)
    (ac, am, as) -> (ac*10+k, am, as)
  • 乘法:进行乘法之前,需要先把之前出现的|运算完成(即ac|k),再将结果与am相乘。如果表达式从头到尾都没有|运算呢?如果是这样的话,ac=0,ac*10+k=k,则am=k*am,这就相当于只做了乘法,这与实际是一致的。
    (ac, am, as) -> (0, (ac*10+k)*am, as)
  • 加法:进行加法之前,需要先把之前出现的|运算和乘法都完成(即(ac|k)*am),再将结果与as相加。如果表达式只有加减法?那么ac=0,am=1,as=(ac*10+k)*am+as = k*am+as=k+as,与只做加法一致。
    (ac, am, as) -> (0, 1, (ac*10+k)*am+as)
  • 减法:与加法的操作大致相同,唯一的不同是需要将乘法累计器设置为-1,这样在之后的运算中(ac*10+k)*am+as就相当于as-|(ac*10+k)*am|。(这里的|x|是绝对值)
    (ac, am, as) -> (0, -1, (ac*10+k)*am+as)

以”125340”在某一搜索过程中得到125+3*40为例:
这里写图片描述
那么表达式的值是(4*10+0)*3+125=40*3+125=245

现在能够准确算出表达式的值,那么剩下的步骤是跟目标值比较,如果一致,则将原来的数字串num(“125340”)与搜索得到的符号串(“||+*|”)合并得到表达式(“125+3*40”),所有的”|”都不输出。

实现的细节如下:

class Solution {
private:
    long long _target;
    string _num;

    // ac : accumlator of combine
    // am : accumlator of multiply
    // as : accumlator of sum&minus
    void recur(char *ops, int index, long long ac, long long am,
               long long as, vector<string>& result) {
      int k = _num[index] - '0';
      if(index == _num.size() - 1) {
        if((ac*10+k)*am+as == _target) {
          string s;
          for(int i = 0;i<_num.size()-1;i++) {
            s.push_back(_num[i]);
            if(ops[i] != '|') s.push_back(ops[i]);
          }
          s.push_back(_num[_num.size()-1]);
          result.push_back(s);
        }
        return;
      }

      if(!(ac == 0 && k == 0)) {
        ops[index] = '|';
        recur(ops, index+1, ac*10+k, am, as, result);
      }

      ops[index] = '*';
      recur(ops, index+1, 0, (ac*10+k)*am, as, result);

      ops[index] = '+';
      recur(ops, index+1, 0, 1, (ac*10+k)*am+as, result);

      ops[index] = '-';
      recur(ops, index+1, 0, -1, (ac*10+k)*am+as, result);
    }

public:
    vector<string> addOperators(string num, int target) {
      _target = target;
      _num = num;
      vector<string> result;

      if(num.size() == 0) return result;
      char ops[num.size()];
      recur(ops, 0, 0, 1, 0, result);
      return result;
    }
};
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值