LeetCode 600. Non-negative Integers without Consecutive Ones 题解
题目描述
Given a positive integer n, find the number of non-negative integers less than or equal to n, whose binary representations do NOT contain consecutive ones.
Example 1:
Input: 5
Output: 5
Explanation:
Here are the non-negative integers <= 5 with their corresponding binary representations:0 : 0
1 : 1
2 : 10
3 : 11
4 : 100
5 : 101Among them, only integer 3 disobeys the rule (two consecutive ones) and the other 5 satisfy the rule.
Note: 1≤n≤109
题目分析
题目大意是求出0~n范围内, 有多少个数用二进制表示时不会出现连续的1
思路:
朴素思想是从0~n
逐一判断,看其是否违反约束。 对于判断的每一个数, 最多需要判断32位。可以近似看做判断一个数是否满足条件的复杂度 是
O(1)
,所以这种方法总复杂度
O(n)
.
这题的数据范围
1≤n≤109
. 这个数据范围是什么概念呢? 简单的说, 就是
O(n)
的方法可能过也可能不过, 取决于常数大小。我自己没有试,但是很可能是有一个测试点就是
n=109
, 为了把这种方法卡掉~。~
下面说更优的解法。数位DP。
数位DP之前有写过一道题, 所以大致的思想就不想再重复一遍了。直接说一下如何DP,以及状态转移方程。
首先,为什么这题可以用数位DP? 注意到这题的新状态显然是可以由先前的状态推导出来的。 并且可以由一系列在“位”层面的
base
来组合推导出来。
先以普通的DP形式来举几个例子。转成数位DP并不复杂。
Example1:n=(1001001001)2
看看看转成以下形式是否也有问题:
f(1001001001)=f(1000000000)+f(1000000)+f(1000)+f(1)−3
前面若干项和应该不难理解。 之后的
−3
或许有些费解。这是一个去重的步骤。
f(1)=2
, 这其中包含了一个
0
.而当我们计算
更简洁的, 可以写成如下形式:
f(1001001001)=f(111111111)+f(111111)+f(111)+f(0)+1
这样写的意思很明显, 后续全是0的都先不算, 最后再加上1即可。
Example2:n=(100101101)2
这个例子和上面那个有所不同, 因为它的二进制本身就有连续
1
出现。
这个例子可以表示成如下形式:
对比可以发现, 这里有两处不同:
1. 出现连续
1
后, 后续位都被舍去。
2. 最后不需要补上
对于第一点,考虑例子的最后几位
(1101)2
,由于不能出现连续
1
, 所以至多只需要统计到
这里再多加解释一个小问题。 考虑如下例子:
按照上面所说, 分解为
(1111111)2,(111111)2
即可。 但是可能会有人说, 这样分完之后必然也有不需要计算的, 因为分解成为
2k−1
的和形式必然伴随大量的连续
1
. 这一点没错,事实上只需要到
但是考虑到分解成 2k−1 的形式对实现数位DP更加容易, 并且两者实际上用数位DP解没有复杂度上的差异(并不会造成重复计算), 所以分解成如上形式是合理的。(可以等下回过头来考虑复杂度, 就不会困惑了)
对于第二点, 如果真的理解了为什么要补上后续为
0
的那一个,应该是很好理解的:对于已经出现连续0的, 后续补上为
好了解释就到上面了。 下面是实现代码。 代码大体上很好理解, 但是其中有若干个地方就体现了刚才说的几个需要注意的细节。
class Solution {
public:
vector<int> equalBitOnes(vector<int> v, bool & merge) {
vector<int> ans;
if (v.size() == 0) return ans;
ans.push_back(v[0]);
for (int i = 1; i < v.size(); ++i) {
if (v[i] < ans[ans.size() - 1] - 1) ans.push_back(v[i]);
else {
if (v[i] == ans[ans.size() - 1] - 1 && ans[ans.size() - 1] - 1 >= 0) {
ans.push_back(ans[ans.size() - 1] - 1);
merge = true;
return ans;
}
}
}
merge = false;
return ans;
}
vector<int> getBitOnes(int n) {
int i = 0;
vector<int> ans;
while (n > 0) {
if (n & 0x1) ans.push_back(i);
++i;
n >>= 1;
}
reverse(ans.begin(), ans.end());
return ans;
}
int findIntegers(int num) {
bool merge = false;
vector<int> bitOnes = equalBitOnes(getBitOnes(num), merge);
vector<int> Fibo(max(bitOnes[0], 2) + 1, 0);
Fibo[0] = 1; Fibo[1] = 2;
for (int i = 2; i <= bitOnes[0]; ++i) {
Fibo[i] = Fibo[i - 1] + Fibo[i - 2];
}
int ans = 0;
for (int i = 0; i < bitOnes.size(); ++i) {
ans+= Fibo[bitOnes[i]];
}
return (merge ? ans : ans + 1);
}
};
其他细节
- 通篇我并没有说分解到若干个 base=2k−1 后, 如何计算这些 base 的函数值。 这并不是一个难解决的问题。 事实上就是一个 Fibonacci 数列。
- 整体的思路其实并不难想, 但是细节的处理要十分小心。 20min想出思路, 却花了3个多小时来调细节。。。 归根到底还是对思路不够明晰, 在处理重复的时候犯了很多次错。
- 数位DP有明显的套路性(至少一些简单的数位DP是这样)。可以和之前我写的那篇对比。 真的是思出同门~.~