计算段中的个(二进制)

时间:2018-11-21 00:47:24

标签: c algorithm math binary

我现在正在处理一个问题,如下所示:

有两个数字x1和x2和x2> x1。

例如x1 = 5;和x2 = 10;

我必须找到二进制表示形式中x1和x2之和。

5 = 101   => 2 ones
6 = 110   => 2 ones
7 = 111   => 3 ones
8 = 1000  => 1 one
9 = 1001  => 2 ones
10= 1010  => 2 ones
so the sum will be 
sum = 2 + 2 + 3 + 1 + 2 + 2 = 12 ones;

所以我设法做到了即使没有将数字转换为二进制数也浪费了执行时间。

我注意到,2^n中每个n >= 1中的个数为1 例如:2^1 => num of ones is 1 2^2 => 1 2^15 => 1

如果需要,您可以在这里进行测试:https://www.rapidtables.com/convert/number/decimal-to-binary.html?x=191

在每个2^n and 2^(n+1)之间有连续的数字,如您在本示例中所看到的:

      num              number of ones
2^4 = 16                      1
      17                      2
      18                      2
      19                      3
      20                      2
      21                      3
      22                      3
      23                      4
      24                      2
      25                      3
      26                      3
      27                      4
      28                      3
      29                      4
      30                      4
      31                      5
2^5 = 32                      1

所以我做了一段代码,可以找到2^n and 2^(n+1)之间的数量

