338. 比特位计数-M

338. 比特位计数-M

给定一个非负整数 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. 最简单的办法是循环计数,或者将最后一个1改为0,计数到这个数变为0,见《191. 位1的个数》
  2. dp:《191. 位1的个数》最后有个问题,就是多次调用这个函数,有啥优化的?其实就变为《338. 比特位计数》这道题了,这道题从0求到num,而单次求num,用末尾1改0的办法,每回都要对num-=1,这样求num必然会有中间过程num-1,所以我们就可以用备忘录的方式,把之前的记录起来,求num时,只需要在num-1的结果上加个1就好了。
  3. dp: 还有两种是根据最高位和最低位的关系来设计的。

338. 比特位计数-M_第1张图片

末尾1改0,num&=(num-1),多次调用,加上备忘录,因为从0求道num,中间已经计算过了,无序再计算

/*
执行用时 :2172 ms, 在所有 Go 提交中击败了81.13%的用户
内存消耗 :27.7 MB, 在所有 Go 提交中击败了24.24%的用户
*/
func countBits(num int) []int {
	ret:=make([]int,num+1)
	for i:=1;i<=num;i++{
        tmp:=(i-1)&i
        ret[i]=ret[tmp]+1 //这样O(N)就求完0~num的所有计数
	}
	return ret
}

循环取余计算法:

/*
执行用时 :2284 ms, 在所有 Go 提交中击败了67.92%的用户
内存消耗 :34.1 MB, 在所有 Go 提交中击败了6.06%的用户
*/
func countBits(num int) []int {
    ret:=make([]int,1)
    for i:=1;i<=num;i++{
        ret=append(ret,counts(i))
    }
    return ret
}
func counts(num int)int{
    cnt:=0
    for num>0{
        if num%2==1{
            cnt+=1
        }
        num>>=1
    }
    return cnt
}

//末尾1改0法
func counts_0(num int)int{
    cnt:=0
    for num>0{
        cnt+=1
        num&=(num-1)
    }
    return cnt
}

加了个map记录,仅仅略微提高,没啥用

/*
执行用时 :2244 ms, 在所有 Go 提交中击败了73.58%的用户
内存消耗 :25.3 MB, 在所有 Go 提交中击败了33.33%的用户
*/
var mp map[int]int
func countBits(num int) []int {
	mp:=make(map[int]int)
	for i:=0;i<16;i++{
		mp[i]=counts(i)
	}
	ret:=make([]int,1)
	for i:=1;i<=num;i++{
		tmp,cnt:=i,0
		for tmp>0{
			cnt+=mp[tmp%16]
			tmp>>=4
		}
		ret=append(ret,cnt)
	}
	return ret
}
func counts(num int)int{
	cnt:=0
	for num>0{
		if num%2==1{
			cnt+=1
		}
		num >>=1
	}
	return cnt
}

你可能感兴趣的:(338. 比特位计数-M)