小O的整数操作
小O的整数操作
OPPO 笔试真题 · 2024 秋招 · AI/算法岗
判题请到牛客原题提交。
题目描述
小O有一个整数 ,她每次可以进行以下操作之一:
将 减去 ;
将 除以 (当 可以被 整除时)。
小O想知道,将 变成 至少需要多少次操作。
输入格式
在一行上输入三个整数 和 表示初始数字、目标数字和 可以除的数字。
输出格式
在一行上输出一个正整数,表示将 变成 至少需要多少次操作。
示例
示例 1
输入:
10 4 2输出:
2说明:
先将 10 除以 2 得到 5,再将 5 减去 1 得到 4,共需要 2 次操作。
解题思路
第一步:理解问题本质
两种操作都只会让 变小:
- ,减 1;
- ,除以 (要求整除)。
所以整个过程的形状是固定的:从 出发一路往下走到 ,中间的每个状态
只需要决定「这一步是减 1 还是除 」。
第二步:为什么除法应该优先
一次除法把 变成 。如果改用减法达到同样的效果,需要
次。只要 ,就有 ,一次除法
几乎总是更划算,而且除完之后数更小,后面的减法次数也跟着变少。
所以策略很直接:能除就除。
第三步:除法之前要处理两件事
真正写代码时,除法有两个前提:
- 要整除。 时先减 次把它凑成 的倍数。
减多了是浪费,减少了除不了,所以恰好减 次。 - 不能除过头。如果 ,除完就低于目标,而操作只能让数变小,
再也回不去了。这种情况下只能老老实实地减 次。
同理,如果「减到最近的 的倍数」这一步就会掉到 以下,那也直接减到
收工。
还有一个退化情况: 时除以 1 原地踏步,完全没有意义,答案就是
。不特判的话会死循环。
每除一次,数字至少减半,所以循环次数是 而不是 。
完整代码实现
import sys
def solve() -> None:
"""贪心地「能除就除,除不动就先补到能除」"""
data = sys.stdin.buffer.read().split()
if not data:
return
n, m, k = int(data[0]), int(data[1]), int(data[2])
if k == 1: # 除法原地踏步,只剩减法
sys.stdout.write(str(n - m) + "\n")
return
ans = 0
while n > m:
if n % k == 0:
if n // k >= m: # 除完不会越过 m,除是划算的
n //= k
ans += 1
else: # 除完就比 m 小了,只能减
ans += n - m
break
else:
r = n % k # 先减到最近的 k 的倍数
if n - r >= m:
n -= r
ans += r
else: # 减不到下一个倍数,直接减到 m
ans += n - m
break
sys.stdout.write(str(ans) + "\n")
if __name__ == "__main__":
solve()示例推演
以示例 n = 10, m = 4, k = 2 为例:
| 步骤 | 当前 | 判断 | 动作 | 累计操作 |
|---|---|---|---|---|
| 1 | 10 | 且 | 除以 2, | 1 |
| 2 | 5 | ,凑整减 1 后 | 减 1, | 2 |
| 3 | 4 | ,结束 | — | 2 |
答案 2。如果全程用减法需要 次,差距很明显。
再看 n = 7, m = 3, k = 2:
| 步骤 | 当前 | 判断 | 动作 | 累计操作 |
|---|---|---|---|---|
| 1 | 7 | , | 减 1, | 1 |
| 2 | 6 | 除以 2, | 2 |
答案 2,比直接减 4 次()更省。
最后看一个「除过头」的例子 n = 10, m = 6, k = 2:,
只要除就会掉到目标以下,所以只能减 次,答案 4。
复杂度分析
| 解法 | 时间复杂度 | 空间复杂度 | 说明 |
|---|---|---|---|
| 一步步减 1 模拟 | 到 时必然超时 | ||
| 贪心「能除就除」(本文) | 每除一次至少减半 |
易错点总结
1. 忘了特判
,循环会永远停在原地。 时除法没有意义,答案直接是
。
2. 先除再判断,掉到 以下
:先除得到 5,比目标还小,再也回不来。必须
先判断 再除。
3. 除不尽时一路减 1
时一直减到 6 才对,但更省的做法是「减 次」。
可能是 ,逐次减 1 会超时。
4. 循环条件写成 while n != m
如果某一步除不动( 且减不到倍数),n 可能永远等于不了m,会死循环。用 while n > m,并且在两个「只能减」的分支里直接break。
5. 的答案是 0
不要漏掉这个情况,也不要把答案写成「正整数」就强行返回 1。
扩展思考
- 如果操作变成「 和 」,方向就反过来了:应当从 出发,考虑
「乘 还是 」,这是另一道经典题( 需要先乘到不超过 的最大
的幂)。 - 如果每一步的除数可以自己选,问题就变成「用哪些因子做除法最省」,
适合从大到小枚举 的因数。 - 如果把两种操作看成无权图上的两条边,这题就是 到 的最短路。
贪心的正确性来自边的方向单一(只会变小)以及除法收益的结构性优势;
一旦允许「变大」的操作,贪心就不成立了,得回到 BFS。 - 也可以用 进制来理解:一次「减到最近的倍数」正好把最低一位清零,代价
等于这一位的数字;一次除法相当于右移一位。所以总操作数大致就是 的
进制各位数字之和,最后再按 做边界修正。