问题描述
小M有一个坏掉的计算器,它有两种功能可以操作屏幕上显示的数字:
- 将显示的数字乘以2;
- 将显示的数字减去1。
现在,计算器上显示的数字是 x,小M希望通过最少的操作次数,将数字变为 y。
请你帮忙计算一下,最少需要多少次操作才能将数字从 x 变为 y。
测试样例
样例1:
输入:
x = 2,y = 3
输出:2
解析:2→4→3,共 2 次操作。
样例2:
输入:
x = 4,y = 7
输出:2
解析:4→8→7,共 2 次操作。
样例3:
输入:
x = 3,y = 66
输出:9
解析: 3→6→5→10→9→18→17→34→33→66,共 9 次操作。
解题思路
这个问题可以通过动态规划或贪心算法解决,但需要注意不同的优化思路。
方法 1:动态规划
思路
我们使用一个数组 dp[i] 表示将数字 i 转化为目标数字 y 所需的最小操作次数。通过递推公式进行求解。
-
当 i≤x直接通过减法操作从 x减到 y,操作次数为 dp[i]=x−i。
-
当 i>x我们需要考虑如何通过之前的状态递推:
- 如果选择将某个数字 j 乘以 2 达到 i,那么所需的操作次数为 dp[j]+1;
- 如果需要通过减法从 i 到某个较小的数字 k,我们可以递归地考虑 dp[k]。
动态规划代码实现
def solution(x: int, y: int) -> int:
if y <= x:
# 如果目标值小于等于初始值,只能通过减法操作
return x - y
else:
# 动态规划数组
dp = [0 for _ in range(y + 1)]
# 遍历所有可能的值
for i in range(y + 1):
if i <= x:
# 当当前值小于等于 x,直接用减法到达目标
dp[i] = x - i
else:
# 当前值大于 x,需要通过状态转移找到最小操作次数
temp = []
for j in range(1, i):
num = j
t = dp[j] # 从 dp[j] 开始
while num < i:
num *= 2
t += 1 # 乘法次数
t += (num - i) # 剩余通过减法操作
temp.append(t)
dp[i] = min(temp)
return dp[y]
# 测试样例
print(solution(2, 3)) # 输出: 2
print(solution(4, 7)) # 输出: 2
print(solution(3, 66)) # 输出: 9
时间复杂度
该算法需要计算所有 dp[i]的值,因此时间复杂度为 O(y2),对于较大的 y可能较慢。
方法 2:贪心算法(优化解法)
动态规划的计算复杂度较高,当 y很大时,可能无法快速计算。此时可以采用贪心算法,从目标数字 y开始反推到 x。
核心思路
- 如果 y 是偶数,我们优先通过除法操作缩小到 y/2;
- 如果 y 是奇数,我们先通过加法使其变为偶数;
- 当 y≤x 时,直接通过加法将 y 增加到 x。
贪心算法实现
def solution(x: int, y: int) -> int:
operations = 0
while y > x:
if y % 2 == 0:
y //= 2 # 如果 y 是偶数,优先进行除法操作
else:
y += 1 # 如果 y 是奇数,先加 1 使其变为偶数
operations += 1
# 当 y <= x 时,只需进行加法操作
return operations + (x - y)
# 测试样例
print(solution(2, 3)) # 输出: 2
print(solution(4, 7)) # 输出: 2
print(solution(3, 66)) # 输出: 9
贪心算法解析
- 从目标数字 y开始反推,确保每一步操作都尽可能减少目标值;
- 当目标值变得小于或等于初始值 x时,直接通过加法补齐差值;
- 整体复杂度为 O(logy),适合处理较大的输入规模。
复杂度分析
| 方法 | 时间复杂度 | 空间复杂度 |
|---|---|---|
| 动态规划 | O(y2) | O(y) |
| 贪心算法 | O(logy) | O(1) |
总结
在这道题中,我们使用了动态规划和贪心算法两种方法。动态规划适合处理小规模问题,而贪心算法则在较大规模下表现出色。最终,贪心算法是更优的解法,其核心在于从目标数字 y开始反推,确保每一步操作都能最大化减少目标值。