【问题标题】:Calculating 2^n in O(n) time在 O(n) 时间内计算 2^n
【发布时间】:2015-06-29 14:18:27
【问题描述】:

不使用位移,有什么方法可以在 O(n) 时间内计算 2^n?

我正在考虑使用记忆的解决方案,因为我总是先从较低的 n 开始计算。 即

d = dict()
def pwr2(n):
    if n == 0:
       return 1
    if n == 1:
       return 2
    if n in d:
       return d[n]
    d[n] = 2 * pwr2(n-1)
    return d[n]

但我不太确定复杂性是什么。

编辑:我应该补充一点,我正在使用算法的一部分以比 O(n^2) 时间更快的速度将二进制转换为十进制。作为我的分治算法的一部分,我必须乘以 2 的幂次方,这就是我尝试记忆的原因。

EDIT2:在这里发布我的完整算法以帮助解决混淆 pwr2dict = dict()

def karatsuba(x, y):
    // use karatsuba's algorithm to multiply x*y


def pwr2(n):
    if n == 0:
        return 1
    if n == 1:
        return 2
    if n in pwr2dict:
        return pwr2dict[n]
    pwr2dict[n] = karatsuba(pwr2(n-1),2)
    return pwr2dict[n]


def binToDec(b):
    if len(b) == 1:
        return int(b)
    n = int(math.floor(len(b)/2))
    top = binToDec(b[:n])  # The top n bits
    bottom = binToDec(b[n:])  # The bottom n bits
    return bottom + karatsuba(top, pwr2(len(b)-n))

print binToDec("10110") // Prints 22

【问题讨论】:

  • 你的记忆解决方案的复杂性与没有记忆的解决方案相同,因为没有结果被重用。但是,如果乘法是常数时间,那么两者仍然是 O(n)。 (不是。)
  • 你的分治算法是什么?也许最好问问这个。
  • 如果 O(n) 中的 n 是 2^n 中 n 的位数,那么您实际上要求的是 O(log n) 算法。或者换句话说,一种在 O(n) 时间内计算 2^k 的算法,其中 n 是 k 中的位数
  • 这是一个关于 Python 的问题,还是抽象的?如果是抽象的,您使用的是什么计算模型?
  • 如果是抽象的,您使用的是什么 RAM 模型?字节? bigints 应该如何表示?例如,如果您选择表示为带有符号位的字节数组,那么答案很容易为 O(n),因为您可以直接构造结果而无需进行任何乘法运算。

标签: algorithm big-o memoization


【解决方案1】:

我们可以在Time = T(f(n)) + T(g(n)) 中实现它,其中f(n) 是构造一个二进制数n binary-digits 所用的时间,g(n) 是将该数字转换为十进制所用的时间。

Test Code Online

public BigInteger pow_2 (int n)
{
    String binary = "1";
    for (int i=0; i<n; i++) binary += "0"; // f(n)
    return new BigInteger(binary, 2); // g(n)
}

并且由于编译器优化,f(n) 将得到T(f(n))Θ(1)

至于g(n),代表BigInteger(value,radix)的构造函数,g(n)基于有多少个二进制数字,其中2^n在二进制中是一个有n+1数字的数字。

【讨论】:

  • 虽然包括基本转换的时间似乎会推动它,但可以朝相反的方向看:任务是计算 2 的 n 次方,没有指定输出表示,选择 ld(n) 产量O(1).
  • @greybeard 是的,我们可以返回二进制文件。对于较小的 n 值,我们可以直接将其写成一个值,如 long r = 0x00..001 通过位运算,即 O(1),对于 n &lt;= 62,因为 2^63 &gt; Long.MAX_VALUE
  • @greybeard 这里的基本问题是,如果解决方案中2^n 范围内的所有零都存在于我们手中(例如,如果我们定义一个原始 int 或 long 变量),那么我们可以只需翻转第 n 位即可获得2^n
【解决方案2】:

我认为你想多了你的问题。 2^n 仅表示将自身乘以 2 n 次。所以一个从 1 到 n 的简单循环就可以了:-)

r = 2
for 1 to n do
  r = r * 2
end

这是在O(n) 中运行的解决方案,计算2^n 的真正问题是,在现代计算机上,您将达到相当小的n 的体系结构字长,例如32、64 或128。然后你必须使用任意长度的整数,这可能不会给你足够的O(n) 时间,但这是一个不同的问题:-) 从理论上讲,它可以在O(n) 中轻松完成。

编辑

好的,如果我理解正确,你有一个很长的二进制字符串,你想把它转换成十进制。

我会按如下方式实现它:

将长度为n 的二进制字符串放入数组s(可以是位图以节省空间,也可以是支持这些的编程语言中的字符串)。反转字符串,使 LSB 位于索引 0 处(如果不是这样的话)。

e := 1
r := 0
for i := 0 to (n - 1) 
  if s[i] = 1
    r := r + e
  end
  e := e * 2
end

反转字符串可以在O(n)中完成,伪代码只有一个从0n - 1的循环,所以在O(n)中也是如此。位串反转可以避免在循环中进行一些简单的算术运算。并不是说r 必须是任意长度的整数类型。

【讨论】:

  • 正如 OP 所述,这正是一个 O(n) 解决方案。
  • 我的问题是乘法是 O(n),对于一个 n 位数是 O(n^2),不是吗?有人提示我使用 Karatsuba 的算法进行乘法运算,因为我们可以得到 O(n^1.59)
  • 用于以二进制表示 2^n 的内存随 n 呈线性增长,而不是指数增长。从字面上看是线性的,因为 2^n 有 n 位。
  • 虽然这个答案的原始形式是问题原始形式的有效解决方案,但答案的原始形式和编辑形式都不能正确解决在处理任意精度时出现的减速整数。 r := r + ee := e * 2 都不能被视为在恒定时间内运行,因此循环不能被视为以线性时间运行。
  • @user23571122,这是真正的任意长度整数的乘法通常是O(n^2)(或者O(n log n log log n),如果你做了一些聪明的事情),但是因为你乘以2的幂,2的幂,你可以在常数时间内进行乘法运算。
【解决方案3】:

您实际上可以在 O(1) 时间内完成。乘以 2 本身意味着左移 n-1 位。或将 1 左移 n 位。

您的问题可以通过以下方式解决。 long k = 2 &lt;&lt; n-1. 要么 long k = 1 &lt;&lt; n.

是不是很简单?

编辑 我意识到这是在没有位移的情况下完成的。你仍然不需要 O(n) 时间。可以在 O(logn) 内完成

private int power(int x, int n) {

    if (n == 1)
        return x;
    if (n % 2 == 0) {
        int power = power(x, n / 2);
        return power * power;
    } 

    else{
        int power = power(x, (n - 1) / 2);
        return x * power * power;
    }
}

详细了解递归Recursion

【讨论】:

  • 仍然不是 O(log n)!你真的可以在 O(1) 中做 x * power * power 吗?
  • 我不确定您如何看待渐近分析。但是是的 x * power * power 是一个简单的乘法函数,因为存在三个数字。此外,这种数学运算所花费的时间不依赖于输入 n。所以乘法是常数时间运算。我同意这个常数可能更大,但是一个常数。无论 n 的值是 10 还是 100 万,将三个数字相乘将是 O(1) w.r.t 我想将一个数字提高到的 n 次方。
猜你喜欢
  • 1970-01-01
  • 2014-04-30
  • 2018-03-04
  • 1970-01-01
  • 1970-01-01
  • 2019-12-13
  • 2019-12-09
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多