题目是LeetCode第183场周赛的第二题,链接:将二进制表示减到 1 的步骤数。具体描述为:给你一个以二进制形式表示的数字 s 。请你返回按下述规则将其减少到 1 所需要的步骤数:如果当前数字为偶数,则将其除以 2 。如果当前数字为奇数,则将其加上 1 。题目保证你总是可以按上述规则将测试用例变为 1 。
- 1 <= s.length <= 500
- s 由字符 ‘0’ 或 ‘1’ 组成。
- s[0] == ‘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
一种简单的思路就是直接模仿二进制操作,用一个left和一个right记录我们所操作的二进制数的位置为left到right(当然这里的left总是0可以去掉)。除以2相当于数右移一位,等价于right-1。加1的话需要模仿二进制加法,遇到111...111
的特殊情况可能会导致left左移而越界,好在这种情况下已经可以知道还需要多少次操作便可以得到需要的1(因为+1后就是1000...000
的格式,只需要不断除以2,需要的次数就是0的个数)。时间复杂度为
O
(
n
)
O(n)
O(n),空间复杂度为
O
(
n
)
O(n)
O(n)。
JAVA版代码如下:
class Solution {
public int numSteps(String s) {
char[] chars = s.toCharArray();
int left = 0;
int right = s.length() - 1;
int count = 0;
while (left < right) {
if (chars[right] == '0') {
++count;
--right;
}
else {
// 奇数的时候必定是先+1(所以有进位)变为偶数再除以2,两步一块做了
count += 2;
boolean carry = true;
--right;
for (int i = right; i >= left; --i) {
if (carry) {
if (chars[i] == '1') {
chars[i] = '0';
if (i == left) {
// 第一位也产生进位,说明这个数是111...111的格式
// +1后是1000...000,可以知道接下来还需要多少次除以2的操作得到1
return count + right - left + 1;
}
}
else {
chars[i] = '1';
carry = false;
}
}
else {
break;
}
}
}
}
return count;
}
}
提交结果如下:
上面的代码还是有点繁琐,可以稍作改变写成下面的更简洁的:
class Solution {
public int numSteps(String s) {
int count = 0;
int i = s.length() - 1;
char[] chars = s.toCharArray();
while (i > 0) {
if (chars[i] == '0') {
++count;
--i;
}
else {
// 奇数+1之后会产生进位,需要考虑前面有多少个连续的1
++count;
// 有多少个连续的1就会在+1之后产生多少个0,也就需要多少次除以2
while (i > 0 && chars[i] == '1') {
++count;
--i;
}
if (i > 0) {
chars[i] = '1';
}
else {
// 一直到首位都是1的话已经可以得到结果了
return count + 1;
}
}
}
return count;
}
}
提交结果如下:
另一种也蛮简洁的写法(空间复杂度为 O ( 1 ) O(1) O(1))如下:
class Solution {
public int numSteps(String s) {
int count = 0;
boolean carry = false;
for (int i = s.length() - 1; i >= 0; --i) {
if (!carry && i == 0) {
// 对应于100...000这种情况,避免进入后面的+1再除以2
break;
}
if ((carry && s.charAt(i) == '0') || (!carry && s.charAt(i) == '1')) {
// 有进位且当前位为0或没进位且当前位为1都会导致当前位实际为1,需要两步:+1然后除以2
++count;
carry = true;
}
// 除以2的那一步
++count;
}
return count;
}
}
提交结果如下:
Python版代码如下:
class Solution:
def minSubsequence(self, nums: List[int]) -> List[int]:
count = [0 for _ in range(100)]
totalSum = 0
for n in nums:
totalSum += n
count[n - 1] += 1
result = []
cumSum = 0
for i in range(99, -1, -1):
for j in range(count[i]):
result.append(i + 1)
cumSum += i + 1
if cumSum > totalSum - cumSum:
return result
return result
提交结果如下: