给定一个正整数 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 * 10^8
一、思路
(一)动态规划
1、踩过的坑
这是一道很明显的动态规划问题,一开始我只是把这题当成一般的动态规划问题,结果。。。
当时是这么想的,既然要把这个target拆成用x表达的计算式,那么不妨从最高位开始,举个例子:
输入:x = 5, target = 501
输出:8
解释:5 * 5 * 5 * 5 - 5 * 5 * 5 + 5 / 5 。表达式包含 8 个运算符。
实际上也可以从125(5*5*5)开始做加法式的计算:
5 * 5 * 5 + 5 * 5 * 5 + 5 * 5 * 5 + 5 * 5 * 5 + 5 / 5
不过这么做显然不妥。因为运算符数量不是最少的。
于是我得出结论,每次给出一个target,需要计算的时候,都有两个方向:
- 减法方向:一个是比target要大的x的幂次(上面的例子中,这个数就是625(5 * 5 * 5 * 5))
- 加法方向:一个是比target要小的x的幂次(上面的例子中,这个数就是125(5 * 5 * 5 ))
只要能够求出两个方向中较小的值,就能得出答案
求解方法是直接递归地求解加(减)之后的target了,还是以上面的例子为例:
如果我从减法方向入手:
首先求出625,然后减去target,得124,这就是我要求的新的target,直接递归求解
当target小于x时,不必递归直接算出答案
实际上不行,因为每次计算都涉及到两个方向,一加一减,有大量的重复,甚至出现自己调用自己的情况:
输入:x = 5, target = 375
第一次做减法得:625 - 375 = 250
第二次做减法得:625 - 250 = 375
这个自己调用自己的情况,是有条件出现的,我做了一点改进,使得每次做加法或者减法时都要看看里哪个方向更近,走近的路,可以避免这个错误
C++代码:
class Solution {
public:
map<long, int> table;
int leastOpsExpressTarget(int x, int target) {
vector<int> dp(x + 5, 0);
dp[0] = -1;
for(int i=1; i <= x; i++){
int temp = ((x - i) > i) ? (2 * i - 1) : 2 * (x - i);
dp[i] = temp;
}
long max_num = computePow(x, target);
if(table.count(target))
return table[target];
long num_up = x * x, num_low = x;
long mid = (num_up + num_low) / 2;
for(long i = 2 * x; i <= target + x; i += x){
int temp;
if(i < num_up){
if(i <= mid){
temp = table[i - num_low] + table[num_low] + 1;
}
else{
temp = table[num_up] + table[num_up - i] + 1;
}
table.insert(map<long, int>::value_type(i, temp));
}
else{
num_up *= x;
num_low *= x;
mid = (num_up + num_low) / 2;
}
}
if(table.count(target))
return table[target];
mid = (num_up + num_low) / 2;
int ans;
if(true){ //
long res = target % x, n = target / x;
int ans1 = dp[res] + table[n * x] + 1, ans2 = table[(n + 1) * x] + dp[x - res] + 1;
ans = (ans1 > ans2) ? ans2 : ans1;
}
if(true){ //
long d = num_up - target;
long n = d / x, res = d % x;
int ans3 = INT32_MAX;
if(table.count(n * x) && table.count((n + 1) * x)){
int ans1 = table[num_up] + dp[res] + table[n * x] + 2, ans2 = table[num_up] + dp[x - res] + table[(n + 1) * x] + 2;
ans3 = (ans1 > ans2) ? ans2 : ans1;
}
ans = (ans > ans3) ? ans3 : ans;
}
return ans;
}
long computePow(int& x, int& target){
long num = x;
int n = 0 ;
while(num <= target + x){
table.insert(map<long, int>::value_type(num, n));
num *= x;
n++;
}
table.insert(map<long, int>::value_type(num, n));
return num;
}
};
这个代码也是错的,虽然例子过了,但是还是错了
这是因为:离的近那一方,虽然从数值上来看较小,但是使用x来表示,其代价不一定比离得远的那一方大
于是,我们还是应该从两个方向入手,不过在加法方向上需要设置限制,以免无穷无尽的加
2、带备忘录的自顶向下的动态规划
这里,我们采用除法而不是乘法,来避免数值溢出
举个例子:
输入:x = 5, target = 501
第一次,通过加法或者减法(5的0次方),使target变为5的倍数(即:500或者505)
第二次,通过加法或者减法(5的1次方),使target变为5 * 5的倍数
.......
如此下去,因为每次计算都有加减两个方向,减法方向经过数次计算会变为0,到达递归出口,但是加法不会,而且还有数值溢出的风险
因此每次计算后,我们将指数+1,而将target除以5,来表示下一次
C++代码:
class Solution {
public:
map<pair<int, int>, int> table;
int leastOpsExpressTarget(int x, int target) {
cost.push_back(2);
for(int i = 1; i < 50; i++)
cost.push_back(i);
num = x;
return dp(target, 0) - 1;
}
// 动态规划
int dp(int target, int exp){
pair<int, int> temp;
temp.first = target;
temp.second = exp;
int ans;
if(target == 0)
ans = 0;
else if(target == 1)
ans = cost[exp];
else if(exp > 32)
ans = target;
else{
int remainder1 = target % num;
int quotient = target / num, remainder2 = num - remainder1;
int cost1 = remainder1 * cost[exp], cost2 = remainder2 * cost[exp];
int ans1, ans2;
// 做减法
if(table.count(pair<int, int>(quotient, exp + 1)))
ans1 = cost1 + table[pair<int, int>(quotient, exp + 1)];
else
ans1 = cost1 + dp(quotient, exp + 1);
// 做加法
if(table.count(pair<int, int>(quotient + 1, exp + 1)))
ans2 = cost2 + table[pair<int, int>(quotient + 1, exp + 1)];
else
ans2 = cost2 + dp(quotient + 1, exp + 1);
// min(sub, add)
ans = min(ans1, ans2);
}
table.insert(map<pair<int, int>, int>::value_type(temp, ans));
return ans;
}
private:
int num;
vector<int> cost;
};