算法比较简单。我们不会一次只计算一个数字并累积其二进制表示所具有的 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;
}