二进制中 1 的个数(C++ 和 Python 实现)

(说明:本博客中的题目题目详细说明参考代码均摘自 “何海涛《剑指Offer:名企面试官精讲典型编程题》2012年”)

题目

  请实现一个函数,输入一个整数,输出该数二进制表示中 1 的个数。例如把 9 表示成二进制是 1001,有 2 位是 1。因此如果输入 9,该函数输出是 2。

二进制中 1 的个数(C++ 和 Python 实现)

算法设计思想

  计算一个整数的二进制表示中 1 的个数有多种算法。本文主要介绍两种算法,按位与运算算法和快速算法,更多算法,可以查看网友 zdd 的博文 “算法-求二进制数中1的个数”。

  按位与运算算法思想很简单,即对整数的二进制表示的每一位与 1 求与,所得结果为非 0 的个数,即为一个整数二进制表示中 1 的个数,这种算法所需的移位次数至少为整数的二进制表示中,数字 1 所在的最高位的位次(例如 0b0101,高位和低位所在的位次分别为 2 和 0),不够高效;

  快速算法,则不采用移位操作,而是用整数 i 与这个整数减 1 的值 i - 1,按位求与,如此可以消除,整数的二进制表示中,最低位的 1 。整数的二进制表示有几个 1,则只需计算几次。在 C/C++ 实现时,负整数溢出后为最大整数,但 Python 数值类型(Numeric Type)不会出现溢出的情况,所以,此时,还需要对边界值进行限定。

注,整数的二进制表示方式和移位操作处理方式:
1)整数的二进制表示方式:在计算机中,整数的二进制表示中,最高位为符号位。最高位为 0 时,表示正数; 最高位为 1 时表示负数。
2)对整数的移位操作:
当整数是负数时,右移时,最低位丢弃最高位补 1;  左移时,最高位丢弃,最低位补 0。
当整数是正数时,右移时,最低位丢弃,最高位补 0;  左移时,最高位丢弃,最低位补 0 。

 

C++ 实现

/*
* Author: klchang
* Date: 2017.12.16
* Description: Compute the number of 1 in the binary representation of an integer.
*/

#include <iostream>

#define INT_BITS 32

// Generic method: bitwise and operation with a number that has only one 1 in binary.
int numberOf_1_InBinary_Generic(int i)
{
    int count = 0;
    int shiftCount = 0;

    while (i && shiftCount < INT_BITS)
    {
        if (i & 1) {
            ++ count;
        }
        i = i >> 1;
        ++ shiftCount;
    }

    return count;
}

// Fast method: bitwise and operation between integer i and (i-1).
int numberOf_1_InBinary_Fast(int i)
{
    int count = 0;
    while (i)
    {
        std::cout << "iter " << count << ": " << i << std::endl;
        i = i & (i - 1);
        count ++;
    }

    return count;
}

void unitest()
{
    int data[] = {-5, 0, 5};

    std::cout << "---------------------- Generic Method -----------------------"  << std::endl;
    for (int i = 0; i < 3; ++i)
        std::cout << "The number of 1 in the binary representation of " << data[i] << " is "
                  << numberOf_1_InBinary_Generic(data[i]) << ".
" << std::endl;

    std::cout << "----------------------- Fast Method --------------------------"  << std::endl;
    for (int i = 0; i < 3; ++i)
        std::cout << "The number of 1 in the binary representation of " << data[i] << " is "
                  << numberOf_1_InBinary_Fast(data[i]) << ".
" << std::endl;
}

int main()
{
    unitest();

    return 0;
}

 

Python 实现

原理

  为了更好的理解这个问题在 Python 中的实现,先简单介绍 Python 数值类型,需要注意 Python 2 和 Python 3 的数值类型是有些区别的。Python 2 的数值类型有 4 种类型,即 int, long, float 和 complex 。而在 Python 3 中,int 和 long 类型已经整合到一起,成为新的 int 类型,也就是说,Python 3 中,只有 3 种类型,即 int, float 和 complex。 对 bool 类型,在 Python 2 中,其是普通整型 int (at least 32 bits of precision)的子类型;在 Python 3 中,其是 int 类型(unlimited precision)的子类型。

  Python 2 的数值类型 int 是普通整型,是有范围的,可以通过 sys.maxint 获取其最大值,至少 32 bit。当 Python 2 程序中的整数值超出范围后,自动转换为 long 类型,而 long 类型是没有范围限制的,即 unlimited precision。在 Python 3 中,这两种类型被统一起来,表示为 int 类型,与 Python 2 的数值类型 long 相同,没有范围限定(unlimited precision)。也就是说,在 Python 中,整型数是没有溢出的(overflow)。在 Python 程序中,当对一个负整数与其减 1 后的值按位求与,若结果为 0 退出,循环执行此过程。由于整型数可以有无限的数值精度,其结果永远不会是 0,如此编程,在 Python 中,只会造成死循环。而在 C/C++ 中,整数(32 bit)的范围是 [ - 2147483648, 2147483647 ],与此相对, Python 2 中的 long 类型和 Python 3 中 int 类型,如果不指定整型数的位数,是没有范围限制的。

