Leetcode每日一题-338.比特位计数

338. 比特位计数

题目链接
题目描述

给定一个非负整数 num。对于 0 ≤ i ≤ num 范围中的每个数字 i ,计算其二进制数中的 1 的数目并将它们作为数组返回。

示例1

输入: 2
输出: [0,1,1]

示例2

输入: 5
输出: [0,1,1,2,1,2]

进阶提示

  • 给出时间复杂度为O(n*sizeof(integer))的解答非常容易。但你可以在线性时间O(n)内用一趟扫描做到吗?
  • 要求算法的空间复杂度为O(n)。
  • 你能进一步完善解法吗?要求在C++或任何其他语言中不使用任何内置函数(如 C++ 中的 __builtin_popcount)来执行此操作。

分析

要在一次线性扫描的时间内解决问题,也就是说直接一次性往数组内遍历赋值。

所以需要找寻赋值规律。

1. 动态规划-低位有效判断

我们先来看这样一个例子

  • 1111,1110他们的bits取决于

    111的bits + 末位是否为1

  • 1101,1100他们的bits取决于

    110的bits + 末位是否为1

很容易可以得到递推公式:

bits[i] = bits[i >> 1] + (i & 1)

公式解释:

  • bits[i] 表示第i个数的bit为1的个数
  • i >> 1,表示i右移1位 可以把1110,1111变为 111,简单理解就是舍掉最低1位
  • i & 1,与运算。将第i个数与1作与运算,如果末位为1,则i & 1的结果就是1,否则0。

代码

func countBits(num int) []int {
    
    
	bits := make([]int, num+1)
	for i := 1; i <= num; i++ {
    
    
		bits[i] = bits[i>>1] + (i & 1)
	}
	return bits
}

2. 动态规划-设置最低有效位

1 & 0 = 001 & 000 = 0 = 000
2 & 1 = 010 & 001 = 0 = 000
3 & 2 = 011 & 010 = 2 = 010
4 & 3 = 100 & 011 = 0 = 000
5 & 4 = 101 & 100 = 4 = 100
6 & 5 = 110 & 101 = 4 = 100
7 & 6 = 111 & 110 = 6 = 110
8 & 7 = 1000 & 0111 = 0 = 0000
不难看出一个规律
n & n-1 得到一个数,这个数就是将n 的最末位的1变成0的结果

所以我们可以得到这样一个递推公式:

bits[i] = bits[i & [i-1]] + 1

公式解释:

  • i & i-1 将i的最末位1 变成了0,得到数m
  • 这个m一定在i之前,因为最末位1变成了0,之前的位不变
  • bits[i] = bits[m] + 1,因为末位1变成0,第i个相比较第m个,i的个数少1

代码

func countBits(num int) []int {
    
    
	bits := make([]int, num+1)
	for i := 1; i <= num; i++ {
    
    
		bits[i] = bits[i&(i-1)] + 1
	}
	return bits
}

猜你喜欢

转载自blog.csdn.net/qq_38557583/article/details/115293726