繁体   English   中英

计数最小 N 的设置位数

[英]Counting Number of Set bits with Minimum N

我正在尝试解决这个问题:

给定一个整数k ,找到最小整数n使得 { 1, 2, ... 的二进制表示中 1 的总数。 . ., n } 至少是k

例如,给定k = 4,我们希望n = 3,因为 1、2 和 3 的二进制表示包含 1、1 和 2(分别),并且 1 + 1 + 2 ≥ 4。

我曾尝试在 Log(n) 中使用从 (1 到 n) 计算设置位,无法以有效的方式找到最小 n。

编辑 :

代码:计算编号设置位从 1 到 n( 参考)但在找到最小 n 时有问题。有没有办法围绕这个得出一些解决方案?

int getSetBitsFromOneToN(int N){ 
int two = 2,ans = 0; 
int n = N; 
while(n){ 
    ans += (N/two)*(two>>1); 
    if((N&(two-1)) > (two>>1)-1) ans += (N&(two-1)) - (two>>1)+1; 
    two <<= 1; 
    n >>= 1; 
} 
return ans; 
} 

算法比较简单。 我们将使用一系列函数 {a(m), b(m), c(m).. .} 更接近目标,最多留下几个数字,最后手动添加。 每个功能都将在格式其中 x 是函数的编号(对于 a(m) x=0,对于 b(m) x=1...)。

这些函数基于二进制数的一个特性:在从 1 到您可以知道 {1,2...n} 的二进制表示中 1 的累积数量。

我们来看看数字 ,它是二进制的 1111。你可以知道从 1 (0001) 到 15 (1111) 的所有数字中 1 的计数 - 它计算你可以将 1 放在 4 个位置 (4) 乘以 1 的多少种方式,加上多少你可以把 2 放在 4 个地方的次数 (6) 乘以 2,再加上你可以把 3 放在 4 个地方的次数 (4) 乘以 3 加上你可以把 4 放在 4 个地方的多少种方法 (1) 乘以 4。所以总数是 32,这也是 . 你会注意到,对于任何这样的数字 n= , 1 的累计数为 . 我们将这个函数命名为 a(m) 如上所述(这里 x=0,所以不需要在这个函数中添加元素)。 例如:

  • 1 = a(1) = = = = 1。
  • 3 = a(2) = = = = 4。
  • 7 = a(3) = = = = 12。
  • 15 = a(4) = = = = 32。
  • 31 = a(5) = = = = 80。

等等。 所以对于数字 15 这是 ,我们计算 a(4) 并得到 32 个累积的 1。 我们也会注意到这个数字其中正好有 m 个 1(所有数字都设置为 1)。

知道这一点,你用你的数字 k 找到最近的小于 k 的 a(m),而 a(m+1) 将大于 k。 如果a(m+1)只比k多m+1,则以a(m+1)为答案,完成算法。 由于 a(m+1) 中至少有 m+2,这意味着如果没有它,您将无法累积所需的所有 k 1。

如果 k 比 a(m+1) 大 m+1 但大于 a(m),您将需要通过定义第二个函数来进行第二步近似——我们称之为 b(m)。 我们将定义 b(m)= . 这个数字将相当于完全(不是就像 a 函数一样)例如:

  • 2 = b(1) = = = = 1+2 = 3。
  • 4 = b(2) = = = = 4+4 = 8。
  • 8 = b(3) = = = = 12+8 = 20。
  • 16 = b(4) = = = = 32+16 = 48。
  • 32 = b(5) = = = = 80+32 = 112。

我们定义 b 的原因是为了描述第一个之间 1 的累积的独特差异一批数字和第二个一批数字 - 将第二批中的每个数字添加到另一个最重要的 1 中。 这就是为什么我们现在看并不是 .

通过将两个函数相加,我们可以得到我们的数字 n。 如果我们在两次近似后仍然缺少最后的 k,我们可以一个一个地累加数字,直到我们达到 k。

让我们假设 k=50。 我们知道 a(4) 是我们能得到的最接近的数,它是仍然低于 50 的最大数。a(5) 将使我们达到 80,如上所示。 所以 a(4) 是解的前半部分,也就是 15。

剩下的 1 是 50-32=18。 我们需要看看我们需要处理多少个超过 15 的数字才能累积至少 18 个 1。 通过计算 b 函数,我们看到 b(2) 使我们更接近,而 b(3) 为 2。 因为我们知道 b(3) 表示的数字至少有 4 个 1,所以我们知道它是我们需要的数字 - 任何低于它的数字只会累积 16 个 1,我们需要 18。所以我们使用 b(3) ),即或 8. 结果是 15+8=23,这是在所有 {1,2..23} 二进制表示中至少累积 50 个 1 的最小数。

如果我们需要再进行一次迭代,我们可以定义它会让我们更接近。 例如,对于 k=120,我们得到 a(5)+b(3)=100,加上 c(2) 将使我们在 112 的基础上再增加 12。我们可以手动添加缺少的 8 个数字或决定添加这将使我们通过添加 a(5)+b(3)+c(2)+d(1) 得到 119。 这意味着下一个数字必须是累积了 k 个或更多 1 的最小 n。 a(5)=31, b(3)=8, c(2)=4 和 d(1)=2 所以 n=46 - 45 收集的 119 个 1 中的下一个数字。

复杂度是 O(log(k)) - 我们有 log(k) 个步骤来获得 a(m) 和另一个最多 log(k) 的累积来获得 b(m) 和我们最终的 n。

代码

//This represents the function a(m), b(m)... etc.
public int getFuncResult(int funcNum, int arg) {
    Double result =  Math.pow(2,arg-1)*arg+funcNum*Math.pow(2,arg);
    return result.intValue();
}

//This is the iterative algorithm described: add a(m)+b(m)... until k
public int countOnesToKIter(int k) {
    int funcNum = 0;
    int counter = 0;
    int retVal = 0;
    int exponent = 0;
    while (k > 0) {
        //for the current function, find the appropriate m
        while (k > counter) {
            exponent++;
            counter = getFuncResult(funcNum, exponent);
        }

        //if the last number contains more 1's than the difference, use it.
        if (counter-k < exponent) {
            counter=getFuncResult(funcNum, exponent-2);
            retVal+=Math.pow(2,exponent-2);
        } else {
            counter = getFuncResult(funcNum, exponent-1);
            retVal+=Math.pow(2,exponent-1);
        }
        funcNum ++;
        exponent=0;
        k = k-counter;
        counter = 0;
    }

    return retVal;
}

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM