【问题标题】:Concatenation of binary representation of first n positive integers in O(logn) time complexity在 O(logn) 时间复杂度中连接前 n 个正整数的二进制表示
【发布时间】:2021-04-25 19:00:14
【问题描述】:

我在一次编程比赛中遇到了这个问题。给定一个数 n,连接前 n 个正整数的二进制表示,并返回所形成的结果数的十进制值。由于答案可以很大,返回答案模 10^9+7。 N 可以大到 10^9。 例如:- n=4。形成的数字=11011100(1=1,10=2,11=3,100=4)。十进制值 11011100=220。

我找到了这个问题的堆栈溢出答案,但问题在于它只包含 O(n) 解决方案。 链接:-concatenate binary of first N integers and return decimal value

由于 n 可以达到 10^9,我们需要提出比 O(n) 更好的解决方案。

【问题讨论】:

标签: math bit-manipulation


【解决方案1】:

这是一些提供快速解决方案的 Python 代码;它使用与Abhinav Mathur 的帖子相同的想法。它需要 Python >= 3.8,但它没有使用 Python 中的任何特别花哨的东西,并且可以很容易地翻译成另一种语言。如果目标语言中不可用,则需要编写模幂运算和模逆算法。

首先,出于测试目的,让我们定义缓慢而明显的版本:

# Modulus that results are reduced by,
M = 10 ** 9 + 7


def slow_binary_concat(n):
    """
    Concatenate binary representations of 1 through n (inclusive).

    Reinterpret the resulting binary string as an integer.
    """
    concatenation = "".join(format(k, "b") for k in range(n + 1))
    return int(concatenation, 2) % M

检查我们是否得到了预期的结果:

>>> slow_binary_concat(4)
220
>>> slow_binary_concat(10)
462911642

现在我们将编写一个更快的版本。首先,我们将[1, n) 范围分割成子区间,这样在每个子区间内,所有数字的二进制长度都相同。例如,[1, 10) 范围将分为四个子区间:[1, 2)[2, 4)[4, 8)[8, 10)。这是一个执行拆分的函数:

def split_by_bit_length(n):
    """
    Split the numbers in [1, n) by bit-length.

    Produces triples (a, b, 2**k). Each triple represents a subinterval
    [a, b) of [1, n), with a < b, all of whose elements has bit-length k.
    """
    a = 1
    while n > a:
        b = 2 * a
        yield (a, min(n, b), b)
        a = b

示例输出:

>>> list(split_by_bit_length(10))
[(1, 2, 2), (2, 4, 4), (4, 8, 8), (8, 10, 16)]

现在对于每个子区间,该子区间中所有数字的串联值由一个相当简单的数学总和表示,可以以精确的形式计算。这是一个计算和模M的函数:

def subinterval_concat(a, b, l):
    """
    Concatenation of values in [a, b), all of which have the same bit-length k.
    l is 2**k.

    Equivalently, sum(i * l**(b - 1 - i)) for i in range(a, b)) modulo M.
    """
    n = b - a
    inv = pow(l - 1, -1, M)
    q = (pow(l, n, M) - 1) * inv
    return (a * q + (q - n) * inv) % M

我不会在这里对总和进行评估:对于本网站来说,这有点离题,如果没有一种呈现公式的好方法,就很难表达。如果您想了解详细信息,那是https://math.stackexchange.com 的主题,或者是相当简单的代数页面。

最后,我们想把所有的区间放在一起。这是执行此操作的函数。

def fast_binary_concat(n):
    """
    Fast version of slow_binary_concat.
    """
    acc = 0
    for a, b, l in split_by_bit_length(n + 1):
        acc = (acc * pow(l, b - a, M) + subinterval_concat(a, b, l)) % M
    return acc

与慢版本的比较表明我们得到了相同的结果:

>>> fast_binary_concat(4)
220
>>> fast_binary_concat(10)
462911642

但是对于更大的输入,可以很容易地评估快速版本,而使用慢速版本是不可行的:

>>> fast_binary_concat(10**9)
827129560
>>> fast_binary_concat(10**18)
945204784

【讨论】:

  • 我没有添加代码,所以读者仍然需要付出一些努力:)
  • 这太棒了!我添加了一个 C++ 解决方案,并且更清楚地解释了数学。看看这里:stackoverflow.com/a/67252483/5524175
【解决方案2】:

您只需要注意一个简单的模式。以n=4为例,让我们从n=1开始逐步构建解决方案。

1 -> 1                         #1
2 -> 2^2(1) + 2                #6
3 -> 2^2[2^2(1)+2] + 3         #27
4 -> 2^3{2^2[2^2(1)+2]+3} + 4  #220

如果你扩展n=4 的每一项的系数,你会得到如下系数:

1 -> (2^3)*(2^2)*(2^2)
2 -> (2^3)*(2^2)
3 -> (2^3)
4 -> (2^0)

N 是我们所需数字的字符串表示中的总位数,D(x)x 中的位数。系数可以写成

1 -> 2^(N-D(1))
2 -> 2^(N-D(1)-D(2))
3 -> 2^(N-D(1)-D(2)-D(3))
... and so on

由于D(x) 的值对于范围(2^t, 2^(t+1)-1) 之间的所有x 相同,对于某些给定的t,您可以将问题分解为这样的范围并使用数学解决每个范围(不是迭代) .由于此类范围的数量将是log2(Given N),因此这应该在给定的时间限制内有效。
例如,各种范围变为:

1. 1 (D(x) = 1)
2. 2-3 (D(x) = 2)
3. 4-7 (D(x) = 3)
4. 8-15 (D(x) = 4)

【讨论】:

猜你喜欢
  • 1970-01-01
  • 2017-06-18
  • 2020-01-29
  • 2018-08-05
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2023-02-23
  • 2023-04-07
相关资源
最近更新 更多