题目描述
给定一个正整数 x
,我们将会写出一个形如 x (op1) x (op2) x (op3) x ...
的表达式,其中每个运算符 op1
,op2
,… 可以是加、减、乘、除(+
,-
,*
,或是 /
)之一。例如,对于 x = 3
,我们可以写出表达式 3 * 3 / 3 + 3 - 3
,该式的值为 3 。
在写这样的表达式时,我们需要遵守下面的惯例:
- 除运算符(
/
)返回有理数。
- 任何地方都没有括号。
- 我们使用通常的操作顺序:乘法和除法发生在加法和减法之前。
- 不允许使用一元否定运算符(
-
)。例如,“x - x
” 是一个有效的表达式,因为它只使用减法,但是 “-x + x
” 不是,因为它使用了否定运算符。
我们希望编写一个能使表达式等于给定的目标值 target
且运算符最少的表达式。返回所用运算符的最少数量。
示例 1:
输入:x = 3, target = 19
输出:5
解释:3 * 3 + 3 * 3 + 3 / 3 。表达式包含 5 个运算符。
示例 2:
输入:x = 5, target = 501
输出:8
解释:5 * 5 * 5 * 5 - 5 * 5 * 5 + 5 / 5 。表达式包含 8 个运算符。
示例 3:
输入:x = 100, target = 100000000
输出:3
解释:100 * 100 * 100 * 100 。表达式包含 3 个运算符。
提示:
2 <= x <= 100
1 <= target <= 2 * 108
解法
方法一:记忆化搜索
我们定义一个函数 $dfs(v)$,表示用 $x$ 凑成数字 $v$ 所需要的最少运算符数量。那么答案就是 $dfs(target)$。
函数 $dfs(v)$ 的执行逻辑如下:
如果 $x \geq v$,那么此时可以用 $v$ 个 $x / x$ 相加来得到 $v$,运算符数量为 $v \times 2 - 1$;也可以用 $x$ 减去 $(x - v)$ 个 $x / x$ 来得到 $v$,运算符数量为 $(x - v) \times 2$。取两者的最小值。
否则,我们从 $k=2$ 开始枚举 $x^k$,找到第一个 $x^k \geq v$ 的 $k$:
- 如果此时 $x^k - v \geq v$,那么只能先得到 $x^{k-1}$,然后再递归计算 $dfs(v - x^{k-1})$,此时运算符数量为 $k - 1 + dfs(v - x^{k-1})$;
- 如果此时 $x^k - v < v$,那么可以按照上面的方式得到 $v$,此时运算符数量为 $k - 1 + dfs(v - x^{k-1})$;也可以先得到 $x^k$,再递归计算 $dfs(x^k - v)$,此时运算符数量为 $k + dfs(x^k - v)$。取两者的最小值。
为了避免重复计算,我们使用记忆化搜索的方式实现 $dfs$ 函数。
时间复杂度 $O(\log_{x}{target})$,空间复杂度 $O(\log_{x}{target})$。
1
2
3
4
5
6
7
8
9
10
11
12
13
14 | class Solution:
def leastOpsExpressTarget(self, x: int, target: int) -> int:
@cache
def dfs(v: int) -> int:
if x >= v:
return min(v * 2 - 1, 2 * (x - v))
k = 2
while x**k < v:
k += 1
if x**k - v < v:
return min(k + dfs(x**k - v), k - 1 + dfs(v - x ** (k - 1)))
return k - 1 + dfs(v - x ** (k - 1))
return dfs(target)
|
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30 | class Solution {
private int x;
private Map<Integer, Integer> f = new HashMap<>();
public int leastOpsExpressTarget(int x, int target) {
this.x = x;
return dfs(target);
}
private int dfs(int v) {
if (x >= v) {
return Math.min(v * 2 - 1, 2 * (x - v));
}
if (f.containsKey(v)) {
return f.get(v);
}
int k = 2;
long y = (long) x * x;
while (y < v) {
y *= x;
++k;
}
int ans = k - 1 + dfs(v - (int) (y / x));
if (y - v < v) {
ans = Math.min(ans, k + dfs((int) y - v));
}
f.put(v, ans);
return ans;
}
}
|
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27 | class Solution {
public:
int leastOpsExpressTarget(int x, int target) {
unordered_map<int, int> f;
function<int(int)> dfs = [&](int v) -> int {
if (x >= v) {
return min(v * 2 - 1, 2 * (x - v));
}
if (f.count(v)) {
return f[v];
}
int k = 2;
long long y = x * x;
while (y < v) {
y *= x;
++k;
}
int ans = k - 1 + dfs(v - y / x);
if (y - v < v) {
ans = min(ans, k + dfs(y - v));
}
f[v] = ans;
return ans;
};
return dfs(target);
}
};
|
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25 | func leastOpsExpressTarget(x int, target int) int {
f := map[int]int{}
var dfs func(int) int
dfs = func(v int) int {
if x > v {
return min(v*2-1, 2*(x-v))
}
if val, ok := f[v]; ok {
return val
}
k := 2
y := x * x
for y < v {
y *= x
k++
}
ans := k - 1 + dfs(v-y/x)
if y-v < v {
ans = min(ans, k+dfs(y-v))
}
f[v] = ans
return ans
}
return dfs(target)
}
|
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24 | function leastOpsExpressTarget(x: number, target: number): number {
const f: Map<number, number> = new Map();
const dfs = (v: number): number => {
if (x > v) {
return Math.min(v * 2 - 1, 2 * (x - v));
}
if (f.has(v)) {
return f.get(v)!;
}
let k = 2;
let y = x * x;
while (y < v) {
y *= x;
++k;
}
let ans = k - 1 + dfs(v - Math.floor(y / x));
if (y - v < v) {
ans = Math.min(ans, k + dfs(y - v));
}
f.set(v, ans);
return ans;
};
return dfs(target);
}
|