leetcode600 二进制不含连续1的非负整数

leetcode600 二进制不含连续1的非负整数

给定一个正整数 n,找出小于或等于 n 的非负整数中,其二进制表示不包含连续的1的个数。

输入: 5
输出: 5
解释:
下面是带有相应二进制表示的非负整数<= 5:
0 : 0
1 : 1
2 : 10
3 : 11
4 : 100
5 : 101
其中,只有整数3违反规则(有两个连续的1),其他5个满足规则。

说明: 1 <= n <= 10^9

可以看到n的大小最高到达 1 0 9 10^9 109,肯定没法去暴力求解。
写这个问题之前我们可以先思考一下另一个问题,给定一个长度l,二进制长度为n且不包含连续的1的数个数。
我们可以使用动态规划的方法,构建一个数组 f [ i ] [ j ] f[i][j] f[i][j]表示长度为 i i i,最高位为j时的合法数的数量。
计算 f [ i ] [ j ] f[i][j] f[i][j]时,

  • 假设 j j j为0,那么它剩下 i − 1 i-1 i1位的最高位无论是0还是1都满足条件,所以有
    f [ i ] [ 0 ] = f [ i − 1 ] [ 0 ] + f [ i − 1 ] [ 1 ] f[i][0]=f[i-1][0]+f[i-1][1] f[i][0]=f[i1][0]+f[i1][1]
  • 假设 j j j位1,那么他剩下 i − 1 i-1 i1位的最高位必须是 0 0 0,否则会有连续的 1 1 1了,有
    f [ i ] [ 1 ] = f [ i − 1 ] [ 0 ] f[i][1]=f[i-1][0] f[i][1]=f[i1][0]

由此我们得到长度为 i i i的满足条件的合法数的数量。

接下来在解决该问题,找出小于等于 n n n的合法数的数量。
首先我们得到 n n n的二进制表示,假设长度为 i i i,设置 c u r cur cur为他的当前最高位

  • 假设它的当前最高位为1,那么长度为 i i i的最高位为0的合法数一定满足小于等于 n n n的条件,所以我们把这个数量即 f [ i ] [ 0 ] f[i][0] f[i][0]累加到最终结果 a n s ans ans中去。对于长度为 i i i最高位为1的合法数,我们不能确定它是不是小于 n n n,所以不能直接把 f [ i ] [ 1 ] f[i][1] f[i][1]加到结果中去。此时我们已经把所有最高位为0的可能的数都计算 a n s ans ans中去了,那么我们接下来考虑的就是默认最高位为1的数了。
  • 假设它当前最高位是0,那么长度为i最高位是1的合法数肯定不能加进去了,而最高位是0的合法数也不一定都能够加进去,但是最高位一定只能是0了,和上面的最高位是1是一样的考虑,那么我们接下来考虑的就是默认最高位为0的数了。
  • 无论是0是1,最后最高位都会默认位一个确定的数,既然他是个常数,我们直接忽略,将最高位 c u r cur cur置为 c u r − 1 cur-1 cur1,再执行前两块内容。直到 c u r cur cur为0或者同时出现两个1(前面不是每块执行完都默认了最高位是0或1了嘛)。

我们以当前最高位为1为例,把 f [ i ] [ 0 ] f[i][0] f[i][0]全算上了, f [ i ] [ 1 ] f[i][1] f[i][1]我们没法判断哪些该算进去,假设下一个最高位还是1,那么 f [ i − 1 ] [ 0 ] f[i-1][0] f[i1][0]的数就全能算上了,而我们又知道 f [ i ] [ 1 ] = f [ i − 1 ] [ 0 ] f[i][1]=f[i-1][0] f[i][1]=f[i1][0],那么也就是说所有情况都被算进去了(因为剩下最多就 f [ i ] [ 1 ] f[i][1] f[i][1]种),同时又到达了出现连续两个1的终止情况,刚刚好。如果下一个最高位是0,从上面我们知道满足条件的合法数在 f [ i − 1 ] [ 0 ] f[i-1][0] f[i1][0]中,然后我们又要再去看下一个最高位,直到最高位是1(位置为 c u r cur cur),我们才可以确定的把 f [ c u r ] [ 0 ] f[cur][0] f[cur][0]加进去,以此类推吧。
还有一个注意点就是边界条件,因为循环条件的原因,当你因为 c u r cur cur变为0而退出循环,会少加一个 f [ 1 ] [ 0 ] f[1][0] f[1][0],所以此时 a n s ans ans要加1

class Solution {
public:
    int findIntegers(int n) {
        //int是32位,稍微取大一点40
        int N = 40;
        //f[i][j],长度为i,最高位是j时的合法数的数量,
        int f[N][2];
        //长度为1的时候,如果j是0,满足条件的就只有0,如果j是1,满足条件的就1
        f[1][0] = 1, f[1][1] = 1;
        //模拟一下长度为2的时候,
        //如果j是0,满足条件的就有00,01,就是上面长度为1,j为1和0之和,
        //如果j是1,最高位为1,他的前一位必须为0,那么就是长度为1,j为0的情况
        for(int i = 2; i < N; i++){
            f[i][0] = f[i - 1][1] + f[i - 1][0];
            //f[i][1] = f[i][0] + f[i - 1][0];
            f[i][1] = f[i - 1][0];
        }
        int len = 0;
        while(n >> len){
            len ++;
        }
        int ans = 0;
        int cur, pre;
        while(len){
            len--;
            cur = (n >> len) & 1;
            cout << cur << endl;
            //如果是1,那么f中长度为len+1最高位是0对应的合法数都可以加进去,而如果是0,因为不能确定是否小于n,还得看下面的迭代
            if(cur == 1) ans += f[len + 1][0];
            //如果连续出现两次1,就不满足题目条件了,上一次循环我们已经把最高位(也就是这一次的前一位)为0的情况全加进去了,现在考虑的时候默认前一位为1了
            if(cur == 1 && pre == 1) break;
            //如果是最后一位,如果是1,那么上面就加了2,如果是0,因为后面不迭代了,得把对应的f[1][0]加进去,就是1。
            if(len == 0) ans ++;
            pre = cur;
        }
        return ans;
    }
};
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值