338. 比特位计数 (动态规划)

力扣题解《卑鄙的异乡人,巧妙的动态规划(图解过程))》

方法一:暴力计算

思路

  • 0num 遍历,将每一个数字转换成二进制,转换同时计算包含几个 1
  • 这里采用了模拟短除的方法,对数字不断做除法,保存余数,并将数字用商替换。

复杂度分析

  • 时间复杂度 O(N*len(N)):遍历需要 O(N),将每个数转二进制需要 O(len(N)),其中 len(N) 表示一个十进制数N,转换为二进制后的长度。

  • 空间复杂度 O(N):最终返回的列表需要占用 O(N) 空间

快乐小视频




具体代码

class Solution {
public:
    // 搞一个新的函数,来将10进制转换成2进制数。
    int trans2bit(int num){
        int ans = 0;
        // 直到num全部转换完成,才退出循环
        while(num > 0){
            // 判断当前位是0还是1
            // 无论是0还是1都添加到答案中
            ans += num % 2;
            // 不断除以2,计算下一位
            num /= 2;
        }
        // 返回二进制表示时,1的个数
        return ans;
    }
    vector<int> countBits(int num) {
        vector<int> ans(num + 1);
        // 0的结果就是0,因此从1开始循环
        for(int i = 1; i <= num; i++){
            ans[i] = trans2bit(i);
        }
        return ans;
    }
};

方法二:简单的动态规划

思路

  • 这是一道很模板的线性动态规划。个人认为线性动态规划就是前人栽树后人乘凉。每个新结果的诞生,都可以依赖于前面已经确定的结果线性变化得出

  • 状态定义:

    • dp[i] 表示数字 i 对应的二进制表示中 1 的数量。
  • 状态转移:

    • i 为偶数时:由二进制表示规则我们可以知道,二进制表示的最后一位为0,也就是说删去这个0,对 dp[i] 也不会有任何影响。因此 dp[i] = dp[i / 2]
    • i 为奇数时:由二进制表示规则我们可以知道,二进制表示的最后一位为1,也就是说数字 i 因为这个1,比数字 (i-1)/2 最终结果多1。因此 dp[i] = dp[(i - 1) / 2] + 1
  • 转移方程
    dp[i] = \left\{\begin{matrix}dp[i/2] \\ dp[(i-1)/2]+1 \end{matrix}\right. \begin{matrix}(i\%2=0) \\ (i\%2=1) \end{matrix}

  • 初始状态:

    • dp[i] 所有元素置0,当计算后才进行赋值。
  • 返回值:

    • 返回 dp 列表。

复杂度分析

  • 时间复杂度 O(N):遍历 dp 数组需要 O(N),计算每个 dp[i] 需要 O(1)

  • 空间复杂度 O(N):最终返回的列表需要占用 O(N) 空间

快乐小视频



具体代码

class Solution {
public:
    vector<int> countBits(int num) {
        // 预先开好空间
        vector<int> dp(num + 1);

        // 同样不需要从0开始,因为dp[0] = 0
        for(int i = 1; i <= num; i++){
            if(i % 2 == 0){ // 偶数
                dp[i] = dp[i / 2];
            }
            else{ // 奇数
                dp[i] = dp[(i - 1) / 2] + 1;
            }
        }
        
        // dp数组即为所求
        return dp;
    }
};

方法三:卑鄙的异乡人,巧妙的动态规划

思路

  • 这里用到了两个位运算操作:

    1. x & 1 :用于判断奇偶性
    2. x & (x-1):用于删除二进制右数第一个 1

    原理证明1:x1 做按位与,相当于与 000...00001 做按位与,当 x 为奇数时,x 的二进制末尾为 1 ,此时得到的按位与结果为 1, 否则的话按位与结果为 0

    原理证明2:xx-1 做按位与。假设 x 形如 10...1000 的形式。则 x-1 形如 10...0111 ,右数第一个 1 被借位,变为 0。按位与后,会发现做到了删除右数第一个 1 的效果。

  • 使用位运算x & 1,我们可以替换方法一和方法二中,判断奇偶的操作。即将所有的x % 2 替换为x & 1。但需要注意的是,位运算的优先级较低,判断时需要加括号(x & 1) == 0

  • 使用位运算x & (x-1),我们可以优化方法一中的循环条件,每次删除最右侧的1,直到数字变为0。

  • 使用位运算x & (x-1),我们还可以优化方法二中的动态规划。由于该位运算的操作效果是删除二进制右数第一个 1,则显然处理后的结果 y 比原结果 x 要小,且二者的关系满足 dp[x] = dp[y] + 1

  • 状态定义:

    • dp[i] 表示数字 i 对应的二进制表示中 1 的数量。
  • 状态转移:

    • i 为0时:显然dp[0]=0
    • i 非0时:按位与之后的结果,比原数字的二进制值的1的数目少1。因此 dp[i] = dp[i & i - 1)] + 1
  • 转移方程

    • dp[i] = dp[i & (i-1)] + 1
  • 初始状态:

    • dp[i] 所有元素置0,当计算后才进行赋值。
  • 返回值:

    • 返回 dp 列表。

复杂度分析

  • 时间复杂度 O(N):遍历 dp 数组需要 O(N),计算每个 dp[i] 需要 O(1)

  • 空间复杂度 O(N):最终返回的列表需要占用 O(N) 空间

快乐小视频




具体代码

  • 方法一优化
class Solution {
public:
    // 搞一个新的函数,计算数字num二进制表示有几个1。
    int trans2bit(int num){
        int ans = 0;
        // 直到num位0,才退出循环
        while(num > 0){
            // num不为零,说明至少还有一个1
            ans++;
            // 删去num最右侧的1
            num &= num - 1;
        }
        // 返回二进制表示时,1的个数
        return ans;
    }
    vector<int> countBits(int num) {
        vector<int> ans(num + 1);
        // 0的结果就是0,因此从1开始循环
        for(int i = 1; i <= num; i++){
            ans[i] = trans2bit(i);
        }
        return ans;
    }
};
  • 方法二优化
class Solution {
public:
    vector<int> countBits(int num) {
        // 预先开好空间
        vector<int> dp(num + 1);

        // 同样不需要从0开始,因为dp[0] = 0
        for(int i = 1; i <= num; i++){
            // 转移方程:
            dp[i] = dp[i & (i - 1)] + 1;
        }
        
        // dp数组即为所求
        return dp;
    }
};
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
平台声明:文章内容(如有图片或视频亦包括在内)由作者上传并发布,文章内容仅代表作者本人观点,简书系信息发布平台,仅提供信息存储服务。
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 230,182评论 6 543
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 99,489评论 3 429
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 178,290评论 0 383
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 63,776评论 1 317
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 72,510评论 6 412
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 55,866评论 1 328
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 43,860评论 3 447
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 43,036评论 0 290
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 49,585评论 1 336
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 41,331评论 3 358
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 43,536评论 1 374
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 39,058评论 5 363
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 44,754评论 3 349
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 35,154评论 0 28
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 36,469评论 1 295
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 52,273评论 3 399
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 48,505评论 2 379

推荐阅读更多精彩内容