给定一个正整数 x,我们将会写出一个形如 x (op1) x (op2) x (op3) x … 的表达式,其中每个运算符 op1,op2,… 可以是加、减、乘、除(+,-,*,或是 /)之一。例如,对于 x = 3,我们可以写出表达式 3 * 3 / 3 + 3 - 3,该式的值为 3
在写这样的表达式时,我们需要遵守下面的惯例:
除运算符(/)返回有理数。任何地方都没有括号。我们使用通常的操作顺序:乘法和除法发生在加法和减法之前。不允许使用一元否定运算符(-)。例如,“x - x” 是一个有效的表达式,因为它只使用减法,但是 “-x + x” 不是,因为它使用了否定运算符。我们希望编写一个能使表达式等于给定的目标值 target 且运算符最少的表达式。返回所用运算符的最少数量。
输入:x = 3, target = 19 输出:5 解释:3 * 3 + 3 * 3 + 3 / 3 。表达式包含 5 个运算符。提示:
2 <= x <= 100 1 <= target <= 2 * 10^8
思路
答案一定有解:因为我们可以使用 target 个 x/x 相加:x/x + x/x + … + x/x,一个比较聪明的方案是先利用乘法 × 快速地将数值接近 target,然后就是讨论边界啦:
s = tar,返回结果s > tar,明显需要用减法将 s 递减,s -= x/x + … + x/xs < tar,s += x/x + x/x + … x/x 或者 s += x - (x/x + … + x/x)接下来就是定义记忆化搜索的状态了:
定义状态: f [ c u r ] f[cur] f[cur]:即 dfs(cur) 表示凑够数值 cur 所需要的最少运算符 思考初始化: f [ 0 : t a r g e t ] = 0 f[0:target] = 0 f[0:target]=0 思考状态转移方程: f [ i ] f[i] f[i],根据上面的情况写代码即可 思考输出: f [ 0 ] f[0] f[0] 因是记忆化搜索是自底向上的 class Solution { public: unordered_map<long, long> f; long dfs(int x, int re) { if (re < x) return min(2*re-1, 2*(x-re)); //剩下的re比x还要小,那么直接用re个x/x或者用x减去(x-re)个 x/x if (re == 0) return 0; if (f[re] != 0) return f[re]; long p = log(re)/log(x), sum = pow(x, p), t = sum * x - re; long ans = dfs(x, re - sum) + p; if (t < re) ans = min(ans, p+1 + dfs(x, t)); return f[re] = ans; } int leastOpsExpressTarget(int x, int target) { return dfs(x, target); } };这里关于为什么 sum * x - re < re 就严格代表 sum > re 有待证明,会的人欢迎给出证明
复杂度分析
时间复杂度: O ( l o g x t a r ) O(log_xtar) O(logxtar),空间复杂度: O ( n ) O(n) O(n),