求前n个数字二进制形式中1的个数---几种思路,层层递进(剑指offer-03)(位运算)

原题链接

题目描述

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

示例1

输入: n = 2
输出: [0,1,1]
解释:
0 --> 0
1 --> 1
2 --> 10

示例2

输入: n = 5
输出: [0,1,1,2,1,2]
解释:
0 --> 0
1 --> 1
2 --> 10
3 --> 11
4 --> 100
5 --> 101

第0种思路

最笨的方法, 用循环先将每个十进制整数先转换为二进制的形式,然后再用循环遍历这个二进制字符串,求出1的个数。再同理求出前n个数字种1的个数各是多少,时间复杂度O(n^2),代码略。

第1种思路

我最先想到的是这个思路,将每个数字和1进行与操作&,这样如果是偶数结果必为0,如果是奇数结果必为1,而且只有奇数的二进制末尾是1,所以如果是1的话就累加到sum里,然后再将这个数字右移>>,继续判断下一位。假设一个整数有k位,那么二进制形式可能有k个1,那么时间复杂度就是O(nk)。

class Solution {
    public int[] countBits(int n) {
        n = n + 1;
        int[] arr = new int[n];

        //偶数
        for(int i = 0; i < n; i += 2){
            int temp = i;
            int sum = 0;
            while(temp > 0){
                if((temp&1) == 1){
                    sum++;
                }
                temp = temp >> 1;
            }
            arr[i] = sum;
        }
        for(int i = 1;  i < n; i += 2){
            arr[i] = arr[i - 1] + 1;
        }
        return arr;
    }
}

第2种思路

i&(i - 1)可以将整数 i 的二进制形式中最右侧的1变为0。
解释一下:i - 1可以将 i 中最右侧的1变为0,然后该位右侧所有的0变为1,因为减1即减去00000001,i需要从末尾开始不断向前面借位。然后i&(i - 1),因为最右侧的1变为0,最右侧的0全变为1,这两部分都与原数 i 刚好相反,0&1必为0,所以最终结果就是最右侧的1变为0。
那么知道了这个操作,我们要求一个数二进制形式中1的个数,只需要每一次都将最右侧的1变为0,即最终变为0,然后看看进行了多少次操作就可以。假设一个整数有k位,那么二进制形式可能有k个1,那么时间复杂度就是O(nk)。

class Solution {
    public int[] countBits(int n) {
        n = n + 1;
        int[] arr = new int[n];
        for(int i = 0; i < n; i++){
            int j = i;
            while(j > 0){
                arr[i]++;
                j = j & (j - 1);
            }
        }
        return arr;
    }
}

第3种思路

由第2种思路我们发现,i&(i - 1)将 i 最右侧的1变为0,即比 i 少了一个1,
所以我们可以直接由前面已经求出的来算后面。这种算法无论这个整数中有多少个1,我们都是直接求出来的,即O(1),那么求n个数复杂度就是O(n)。

class Solution {
    public int[] countBits(int n) {
        n = n + 1;
        int[] arr = new int[n];
        for(int i = 1; i < n; i++){
            arr[i] = arr[i & (i - 1)] + 1;
        }
        return arr;
    }
}

第4种思路

对于一个整数 i 而言,如果它是偶数,那么相当于由 i / 2 左移得来的,并且又因为偶数的末尾一定是0,不会丢失1,所以 i 和 i / 2二进制中1的个数是相同的;如果 i 是奇数,那么可以看作由 i / 2左移后,将末尾的0再变成1得来的,因为奇数末尾一定是1,右移会丢失掉最后一个1,所以要加回来,时间复杂度同第3种思路,为O(n)。

class Solution {
    public int[] countBits(int n) {
        n = n + 1;
        int[] arr = new int[n];
        for(int i = 1; i < n; i++){
            arr[i] = arr[i / 2] + i % 2;
        }
        return arr;
    }
}

当然,为了更高效,我们可以将 i / 2改成 i >> 1,将 i % 2改为 i & 1,效果一样,但是位运算更接近底层,所以效率更高。

class Solution {
    public int[] countBits(int n) {
        n = n + 1;
        int[] arr = new int[n];
        for(int i = 1; i < n; i++){
            arr[i] = arr[i >> 1] + (i & 1);
        }
        return arr;
    }
}
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值