【问题标题】:Counting Number of Set bits with Minimum N计数具有最小 N 的设置位数
【发布时间】:2019-01-06 07:30:23
【问题描述】:

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

给定一个整数k,找到最小整数n,使得{ 1, 2, . . ., 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; 
} 

【问题讨论】:

  • 请发布您尝试过的内容,最好是minimal reproducible example
  • 我认为你应该分开你的答案。第一部分是正确的算法,第二部分是对其进行编程。第一部分独立于语言,它不应该有 C++ 标签。第二部分应该从第一部分的结果开始,并准确描述您在将算法转换为 C++ 时遇到的问题。也就是说,作为新用户,请使用tour 并阅读How to Ask

标签: c++ algorithm data-structures


【解决方案1】:

算法比较简单。我们不会一次只计算一个数字并累积其二进制表示所具有的 1 的数量,而是使用一系列函数 {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 找到最近的 a(m) 小于 k,而 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 and d(1)=2 so 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;
}

【讨论】:

  • 一旦我得到正确的 m 我们如何才能达到确切的 n 并不清楚你能解释一下吗
  • 给我一点时间来编辑,我会将此步骤添加到答案中并更新。
  • 完成,看看。我给你两个近似值,这是你能得到的最好的。您首先计算哪个 a(m) 离您最近,然后如果需要计算 b(m) 以使您更接近。
  • 那是因为你需要 a(3)=12,而不是 a(2)。 a(3) 里面有 4 个 1(7 用 1111 表示),所以它下面的任何数字最多只能有 8 个 1,这意味着 a(3) 是你能找到的至少有 11 个 1 以上的 n。请注意我在 a(m) 示例之后的评论。
  • 你能检查一下这是找到我所理解的最小 m 的正确方法吗:ideone.com/oQ4f7S
猜你喜欢
  • 1970-01-01
  • 2018-03-06
  • 2013-01-18
  • 1970-01-01
  • 2012-06-26
  • 1970-01-01
  • 2016-11-30
  • 2020-04-03
  • 1970-01-01
相关资源
最近更新 更多