1404-将二进制表示减到 1 的步骤数
给你一个以二进制形式表示的数字 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 <= 500
s
由字符'0'
或'1'
组成。s[0] == '1'
方法一:模拟
我们可以直接模拟题目中的过程:
如果当前数字为偶数,则将其除以 2。当 s 为二进制表示时,就相当于去除末尾的 0。例如当 s = (11010)_2 时,除以 2 得到 (1101)_2;
如果当前数字为奇数,则将其加上 1。当 s 为二进制表示时,就相当于对 1 加上 1。例如当 s = (10011)_2 时,加上 1 得到 (10100)_2。特别地,如果 s 的二进制表示只包含 1,那么加上 1 之后会产生额外的进位,需要注意。例如当 s=(1111)_2 时,加上 1 会得到 (10000)_2。
因此我们可以使用字符串直接模拟这两种操作。由于在 C++
语言中的字符串是可变字符串,而 Python
和 Java
语言中的字符串不可变,因此下面只给出 C++
语言的代码。
1 | class Solution { |
复杂度分析
时间复杂度:O(N),其中 N 是字符串 s 的长度。我们使用均摊分析的方法计算时间复杂度:对于字符串中的一个 1,它的「命运」是在某一时刻由于加一操作变成了 0,并在接下来的某一次除二操作中被丢弃;而对于字符串中的一个 0,它的「命运」要么是直接在某一次除二操作中被丢弃,要么是由于加一操作的进位变成了 1,接下来重复作为 1 的「命运」。因此,字符串 s 中的任意一个字符不会被处理超过三次,即时间复杂度为 O(3N) = O(N)。
空间复杂度:O(1)。因为我们直接在原字符串上进行操作,因此没有使用到额外的空间。
方法二:遍历计数
在方法一的「复杂度分析」部分,我们其实已经有了一些优化的思路。具体地,我们举一个例子来看看每个字符的「命运」到底是什么。
我们以字符串 1100011100 为例:
一开始最低位为 0,这些 0 的「命运」就是被除二操作删除。我们通过两次除二的操作,得到字符串 11000111;
现在的最低位为 1,这些 1 的「命运」就是被加一操作变成 0 再被删除。我们通过一次加一操作将字符串变为 11001000,再进行三次除二操作,得到 11001。这一步的操作次数为 4;
现在的最低位为 1,我们通过两次操作(一次加一,一次除二)将字符串变为 1101。最低位仍然为 1,我们通过两次操作将字符串变为 111;最低位还是 1(思考一下,为什么最低位总是 1?除了一开始的情况,最低位可能为 0 吗?),我们通过一次加一操作将字符串变为 1000,产生了额外的进位,再通过三次除二操作将字符串变为 1。
通过上面的例子,我们可以归纳出每一个字符的「命运」以及需要的步骤数:
只有在一开始的时候,我们才需要考虑字符串最低位为 0 的情况,我们通过若干次操作删去低位的所有 0;
在任意时刻,字符串的最低位均为 1。如果有 k 个 1,那么我们需要 k + 1 次操作(1 次加一操作和 k 次除二操作)将所有的 1 变为 0 并删除。并且在这 k + 1 次操作后,原本最靠近右侧的那个 0 变为了 1。这也解释了为什么我们不需要考虑最低位为 0 的情况了。
在下面的代码中,我们将「命运」均摊到字符串中的每一个字符上进行计数,并给出了详细的注释。
1 | class Solution { |
1 | class Solution { |
1 | class Solution: |
复杂度分析
时间复杂度:O(N),我们只遍历整个字符串一次。
空间复杂度:O(1)。