这道LeetCode 964题要求我们找到用最少的运算符表达给定数字target的方法。题目允许使用数字x,通过加减乘除和括号组合来构造表达式,最终计算得到target值。关键在于如何系统性地寻找运算符数量最少的表达式。
从数学角度看,这实际上是一个动态规划问题。我们需要考虑如何将target分解为x的幂次组合,同时最小化运算符的使用次数。举个例子,当x=3,target=19时,最优解是"33+33+3/3",共使用了5个运算符。
这个问题本质上是在寻找target的x进制表示的最优形式。我们可以将target表示为:
target = a0 * x^0 + a1 * x^1 + ... + ak * x^k
其中每个系数ai满足0 ≤ ai < x。但直接这样表示可能不是最优的,因为有时用(x^(k+1) - (x-a)*x^k)这样的形式可能更节省运算符。
我们定义dp(t)表示表达数字t所需的最少运算符数量。状态转移需要考虑以下几种情况:
对于任意t,我们可以考虑将其表示为:
t = k * x^d + remainder
然后递归求解k和remainder的最优表达式。这里k可以是:
因为有时候用(x^(d+1) - (x-k)*x^d)的形式可能更优。
我们可以采用自上而下的记忆化递归方法,使用哈希表存储已经计算过的结果,避免重复计算。
c复制#define MIN(a,b) ((a)<(b)?(a):(b))
int leastOpsExpressTarget(int x, int target) {
int memo[10000] = {0};
return dp(x, target, memo);
}
int dp(int x, int t, int* memo) {
if (t == 0) return 0;
if (t == 1) return 1; // x/x
if (memo[t] != 0) return memo[t];
int k = log(t) / log(x);
int p = pow(x, k);
int d = t / p;
int r = t % p;
// 情况1:d*x^k + remainder
int res = d * k + dp(x, r, memo);
// 情况2:(d+1)*x^k - (x - remainder)*x^{k-1}
if (r > 0) {
int res2 = (d + 1) * k + dp(x, p - r, memo);
res = MIN(res, res2);
}
// 特殊情况:当k=0时,需要考虑用x/x相加
if (k == 0) {
res = MIN(res, 2 * (x - t));
}
memo[t] = res;
return res;
}
log(t)/log(x)计算x的幂次k,使得x^k ≤ t < x^d = t / p计算系数d,r = t % p计算余数r使用记忆化后,每个target值只计算一次,时间复杂度为O(target)。但由于x的幂次增长很快,实际递归深度为O(log_x(target))。
需要O(target)的空间存储memo数组。可以通过限制memo大小来优化,因为较大的target会很快被分解为较小的子问题。
需要特别注意以下边界情况:
c复制printf("%d\n", leastOpsExpressTarget(3, 19)); // 输出5
printf("%d\n", leastOpsExpressTarget(5, 501)); // 输出8
printf("%d\n", leastOpsExpressTarget(100, 1000000)); // 输出3
x=3, target=19:
x=5, target=501:
x=100, target=1000000:
提示:在实现时,可以先用小例子手动计算验证算法正确性,再逐步扩展到一般情况。
可以将递归改为迭代,从小的target开始计算,逐步构建更大的解。这样可以避免递归开销,有时还能优化空间复杂度。
对于特定x值,可能存在数学规律可以直接计算最优解,而不需要动态规划。例如当x=2时,问题与二进制表示相关。
在递归过程中,可以提前终止明显不会得到更优解的分支,减少不必要的计算。
虽然这个问题看起来是纯数学问题,但它有实际的应用价值:
这个问题可以扩展为更一般的算术表达式优化问题:
在实际编码中,我发现以下几点特别重要:
这个问题的难点在于如何系统地考虑所有可能的表达式形式,并找到其中最优的。通过动态规划方法,我们可以有效地分解问题,避免暴力搜索的高复杂度。