【问题标题】:Finding digits in powers of 2 fast快速查找 2 次方的数字
【发布时间】:2013-09-22 09:54:34
【问题描述】:

任务是搜索 2^10000 以下的每个 2 的幂,返回包含字符串的第一个幂的索引。例如,如果要搜索的给定字符串是“7”,程序将输出 15,因为 2^15 是其中包含 7 的第一个幂。

我通过蛮力尝试解决了这个问题,该尝试在大约 70% 的测试用例中超时。

for i in range(1,9999):
    if search in str(2**i):
        print i
        break

在 5 秒的时间限制下如何解决这个问题?

【问题讨论】:

  • 您输入的搜索字符串是否有限制?
  • @Save 是的,忘了说。搜索字符串将少于 200 个字符

标签: python algorithm math language-agnostic


【解决方案1】:

尽量不要在每一步都计算2^i

pow = 1
for i in xrange(1,9999):
    if search in str(pow):
        print i
        break
    pow *= 2

您可以随时计算它。这应该会节省大量的计算时间。

使用xrange 会阻止构建列表,但这在这里可能不会有太大的不同。

in 可能实现为二次字符串搜索算法。使用KMP 之类的东西进行字符串搜索可能会(也可能不会,你必须测试)更有效。

【讨论】:

  • 另外,xrange 而不是 range
  • 使用 KMP 进行字符串搜索我会看到什么样的增长?
  • @user1411838 - 很难说。虽然 KMP 在两个字符串的大小上是线性的,但在编程语言中实现的二次算法通常会进行大量优化,因此差异可能不会很大。您必须进行测试才能确定。
  • @IVlad 我会试一试,看看会发生什么,还有其他建议可以加快速度吗?也许不是计算每一个力量?我不太确定
  • @user1411838 - 我不确定。仅仅通过避免** 和使用xrange,你得到了什么提升?你能多线程吗?
【解决方案2】:

一种更快的方法是直接以十进制计算数字

def double(x):
    carry = 0
    for i, v in enumerate(x):
        d = v*2 + carry
        if d > 99999999:
            x[i] = d - 100000000
            carry = 1
        else:
            x[i] = d
            carry = 0
    if carry:
        x.append(carry)

那么搜索功能就可以变成

def p2find(s):
    x = [1]
    for y in xrange(10000):
        if s in str(x[-1])+"".join(("00000000"+str(y))[-8:]
                                   for y in x[::-1][1:]):
            return y
        double(x)
    return None

还要注意,2 到 2^10000 的所有幂的位数只有 1500 万,搜索静态数据要快得多。如果程序不能每次都重新启动,那么

def p2find(s, digits = []):
    if len(digits) == 0:
        # This precomputation happens only ONCE
        p = 1
        for k in xrange(10000):
            digits.append(str(p))
            p *= 2
    for i, v in enumerate(digits):
        if s in v: return i
    return None

使用这种方法,第一次检查需要一些时间,接下来的检查会非常非常快。

【讨论】:

  • 后缀树比搜索这个大字符串要快,因为查找与这个大字符串的大小无关——它只取决于搜索字符串。
【解决方案3】:

计算每个 2 的幂并使用每个字符串构建一个后缀树。这是所有字符串大小的线性时间。现在,查找基本上是每个查找字符串长度的线性时间。

我认为您无法在计算复杂性方面击败它。

【讨论】:

  • 这听起来很有趣,如何在 python 中编写一个基本的后缀树?幂的计算会一样吗?
  • @user1411838,是的,都是一样的代码;只需使用适当的数据结构,以便重复搜索更快。
【解决方案4】:

只有 10000 个数字。您不需要任何复杂的算法。只需提前计算并进行搜索即可。这应该只需要 1 或 2 秒。

powers_of_2 = [str(1<<i) for i in range(10000)]

def search(s):
    for i in range(len(powers_of_2)):
        if s in powers_of_2[i]:
            return i

【讨论】:

    【解决方案5】:

    试试这个

    twos = []
    twoslen = []
    two = 1
    for i in xrange(10000):
        twos.append(two)
        twoslen.append(len(str(two)))
        two *= 2
    
    tens = []
    ten = 1
    for i in xrange(len(str(two))):
        tens.append(ten)
        ten *= 10
    
    s = raw_input()
    l = len(s)
    n = int(s)
    
    for i in xrange(len(twos)):
        for j in xrange(twoslen[i]):
            k = twos[i] / tens[j]
            if k < n: continue
            if (k - n) % tens[l] == 0:
                print i
                exit()
    

    这个想法是预先计算 2、10 的每个幂,并且还预先计算 2 的每个幂的位数。这样,​​问题就简化为找到存在 j 的最小值 i,这样在移除后来自 2 ** i 的最后 j 个数字,您获得一个以 n 结尾的数字或表示为公式 (2 ** i / 10 ** j - n) % 10 ** len(str(n)) == 0。

    【讨论】:

    • 如果遇到不是数字的输入会发生什么?类似 31415926535897932
    【解决方案6】:

    这里的一个大问题是,将二进制整数转换为十进制表示法所花费的时间是位数的二次方(至少以 Python 的直接方式)。正如@6502 在他的回答中所做的那样,伪造自己的十进制算术实际上更快。

    但是让 Python 的 decimal 模块执行它非常快得多 - 至少在 Python 3.3.2 下(我不知道 Python decimal 内置了多少 C 加速之前的版本)。代码如下:

    class S:
        def __init__(self):
            import decimal
            decimal.getcontext().prec = 4000  # way more than enough for 2**10000
            p2 = decimal.Decimal(1)
            full = []
            for i in range(10000):
                s = "%s<%s>" % (p2, i)
                ##assert s == "%s<%s>" % (str(2**i), i)
                full.append(s)
                p2 *= 2
            self.full = "".join(full)
    
        def find(self, s):
            import re
            pat = s + "[^<>]*<(\d+)>"
            m = re.search(pat, self.full)
            if m:
                return int(m.group(1))
            else:
                print(s, "not found!")
    

    和示例用法:

    >>> s = S()
    >>> s.find("1")
    0
    >>> s.find("2")
    1
    >>> s.find("3")
    5
    >>> s.find("65")
    16
    >>> s.find("7")
    15
    >>> s.find("00000")
    1491
    >>> s.find("666")
    157
    >>> s.find("666666")
    2269
    >>> s.find("66666666")
    66666666 not found!
    

    s.full 是一个超过 1500 万个字符的字符串。它看起来像这样:

    >>> print(s.full[:20], "...", s.full[-20:])
    1<0>2<1>4<2>8<3>16<4 ... 52396298354688<9999>
    

    所以字符串包含 2 的每个幂,指数跟随在尖括号中的幂。 find() 方法构造一个正则表达式来搜索所需的子字符串,然后向前查找以查找幂。

    玩弄这个,我确信几乎任何搜索方式“足够快”。它正在获取占用大量时间的大国的十进制表示。 decimal 模块解决了这个问题。

    【讨论】:

      猜你喜欢
      • 2017-10-27
      • 2011-07-11
      • 1970-01-01
      • 2010-12-20
      • 1970-01-01
      • 2013-10-28
      • 1970-01-01
      • 2014-06-04
      • 1970-01-01
      相关资源
      最近更新 更多