0964. 表示数字的最少运算符
大约 3 分钟
---
0964. 表示数字的最少运算符
- 标签:记忆化搜索、数学、动态规划
- 难度:困难
题目链接
题目大意
描述:
给定一个正整数 ,我们将会写出一个形如 的表达式,其中每个运算符 可以是加、减、乘、除(+,-,*,或是 /)之一。例如,对于 ,我们可以写出表达式 ,该式的值为 3。
在写这样的表达式时,我们需要遵守下面的惯例:
- 除运算符(
/)返回有理数。 - 任何地方都没有括号。
- 我们使用通常的操作顺序:乘法和除法发生在加法和减法之前。
- 不允许使用一元否定运算符(
-)。例如, 是一个有效的表达式,因为它只使用减法,但是 不是,因为它使用了否定运算符。
我们希望编写一个能使表达式等于给定的目标值 且运算符最少的表达式。
要求:
返回所用运算符的最少数量。
说明:
- 。
- 。
示例:
- 示例 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 个运算符。解题思路
思路 1:记忆化搜索 + 动态规划
思路
这道题要求用最少的运算符表示目标数字 。我们可以将 看作 进制数,每一位可以是 到 (允许进位)。
关键观察:
- 需要 ,即 个运算符(一个除号一个乘号,但连接时算作 个代价)。
- 不需要运算符(直接使用 ,但连接时需要 个加号/减号)。
- 需要 个运算符( 个乘号 + 个加号/减号用于连接)。
状态定义:
- 定义 表示用 及更高次幂表示 的最少运算符数。
- 对于每个位置 ,计算 除以 的商 和余数 :
- 方案 1:使用 个 ,然后递归处理 。
- 方案 2:使用 个 (相当于凑到下一个 ),然后递归处理 。
代价计算:
- 预先计算 ,表示使用一个 需要的运算符数:
- ( 需要 个运算符)
- ( 时, 需要 个运算符)
代码
class Solution:
def leastOpsExpressTarget(self, x: int, target: int) -> int:
from functools import lru_cache
# cost[i] 表示使用一个 x^i 需要的运算符数
# cost[0] = 2 (x/x),cost[i] = i (i >= 1)
cost = [2] + list(range(1, 40))
@lru_cache(None)
def dp(i, targ):
"""用 x^i 及更高次幂表示 targ 的最少运算符数"""
# 基本情况
if targ == 0:
return 0
if targ == 1:
return cost[i]
if i >= 39: # 防止溢出
return float('inf')
# 计算 targ 除以 x 的商和余数
quotient, remainder = divmod(targ, x)
# 方案 1:使用 remainder 个 x^i,然后处理 quotient
# 每个 x^i 需要 cost[i] 个运算符
ans1 = remainder * cost[i] + dp(i + 1, quotient)
# 方案 2:使用 (x - remainder) 个 -x^i,然后处理 quotient + 1
# 相当于凑到下一个 x^(i+1)
ans2 = (x - remainder) * cost[i] + dp(i + 1, quotient + 1)
return min(ans1, ans2)
# 从 x^0 开始,最后减 1 是因为最外层不需要加号
return dp(0, target) - 1复杂度分析
- 时间复杂度:,递归深度最多为 ,每个状态最多被计算一次。
- 空间复杂度:,记忆化搜索的缓存空间。