题目
给你一个以二进制形式表示的数字 s 。请你返回按下述规则将其减少到 1 所需要的步骤数:
- 如果当前数字为偶数,则将其除以
2。 - 如果当前数字为奇数,则将其加上
1。
题目保证你总是可以按上述规则将测试用例变为 1 。
示例 1:
输入:s = "1101" 输出:6 解释:"1101" 表示十进制数 13 。 Step 1) 13 是奇数,加 1 得到 14 Step 2) 14 是偶数,除 2 得到 7 Step 3) 7 是奇数,加 1 得到 8 Step 4) 8 是偶数,除 2 得到 4 Step 5) 4 是偶数,除 2 得到 2 Step 6) 2 是偶数,除 2 得到 1
示例 2:
输入:s = "10" 输出:1 解释:"10" 表示十进制数 2 。 Step 1) 2 是偶数,除 2 得到 1
示例 3:
输入:s = "1" 输出:0
提示:
1 <= s.length <= 500s由字符'0'或'1'组成。s[0] == '1'
解题思路
直观模拟法
这是最符合直觉的方法:题目怎么说,我就怎么做。
- 准备阶段:因为 Python 的字符串不能改,所以先用
list_s = list(s)把文本拆成列表。 - 判断奇偶:看列表的最后一位
list_s[-1]。- 如果是
'0'(偶数):执行“除以 2”,在二进制里就是删掉最后一位。对应操作:list_s.pop()。 - 如果是
'1'(奇数):执行“加 1”。对应操作:调用你写的add_one函数。
- 如果是
- 循环终止:直到列表里只剩下
['1']为止。 - 计数:每操作一次,
ans += 1。
评价:逻辑清晰,非常适合理解二进制底层操作。虽然在极端情况下(比如全是 1)不断进位会导致效率略低,但对于题目给出的长度 500 的限制,这绝对能过。
大数转换法(Python 的特权)
Python 的 int 类型是“自带外挂”的——它支持无限精度。
- 一键转换:利用
num = int(s, 2)直接把几百位的二进制字符串变成一个巨大的十进制整数。
简单循环:
while num > 1:
if num % 2 == 0:
num //= 2
else:
num += 1
ans += 1
评价:这是“暴力美学”。在别的语言(如 C++ 或 Java)里,500 位的二进制数会直接溢出(long long 也放不下),但 Python 却能处理得游刃有余。这是日常开发中最快、最不容易出错的写法。
一次遍历法(算法优化 $O(N)$)
这里的核心逻辑是观察“进位”**对后续位的影响:
- 从右往左看:
- 如果遇到
0且没有进位,直接除以 2(1 步)。 - 如果遇到
1,它必须先加 1 变成偶数(1 步),然后除以 2(1 步),共 2 步。并且,它会产生一个持续向左的“进位”。 - 一旦产生了进位,接下来的
0也会被进位变成1,从而变成“奇数”的情况。
逻辑精髓:
- 只要最右边出现了
1,它就迟早要通过“加 1”变成0并进位。 - 每一个
0最终都会因为除以 2 被消掉。 - 每一个
1(除了最左边的那个)最终都要先加 1 变成0,再除以 2 消耗掉。
在二进制中,如果你从最低位(最右边)开始看:
- 如果当前位 + 进位 = 0:
- 说明它是偶数。只需要执行 1 步:除以 2。
- 进位保持为
0。
- 如果当前位 + 进位 = 1:
- 说明它是奇数。需要执行 2 步:先加 1(变偶数),再除以 2。
- 此时会产生一个进位,所以
carry变为1。
- 如果当前位 + 进位 = 2:
- 说明原本是
1且有进位,变回了偶数(10)。只需要执行 1 步:除以 2。 - 进位依然保持为
1。
- 说明原本是
具体代码
方法一
class Solution:
def numSteps(self, s: str) -> int:
ans = 0
list_s = list(s)
while list_s != ["1"]:
if list_s[-1] == "1":
list_s = self.add_one(list_s)
else:
list_s.pop()
ans += 1
return ans
def add_one(self, list_s):
i = len(list_s) - 1
while i >= 0:
if list_s[i] == "0":
list_s[i] = "1"
return list_s
else:
list_s[i] = "0"
i -= 1
return ["1"] + list_s
方法二
class Solution:
def numSteps(self, s: str) -> int:
num = int(s, 2) # 直接转成十进制大整数
steps = 0
while num > 1:
if num % 2 == 1: num += 1
else: num //= 2
steps += 1
return steps
方法三
class Solution:
def numSteps(self, s: str) -> int:
steps = 0
carry = 0
# 从右往左遍历,直到倒数第二位 (索引 1)
for i in range(len(s) - 1, 0, -1):
# 当前位真正的数值 = 原始字符 + 进位
current_val = int(s[i]) + carry
if current_val == 1:
# 奇数情况:加 1 再除以 2,共 2 步
steps += 2
carry = 1 # 产生/维持进位
else:
# 偶数情况(0+0 或 1+1):只需除以 2,共 1 步
steps += 1
# 如果是 1+1,carry 依然是 1;如果是 0+0,carry 依然是 0
# 简单写法:如果 current_val 是 2,carry 保持 1,否则保持原样
if current_val == 2:
carry = 1
# 最后处理最左边的 s[0]
# s[0] 始终是 '1'。如果最后还有进位,说明变成了 '10',还需要 1 步除法
return steps + carry