计数最小N的置位位数

时间:2019-01-06 07:30:24

标签: c++ algorithm data-structures

我正在尝试解决此问题:

  

给出一个整数 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(Reference)的个数,但是找到最小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; 
} 

1 个答案:

答案 0 :(得分:1)

该算法相对简单。我们将使用一系列函数{a(m),b(m),c(m)而不是一次累积一个二进制表示的1的数目,而不是一次累积一个数字。 。}靠近目标,最后最多留下几个数字以手动添加。每个函数的格式为,其中x是函数的编号(对于a(m)x = 0,对于b(m)x = 1 ...)。

这些函数基于二进制数的特征:在从1到1的所有数字中 以其{1,2 ... n}的二进制表示形式可以知道1的累加数。

让我们看一下数字,它位于二进制文件1111中。您可以知道从1(0001)到15(1111)的所有数字中1的计数-它计算的是您可以放置​​多少种方法四分之一的分数(4)乘以1,再加上四分之六的分数可以乘以2(6)乘2,四分之三的分数可以四分之三(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个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。 / p>

如果需要再次迭代,可以定义,它将使我们更加接近。例如,对于k = 120,我们得到a(5)+ b(3)= 100,加上c(2)将使我们再增加12个到112。我们可以手动添加缺失的8个数字或决定添加{{0 }}通过添加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-从119 1的45中收集的下一个数字。

复杂度为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;
}