注:数值精度(numeric precision)是指数值中的数字位数(the number of digits);数值尺度(numeric scale)是指数值中的小数位数(the number of digits after the decimal point)。例如 123.45 可表示为 decimal(p = 5, s = 2),即 10进制,数值精度为 5, 数值尺度为 2。

实现

#!/usr/bin/python
# -*- coding: utf8 -*-
"""
# Author: klchang
# Date: 2017.12.16
# Description: Compute the number of 1 in the binary representation of an integer.
"""

INT_BITS = 32 
MAX_INT = (1 << (INT_BITS - 1)) - 1  # Maximum Integer for INT_BITS


# Generic method: bitwise and operation with a number that has only one 1 in binary.
def number_of_1_in_binary_generic(num):
    
    count, bit = 0, 1
    while num and bit <= MAX_INT + 1:
        if bit & num:
            count += 1
            num -= bit
        bit = bit << 1

    return count
        

# Fast method: bitwise and operation between integer num and (num-1).
def number_of_1_in_binary_fast(num):
    count = 0
    while num:
        if num < - MAX_INT - 1 or num > MAX_INT:
            break
        print("iter %d: %d" % (count, num))
        count += 1
        num = num & (num-1)
        
    return count


def unitest():
    nums = [-5, 0, 5]
    # Generic Method
    print("-" * 30 + " Generic Method " + "-" * 30)
    for n in nums:
        print("The number of 1 in the binary representation of %d is %d.
" % (n, number_of_1_in_binary_generic(n)))
    # Fast Method
    print('
' + "-" * 30 + " Fast Method " + "-" * 30)
    for n in nums:
        print("The number of 1 in the binary representation of %d is %d.
" % (n, number_of_1_in_binary_fast(n)))

if __name__ == '__main__':
    unitest()

参考代码

 1. targetver.h

#pragma once

// The following macros define the minimum required platform.  The minimum required platform
// is the earliest version of Windows, Internet Explorer etc. that has the necessary features to run 
// your application.  The macros work by enabling all features available on platform versions up to and 
// including the version specified.

// Modify the following defines if you have to target a platform prior to the ones specified below.
// Refer to MSDN for the latest info on corresponding values for different platforms.
#ifndef _WIN32_WINNT            // Specifies that the minimum required platform is Windows Vista.
#define _WIN32_WINNT 0x0600     // Change this to the appropriate value to target other versions of Windows.
#endif

2. stdafx.h

// stdafx.h : include file for standard system include files,
// or project specific include files that are used frequently, but
// are changed infrequently
//

#pragma once

#include "targetver.h"

#include <stdio.h>
#include <tchar.h>



// TODO: reference additional headers your program requires here

3. stdafx.cpp

// stdafx.cpp : source file that includes just the standard includes
// NumberOf1.pch will be the pre-compiled header
// stdafx.obj will contain the pre-compiled type information

#include "stdafx.h"

// TODO: reference any additional headers you need in STDAFX.H
// and not in this file

4. NumberOf1.cpp

// NumberOf1.cpp : Defines the entry point for the console application.
//

// 《剑指Offer——名企面试官精讲典型编程题》代码
// 著作权所有者:何海涛

#include "stdafx.h"
#include <string.h>
#include <stdlib.h>

// ====================方法一====================
int NumberOf1(unsigned int n);

int NumberOf1Between1AndN_Solution1(unsigned int n)
{
    int number = 0;

    for(unsigned int i = 1; i <= n; ++ i)
        number += NumberOf1(i);

    return number;
}

int NumberOf1(unsigned int n)
{
    int number = 0;
    while(n)
    {
        if(n % 10 == 1)
            number ++;

        n = n / 10;
    }

    return number;
}

// ====================方法二====================
int NumberOf1(const char* strN);
int PowerBase10(unsigned int n);

int NumberOf1Between1AndN_Solution2(int n)
{
    if(n <= 0)
        return 0;

    char strN[50];
    sprintf(strN, "%d", n);

    return NumberOf1(strN);
}

int NumberOf1(const char* strN)
{
    if(!strN || *strN < '0' || *strN > '9' || *strN == '