【剑指Offer】整数(三)二进制 - 前n个数字二进制形式中1的个数 - JavaScript

173 阅读2分钟

嗨!~ 大家好,我是YK菌 🐷 ,一个微系前端 ✨,爱思考,爱总结,爱记录,爱分享 🏹,欢迎关注我呀 😘 ~ [微信公众号:YK菌]

持续创作,加速成长!这是我参与「掘金日新计划 · 6 月更文挑战」的第5天,点击查看活动详情

从今天继续来刷《剑指offer(专项突破版)》,原书是Java版本的,这里就是以JavaScript角度来看这些算法题。

剑指 Offer II 003. 前 n 个数字二进制中 1 的个数

给定一个非负整数 n ,请计算 0 到 n 之间的每个数字的二进制表示中 1 的个数,并输出一个数组。

分析

题目要求:给一个n,然后计算出从0n的每个数字的二进制形式的1的个数。

最朴素的解法就是使用双重循环首先将十进制数字转换为二进制,然后看每个数字中有多少1即可。

题解

/**
 * @param {number} n
 * @return {number[]}
 */
var countBits = function(n) {
    let result = []
    for(let i = 0; i < n + 1; i++){
        let binary = i.toString(2)
        let item = 0
        for(let j = 0; j < binary.length; j++){
            if(binary[j]==='1'){
                item++
            }
        }
        result.push(item)
    }
    return result
};

image.png

这里使用了双重for循环,外层是OnO(n);接下来分析内层:如果一个整数共有kk位,那么它的二进制形式中可能有OkO(k)11。在上述代码中,内部for循环中的代码对每个整数将执行OkO(k)次,因此,上述代码的时间复杂度是OnkO(nk)

我们要对解法进行优化,可以仅使用一趟扫描吗?

优化

技巧一:利用 i&(i-1)

之前博文【青训营】月影老师告诉我写好JavaScript的四大技巧——风格优先 - 掘金 (juejin.cn)中我们提到过:通过i &= i - 1 可以去掉二进制中最后面的1

由于i&(i-1)i的二进制形式中最右边的1变成0,也就是说,整数i的二进制形式中1的个数比i&(i-1)的二进制形式中1的个数多1。所以我们可以优化代码:

/**
 * @param {number} n
 * @return {number[]}
 */
var countBits = function(n) {

    let result = [0]

    for(let i = 1; i < n + 1; i++){
        result[i] = result[i &(i-1)] + 1
    }

    return result
};

该解法时间复杂度为OnO(n)

image.png

技巧二:利用 i/2

如果正整数i是一个偶数,那么i相当于将i/2左移一位的结果,因此偶数ii/2的二进制形式中1的个数是相同的。

如果i是奇数,那么i相当于将i/2左移一位之后再将最右边一位设为1的结果,因此奇数i的二进制形式中1的个数比i/21的个数多1

因此可以这样优化代码:

/**
 * @param {number} n
 * @return {number[]}
 */
var countBits = function(n) {

    let result = [0]

    for(let i = 1; i < n + 1; i++){
        // result[i] = result[~~(i / 2)] + (i%2)
        result[i] = result[i >> 1] + (i & 1)
    }

    return result
};

i>>1计算i/2,用i&1计算i%2,这是因为位运算比除法运算和求余运算更高效

该解法时间复杂度为OnO(n)

image.png

最后,欢迎关注我的专栏,和YK菌做好朋友