LeetCode 600. Non-negative Integers without Consecutive Ones 题解

来源:互联网 发布:达达妈淘宝店卖假货吗 编辑:程序博客网 时间:2024/05/21 11:23

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 : 101

Among them, only integer 3 disobeys the rule (two consecutive ones) and the other 5 satisfy the rule.

Note: 1n109


题目分析

题目大意是求出0~n范围内, 有多少个数用二进制表示时不会出现连续的1

思路:
朴素思想是从0~n逐一判断,看其是否违反约束。 对于判断的每一个数, 最多需要判断32位。可以近似看做判断一个数是否满足条件的复杂度 是O(1),所以这种方法总复杂度O(n).
这题的数据范围 1n109. 这个数据范围是什么概念呢? 简单的说, 就是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(1000)+f(1)时, 实际上我们想算的是 f(1001), 而f(1000)是包含 k=1000这一项的, 因此再加上 f(1) 时, 要减去 k=0这一项, 故 f(1001)=f(1000)+f(1)1.以此类推, 一共 3.
更简洁的, 可以写成如下形式:
f(1001001001)=f(111111111)+f(111111)+f(111)+f(0)+1
这样写的意思很明显, 后续全是0的都先不算, 最后再加上1即可。

Example2:n=(100101101)2
这个例子和上面那个有所不同, 因为它的二进制本身就有连续 1 出现。
这个例子可以表示成如下形式:
f(100101101)=f(11111111)+f(11111)+f(1111)+f(111)
对比可以发现, 这里有两处不同:
1. 出现连续1后, 后续位都被舍去。
2. 最后不需要补上 +1.

对于第一点,考虑例子的最后几位(1101)2,由于不能出现连续1, 所以至多只需要统计到 (1011)2,再往下必有连续1出现。
这里再多加解释一个小问题。 考虑如下例子:
Example1:n=(11000000)2
按照上面所说, 分解为(1111111)2,(111111)2即可。 但是可能会有人说, 这样分完之后必然也有不需要计算的, 因为分解成为 2k1的和形式必然伴随大量的连续 1. 这一点没错,事实上只需要到 (1010101)2,(101010)2即可。

但是考虑到分解成 2k1的形式对实现数位DP更加容易, 并且两者实际上用数位DP解没有复杂度上的差异(并不会造成重复计算), 所以分解成如上形式是合理的。(可以等下回过头来考虑复杂度, 就不会困惑了)

对于第二点, 如果真的理解了为什么要补上后续为0的那一个,应该是很好理解的:对于已经出现连续0的, 后续补上为0的那一个, 实际上是加上了一个 xxxxxxx11000...000 的数, 这显然是不符合要求的。。

好了解释就到上面了。 下面是实现代码。 代码大体上很好理解, 但是其中有若干个地方就体现了刚才说的几个需要注意的细节。

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);    }};

其他细节

  1. 通篇我并没有说分解到若干个base=2k1后, 如何计算这些base的函数值。 这并不是一个难解决的问题。 事实上就是一个 Fibonacci 数列。
  2. 整体的思路其实并不难想, 但是细节的处理要十分小心。 20min想出思路, 却花了3个多小时来调细节。。。 归根到底还是对思路不够明晰, 在处理重复的时候犯了很多次错。
  3. 数位DP有明显的套路性(至少一些简单的数位DP是这样)。可以和之前我写的那篇对比。 真的是思出同门~.~

The End.

阅读全文
0 0
原创粉丝点击