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: 1 <= n <= 109
這道題給了我們一個數字,讓我們求不大於這個數字的所有數字中,其二進制的表示形式中沒有連續1的個數。根據題目中的例子也不難理解題意。我們首先來考慮二進制的情況,對於1來說,有0和1兩種,對於11來說,有00,01,10,三種情況,那么有沒有規律可尋呢,其實是有的,我們可以參見這個帖子,這樣我們就可以通過DP的方法求出長度為k的二進制數的無連續1的數字個數。由於題目給我們的並不是一個二進制數的長度,而是一個二進制數,比如100,如果我們按長度為3的情況計算無連續1點個數個數,就會多計算101這種情況。所以我們的目標是要將大於num的情況去掉。下面從頭來分析代碼,首先我們要把十進制數轉為二進制數,將二進制數存在一個字符串中,並統計字符串的長度。然后我們利用這個帖子中的方法,計算該字符串長度的二進制數所有無連續1的數字個數,然后我們從倒數第二個字符開始往前遍歷這個二進制數字符串,如果當前字符和后面一個位置的字符均為1,說明我們並沒有多計算任何情況,不明白的可以帶例子來看。如果當前字符和后面一個位置的字符均為0,說明我們有多計算一些情況,就像之前舉的100這個例子,我們就多算了101這種情況。我們怎么確定多了多少種情況呢,假如給我們的數字是8,二進制為1000,我們首先按長度為4算出所有情況,共8種。仔細觀察我們十進制轉為二進制字符串的寫法,發現轉換結果跟真實的二進制數翻轉了一下,所以我們的t為"0001",那么我們從倒數第二位開始往前遍歷,到i=1時,發現有兩個連續的0出現,那么i=1這個位置上能出現1的次數,就到one數組中去找,那么我們減去1,減去的就是0101這種情況,再往前遍歷,i=0時,又發現兩個連續0,那么i=0這個位置上能出1的次數也到one數組中去找,我們再減去1,減去的是1001這種情況,參見代碼如下:
解法一:
class Solution { public: int findIntegers(int num) { int cnt = 0, n = num; string t = ""; while (n > 0) { ++cnt; t += (n & 1) ? "1" : "0"; n >>= 1; } vector<int> zero(cnt), one(cnt); zero[0] = 1; one[0] = 1; for (int i = 1; i < cnt; ++i) { zero[i] = zero[i - 1] + one[i - 1]; one[i] = zero[i - 1]; } int res = zero[cnt - 1] + one[cnt - 1]; for (int i = cnt - 2; i >= 0; --i) { if (t[i] == '1' && t[i + 1] == '1') break; if (t[i] == '0' && t[i + 1] == '0') res -= one[i]; } return res; } };
下面這種解法其實蠻有意思的,其實長度為k的二進制數字符串沒有連續的1的個數是一個斐波那契數列f(k)。比如當k=5時,二進制數的范圍是00000-11111,我們可以將其分為兩個部分,00000-01111和10000-10111,因為任何大於11000的數字都是不成立的,因為有開頭已經有了兩個連續1。而我們發現其實00000-01111就是f(4),而10000-10111就是f(3),所以f(5) = f(4) + f(3),這就是一個斐波那契數列啦。那么我們要做的首先就是建立一個這個數組,方便之后直接查值。我們從給定數字的最高位開始遍歷,如果某一位是1,后面有k位,就加上f(k),因為如果我們把當前位變成0,那么后面k位就可以直接從斐波那契數列中取值了。然后標記pre為1,再往下遍歷,如果遇到0位,則pre標記為0。如果當前位是1,pre也是1,那么直接返回結果。最后循環退出后我們要加上數字本身這種情況,參見代碼如下:
解法二:
class Solution { public: int findIntegers(int num) { int res = 0, k = 31, pre = 0; vector<int> f(32, 0); f[0] = 1; f[1] = 2; for (int i = 2; i < 31; ++i) { f[i] = f[i - 2] + f[i - 1]; } while (k >= 0) { if (num & (1 << k)) { res += f[k]; if (pre) return res; pre = 1; } else pre = 0; --k; } return res + 1; } };
類似題目:
參考資料:
https://discuss.leetcode.com/topic/90571/java-solution-dp
https://discuss.leetcode.com/topic/90639/c-non-dp-o-32-fibonacci-solution
https://discuss.leetcode.com/topic/90671/java-o-1-time-o-1-space-dp-solution
http://www.geeksforgeeks.org/count-number-binary-strings-without-consecutive-1s/