int t;                ////turns
int bin = 1;              //// numbers of ones in the binary format ,,, and 1 for 2^5
int n1 = 32;          //// 2^5  this is just for clarification 
int n2 = 64;          //// 2^6
int *keep = malloc(sizeof(int) * (n2 - n1);             ///this is to keep numbers because 
                                      /// i'll need it later in my consecutive numbers
int i = 0;
int a = 0;
n1 = 33               //// I'll start from 33 cause "bin" of 32 is "1";

while (n1 < n2)       /// try to understand it now by yourself
{
    t = 0;
    while (t <= 3)
    {

        if (t == 0 || t == 2) 
            bin = bin + 1;

        else if (t == 1) 
            bin = bin;

        else if (t == 3)
        {
            bin = keep[i];
            i++;
        }
        keep[a] = bin;
        a++;
        t++;
    }
    n1++;
}

无论如何,正如您所见,我已经很接近解决问题了,但是它们给了我很多数字,我必须在它们之间找到它们,不幸的是,我已经尝试了很多方法来使用上面的代码来计算“和”,我结束了时间执行问题。
例如:1, 1000000000 the numbers of ones is >>> 14846928141

所以您能提前提示我下一步做什么吗?

我正在为此做代码战挑战:https://www.codewars.com/kata/596d34df24a04ee1e3000a25/train/c

4 个答案:

答案 0 :(得分:3)

您可以通过计算1n范围内的位数并为所有子范围使用简单的减法来解决此问题:

#include <stdio.h>
#include <stdlib.h>

/* compute the number of bits set in all numbers between 0 and n excluded */
unsigned long long bitpop(unsigned long long n) {
    unsigned long long count = 0, p = 1;
    while (p < n) {
        p += p;
        /* half the numbers in complete slices of p values have the n-th bit set */
        count += n / p * p / 2;
        if (n % p >= p / 2) {
            /* all the numbers above p / 2 in the last partial slice have it */
            count += n % p - p / 2;
        }
    }
    return count;
}    

int main(int argc, char *argv[]) {
    unsigned long long from = 1000, to = 2000;

    if (argc > 1) {
        to = from = strtoull(argv[1], NULL, 0);
        if (argc > 2) {
            to = strtoull(argv[1], NULL, 0);
        }
    }

    printf("bitpop from %llu to %llu: %llu\n", from, to, bitpop(to + 1) - bitpop(from));
    return 0;
}

答案 1 :(得分:2)

以下是提速建议:

  1. 找到最小的y1,以使y1> = x1且y1为2的幂。

  2. 找到最大的y2,以使y2 <= x2且y2为2的幂。

  3. 找到p1和p2,使2 ^ p1 = y1和2 ^ p2 = y2

  4. 计算y1和y2之间的1:s量

  5. 分别处理x1到y1和y2到x2的交易

  6. 对4和5的结果求和

让我们集中在步骤4上。令f(n)为最大(2 ^ n)-1的和。我们可以很快认识到f(n)= 2 * f(n-1)+ 2 ^(n-1)且f(1)= 1。甚至可以进一步完善它,这样您就不必处理递归调用,但是我非常怀疑这是否重要。反正f(n)= n * 2 ^(n-1)

要获得y1和y2之间的结果,只需使用f(p2)-f(p1)

对于步骤5,您可以使用步骤4的修改版本。

编辑:

也许我很快就会说“迅速意识到”。这是一种了解它的方法。不难看出2 -1的数量。 2¹以下的唯一两个二进制数是0和1。为使2²以下的二进制数,我们将2¹以下的数字制成一列:

0
1

克隆它:

0
1
0
1

并在上半部分之前放置0:s,在下半部分之前放置1:s:

00
01
10
11

要得到2³,我们做同样的事情。克隆它:

00
01
10
11
00
01
10
11

并添加0和1:

000
001
010
011
100
101
110
111

现在应该很容易理解为什么f(n)= 2 * f(n-1)+ 2 ^(n-1)。克隆得到2f(n-1),将0:s和1:s相加得到2 ^(n-1)。如果2 ^(n-1)难以理解,请记住2 ^(n-1)=(2 ^ n)/ 2。在每一步中,我们有2 ^ n行,其中一半获得额外的1。

EDIT2:

当我查看这些列时,我对如何执行步骤5有所了解。假设您要查找从10到15的1:s数量。为此,二进制表为:

10: 1010
11: 1011
12: 1100
13: 1101
14: 1110
15: 1111

查看间隔12-15。二进制的最后两位数字是0-3对应表的副本。可以利用,但我留给您。

编辑3:

这是一个有趣的问题。我写了一些Python代码来做到这一点。递归调用过多,我会遇到一些问题,但可以很轻松地解决,将其转换为C并不会太复杂:

def f(n):
    return n*2**(n-1)

def numberOfOnes(x):
    if(x==0):
        return 0
    p = floor(log(x,2))
    a = f(p)
    b = numberOfOnes(x-2**p)
    c = x - 2**p +1
    return a+b+c

我制作了一张图像,以便您更容易理解abc在函数numberOfOnes中的作用(如果我们用numberOfOnes(12)对其进行调用) :

enter image description here

我终于将其转换为C。当然,我使用了一些在堆栈溢出中找到的代码。我借用了log2和pow的整数版本的代码,并做了一些小的修改。

此代码可能可以进一步优化,但这不是必需的。它的照明速度很快,我无法测量其性能。

#include <stdio.h>
#include <math.h>
#include <assert.h>
#include <stdint.h>
#include <inttypes.h>
typedef uint64_t T;

// https://stackoverflow.com/a/11398748/6699433                                                                    
const int tab64[64] = {
    63,  0, 58,  1, 59, 47, 53,  2,
    60, 39, 48, 27, 54, 33, 42,  3,
    61, 51, 37, 40, 49, 18, 28, 20,
    55, 30, 34, 11, 43, 14, 22,  4,
    62, 57, 46, 52, 38, 26, 32, 41,
    50, 36, 17, 19, 29, 10, 13, 21,
    56, 45, 25, 31, 35, 16,  9, 12,
    44, 24, 15,  8, 23,  7,  6,  5};

T log2_64 (T value) {
    value |= value >> 1;
    value |= value >> 2;
    value |= value >> 4;
    value |= value >> 8;
    value |= value >> 16;
    value |= value >> 32;
    return tab64[((T)((value - (value >> 1))*0x07EDD5E59A4E28C2)) >> 58];
}

// https://stackoverflow.com/a/101613/6699433                                                                      
T ipow(T base, T exp) {
    T result = 1;
    for (;;) {
        if (exp & 1) result *= base;
        exp >>= 1;
        if (!exp) break;
        base *= base;
    }
    return result;
}

T f(T n) { return ipow(2,n-1)*n; }

T numberOfOnes(T x) {
    if(x==0) return 0;

    T p = floor(log2(x));
    T a = f(p);
    T e = ipow(2,p);
    T b = numberOfOnes(x-e);
    T c = x - e + 1;
    return a+b+c;
}

void test(T u, T v) {
    assert(numberOfOnes(u) == v);
}

int main() {
    // Sanity checks
    test(0,0);
    test(1,1);
    test(2,2);
    test(3,4);
    test(4,5);
    test(5,7);
    test(6,9);
    // Test case provided in question
    test(1000000000,14846928141);
}

答案 2 :(得分:1)

int x1 = 5;
int x2 = 10;
int i=0;
int looper = 0;
unsigned long long ones_count = 0;

for(i=x1; i<=x2; i++){
    looper = i;
    while(looper){
        if(looper & 0x01){
            ones_count++;
        }
        looper >>= 1;
    }
}

printf("ones_count is %llu\n", ones_count);
return 0;

输出:ones_count为12

这是一种对两个值之间的每个值的每个位进行计数的方法。移位/掩码最有可能比算术运算符快,但仍可能会超时。您需要一个聪明的算法,如我认为的其他答案所示,但这是愚蠢的蛮力方式:)

答案 3 :(得分:0)

这是我对问题的解决方案:

** = exponentiation
/ = whole number division 

考虑从 1 到 16 的数字:

00001
00010
00011
00100
00101
00110
00111
01000
01001
01010
01011
01100
01101
01110
01111
10000

如果您注意每一列,您会注意到一个模式。列索引 i (0,1,2 ...) 处的位从右边开始经过一个长度为 2**(i+1) 的循环,即每 2**(i+1) 行,列 i 中的模式重复自身。另请注意,第一个循环从给定列中第一次出现 1 开始。一个模式的个数是模式长度的一半。

示例:

i   pattern
0   10
1   1100
2   11110000
3   1111111100000000
   ...

因此,鉴于将所有 1 相加到 n 的任务,我们必须跟踪每个模式自身重复的次数以及模式是否无法自行完成。

解决方案:

x 是二进制数 n 的最大指数,让 s 是直到 n 的所有指数之和。然后,对于 i = (0, 1, 2, ... , x),将 (n / 2**(i+1)*(2**i) 添加到 s。如果余数大于 2**i,则将 2**i 添加到 s,否则添加余数。然后从 2**i 中减去 n 并重复该过程。

示例: n = 7 -> x = 2

(7 / 2**1)*(2**0) = 3
7 % 2**1 = 1 !> 2**0
s = 1 + 3     (4)
n = n - 2**0  (6)

(6 / 2**2)*(2**1) = 2
6 % 2**2 = 2 !> 2**1
s = s + 2 + 2  (8)
n = n - 2**1   (4)

(4 / 2**3)*(2**2) = 0
4 % 2**3 = 4 !> 2**2
s = s + 4     (12)
n = n - 2**2  (0)

s = 12

也许不是最好的解释或最漂亮的解决方案,但它工作得很好。

在蟒蛇中:

def cnt_bin(n):
    bits = n.bit_length()
    s = 0
    for i in range(bits):
        s += (n // 2**(i+1))*2**i
        if n % 2**(i+1) > 2**i:
            s += 2**i
        else:
            s += (n % 2**(i+1))
        n -= 2**i
    return s

然后,对于范围 [a, b],您只需计算 cnt_bin(b) - cnt_bin(a-1)