【问题标题】:Count ones in a segment (binary)计算段中的个数(二进制)
【发布时间】:2019-04-23 12:47:40
【问题描述】:

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

有两个数 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^nn >= 1 的个数是1 例如:2^1 => num of ones is 1 2^2 => 12^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 &gt;&gt;&gt; 14846928141

所以你能不能给我一点提示下一步该做什么,谢谢。

我这样做是为了代码战挑战:https://www.codewars.com/kata/596d34df24a04ee1e3000a25/train/c

【问题讨论】:

  • Bit Population(中间算法)
  • 只要解决 1 和 x 之间有多少个,然后对于 y-x 范围,答案是从 1 到 x - 从 1 到 y。请注意,对于最低有效位,每 2 个数字就会得到一个 1。第二个,每 4 个 2 个,第三个,每 8 个 4 个……
  • 在恒定时间内计算它应该是相当简单的(即不循环遍历每个数字)

标签: c algorithm math binary


【解决方案1】:

这是一个加速建议:

  1. 找到最小的 y1,使得 y1 >= x1 并且 y1 是 2 的幂

  2. 找到最大的 y2 使得 y2

  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。为了使 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 步有了一个想法。假设您想找到 1:s 从 10 到 15 的数量。为此的二进制表是:

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)调用它:

我终于将它转换为 C。当然,我使用了一些在 Stack Overflow 上找到的代码。我借用了 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);
}

【讨论】:

  • @Broman emmm 也许你的代码中有一个错误Error: countOnes(476744280, 477171088) Expected: 6676768 Got: 6676755 我做了sum = numberOfOnes(477171088) - numberOfOnes(476744280)
  • @HeIs 你在哪里找到测试用例?
  • @Broman 我正在代码战中挑战codewars.com/kata/596d34df24a04ee1e3000a25/train/c
  • @HeIs 我能说什么?我已经对它进行了 n=1 到 10⁸ 的测试,完全没有失败。
  • @HeIs 我发现了你的错误。你应该使用 numberOfOnes(right) - numberOfOnes(left - 1)
【解决方案2】:

您可以通过计算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;
}

【讨论】:

    【解决方案3】:
    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

    这是一种计算两个值之间每个值的每一位的方法。移位/掩码很可能比您的算术运算符快,但仍可能超时。你需要一个聪明的算法,就像我认为的其他答案所暗示的那样,但这是愚蠢的蛮力方式:)

    【讨论】:

    • 是的,它很有用,但代码需要找到 x1 和 x2 之间所有 1 的总和。
    • x1 和 x2 在该代码中是 min_val 和 max_val。它适用于 5,10 但在 codechef 上超时 1,1000000000 次,我会更新代码以使用 OP 中的变量名。
    • 尝试在这个站点中执行你的代码,它可以运行 1000000000 但它需要时间onlinegdb.com/online_c_compiler
    • 比算术方式快吗?你的测试速度够快吗?
    • 不幸的是,我得到了“执行超时”。
    【解决方案4】:

    这是我解决问题的方法:

    ** = 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
       ...
    

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

    解决方案:

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

    示例: n = 7 -&gt; 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
    

    也许不是最好的解释或最漂亮的解决方案,但效果很好。

    在python中:

    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)

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2015-08-31
      • 1970-01-01
      • 2021-03-05
      • 1970-01-01
      • 2021-12-27
      相关资源
      最近更新 更多