数位DP问题

这篇博客介绍了如何使用数位动态规划(DP)方法解决一类计算机科学中的问题,即在不超过给定正整数n的非负整数中,找到不含有连续1的二进制表示的数的数量。文章通过示例详细阐述了暴力解法的局限性和DP解法的思路,包括状态转移方程的建立,并提供了相应的代码实现。该问题涉及到位操作和预处理策略,是算法设计和复杂度优化的一个实例。
摘要由CSDN通过智能技术生成

数位DP问题

数位DP问题一般都是问在 小于等于 n 的数里面, 有多少个数是满足条件的, 当然问题也可以转换为问一个区间里面[a, b] 是满足某种条件的, 当然对于这种区间的问题, 我们可以将其转化为前缀和问题, 例如转化为求 0 ~ b里满足的个数 - 0 ~ a - 1 里面满足条件的个数, 一般做法分两步:

(1) 预处理 (一般情况)

(2) 按位进行处理

例题

题目链接: 600. 不含连续1的非负整数

题目描述:

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

示例:

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

说明: 1 ≤ \le n ≤ \le 1 0 9 10^9 109

思路分析

首先思考暴力做法, 即从 n 开始一直到 0, 判断每一个数是否满足条件, 这里判断可以利用位操作在 O ( 1 ) O(1) O(1)的时间内判断出来, 但是数量级 1 0 9 10^9 109 必然超时

// 超时的暴力做法
class Solution 
{
public:
    void test(int n)
    {
        cout << n << "   "; 
        for(int i = 31; ~i; --i) cout << (n >> i & 1); 
        cout << endl; 
    }

    bool fun(int n)
    {
        int num = n; 
        while(n)   
        {
            int t = n & -n; 
            n -= t; 
            if(n) 
            {
                int t2 = n & -n; 
                if(t2 == t * 2) return true; 
            }
        }
        return false; 
    }

    int findIntegers(int n) 
    {
        int num = n; 
        int res = 0; 
        while(~num) res += fun(num--); 
        return n + 1 - res; 
    }
};

数位DP分析:

在这里插入图片描述
上面例子中我们假设给我们一个非负整数n其二进制表示为 10100110, 合法的数字的规则是小于等于n的数, 且这个数的二进制表示中不能有连续的1(具体见示例), 故我们这里从严格小于n的数开始找起, n这个数本身我们可以最后判断, 首先这里n的最高位为1, 那么此时的位数总共为8位,那么此时二进制表示为8位且最高位为0的数必定是小于n的数, 其中符合条件的数, 我们从预处理条件可以知道为 f(8, 0); 接着假定此时最高为1, 接着判断第7位, 由于此时第八位已经为1, 如果要合法那么第七位只能为0, 接着判断第六位, 此时如果为0, 那么合法个数为f(6, 0); 接着假定第六位为1,继续判断第五位的情况, 同样要保持合法, 第五位只能为0, 判断第四位,由于n的第四位已经为0了,不能再比其更小了, 故第四位为0的情况, 判断第三位, 假定第三位为0, 则合法个数为 f(3, 0), 假定第三位为1的情况下, 第二位假设为0, 此时合法个数为 f(2, 0), 此时第二位为1就不合法, 故如果原本的n中如果出现连续的1那么后面就可以不用判断了.

所以可以得出这里的状态转移方程:
注意这里的f(x, 0/1) 仅表示共 x 位的且最高位为0, 1的合法个数(不含连续1的个数)  
f(i, 0) = f(i - 1, 0) + f(i - 1, 1);  // 最高位为0, 所以次高为可以为 0 或者 1
f(i, 1) = f(i - 1, 0);  // 最高位为1, 次高位仅能为0, 否则就构成连续的1

class Solution 
{
public:
    int f[40][2]; 
    int findIntegers(int n) 
    {
        memset(f, 0, sizeof f); 
        vector<int> num; 
        while(n) num.push_back(n % 2), n /= 2; 
        f[1][0] = f[1][1] = 1; 

        for(int i = 2; i <= num.size(); ++i) 
        {
            f[i][0] = f[i - 1][0] + f[i - 1][1]; 
            f[i][1] = f[i - 1][0]; 
        }
        
        int res = 0; 
        for(int i = num.size(), last = 0; i; --i) 
        {
            int n = num[i - 1]; 
            if(n) 
            {
                res += f[i][0]; 
                if(last == 1) return res;  // 出现连续的1, 此时继续下去组成的必然是不合法的数字, 所以直接返回
            }
            last = n; 
        }
        return res + 1; 
    }
};
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值