【问题标题】:How to multiply two big big numbers如何将两个大数相乘
【发布时间】:2010-07-18 14:54:51
【问题描述】:

给你一个包含 n 个数字 L=&lt;a_1, a_2,...a_n&gt; 的列表。他们每个人都是 0 或 +/- 2k, 0 p=a_i*a_i+1*...*a_j, 1 <= i <= j <= n.

例如,对于输入 &lt;8 0 -4 -2 0 1&gt;,它应该返回 8(或者 8 或 (-4)*(-2))。

您可以使用任何标准的编程语言,并且可以假设 该列表以任何标准数据结构给出,例如int[], vector&lt;int&gt;List&lt;Integer&gt;

你的算法的计算复杂度是多少?

【问题讨论】:

  • 请看一下我新编辑的答案中的伪代码,它似乎比接受的答案更有效。
  • @Peter G:看起来很复杂,我不知道它是否正确,但如果是的话,你已经做得很好了!看起来像一个 O(n) 解决方案。
  • 与问题没有直接关系,但对于那些可能遇到此页面并试图找到使用 Java 处理大数的库例程的人,请参阅 BigDecimalBigInteger
  • @Carl 我添加了一些解释 cmets。

标签: java arrays multiplication


【解决方案1】:

在我的第一个答案中,我解决了 OP 的“将两个大数相乘”的问题。事实证明,这个愿望只是我现在要解决的更大问题的一小部分:

“我还没有达到我算法的最终框架,不知道你能不能帮我解决这个问题。”

(问题描述见问题)

我要做的只是更详细地解释the approach Amnon proposed,所以所有功劳都应该归功于他。

你必须从一个整数列表中找到一个连续子列表的最大乘积,这些整数是 2 的幂。这个想法是:

  1. 计算每个连续子列表的乘积。
  2. 退回所有这些产品中最大的一个。

您可以通过其startend 索引来表示子列表。对于start=0end 有 n-1 个可能的值,即 0..n-1。这将生成从索引 0 开始的所有子列表。在下一次迭代中,您将 start 增加 1 并重复该过程(这一次,end 有 n-2 个可能的值)。这样您就可以生成所有可能的子列表。

现在,对于这些子列表中的每一个,您必须计算其元素的乘积——即提出一个方法computeProduct(List wholeList, int startIndex, int endIndex)。您可以使用内置的BigInteger 类(它应该能够处理您的作业提供的输入)来使您免于进一步的麻烦,或者尝试实现其他人描述的更有效的乘法方法。 (我会从更简单的方法开始,因为它更容易查看您的算法是否正常工作,然后首先尝试对其进行优化。)

现在您可以遍历所有子列表并计算其元素的乘积,确定具有最大乘积的子列表应该是最简单的部分。

如果您仍然难以在两个步骤之间建立联系,请告诉我们 - 但也请在您解决问题时向我们提供您的代码草稿,这样我们就不会最终逐步构建解决方案,然后您复制并粘贴它。

编辑:算法骨架

public BigInteger listingSublist(BigInteger[] biArray)
{       
    int start = 0;
    int end = biArray.length-1;
    BigInteger maximum;

    for (int i = start; i <= end; i++)
    {
        for (int j = i; j <= end; j++)
        {
            //insert logic to determine the maximum product.
            computeProduct(biArray, i, j);
        }
    }

    return maximum;                
}

public BigInteger computeProduct(BigInteger[] wholeList, int startIndex, 
                                                         int endIndex)
{
    //insert logic here to return
    //wholeList[startIndex].multiply(wholeList[startIndex+1]).mul...(
    //    wholeList[endIndex]);       
}

【讨论】:

  • @Amnon 我很感激其他有经验的人与我分享他们的知识并在我还是初学者时帮助了我。很高兴能够偿还其中的一部分。
  • 这就是我提供其文档链接的原因。应用“乘法”方法并不比“”运算符更难。如果您设法使用“”解决它,那么使用乘法的解决方案并不遥远
【解决方案2】:

由于 k k 都将适合 Java int。然而,这两个整数的乘积可能不一定适合 Java int,因为 2k * 2k = 22*k60 填入 Java long。这应该回答您关于“(乘以)两个数字......”的问题。

如果您可能想要将两个以上的数字相乘,您的作业暗示“...连续子列表的最大乘积...”(子列表的长度可能 > 2),请查看在 Java 的 @987654321@ 类中。

【讨论】:

  • 这取决于您用来解决作业中定义的问题的算法。发布您的第一种方法怎么样? - 如果您还没有正确的算法,我相信 SO 社区会很乐意帮助您找到正确的算法。
  • @Free Styler 我已经在单独的答案中解决了这个问题。
【解决方案3】:

实际上,最有效的乘法方法是加法。在这种特殊情况下,您所拥有的只是 2 的幂的数字,您可以通过简单地将指数加在一起来获得子列表的乘积(并计算乘积中的负数,并在奇数的情况下将其设为负数底片)。

当然,如果位用完,您可能需要 BigInteger 来存储结果。或者根据输出的样子,只需说 (+/-)2^N,其中 N 是指数的总和。

解析输入可能是 switch-case 的问题,因为您只有 30 个数字需要处理。加上底片。

那是无聊的部分。有趣的部分是如何获得产生最大数字的子列表。您可以通过检查每一个变化来采取愚蠢的方法,但在最坏的情况下(IIRC)这将是一个 O(N^2) 算法。对于较长的输入,这确实不是很好。

你能做什么?我可能会从列表中最大的非负数作为子列表开始,然后扩展子列表以在每个方向上获得尽可能多的非负数。然后,在所有正面都触手可及的情况下,继续在两侧进行成对的底片,例如。只有当你可以在列表的两边都增长时才会增长。如果您不能双向增长,请尝试使用两个(四个、六个等,甚至是偶数)连续负数的方向。如果你连这种方式都无法成长,那就停下来吧。

嗯,我不知道这个算法是否有效,但如果它(或类似的东西)有效,它是一个 O(N) 算法,这意味着性能很好。让我们试试吧! :-)

【讨论】:

  • 好吧,不,不是那么容易,如果你可以左右增长,或者向右增长两次呢?你需要朝着更大的结果成长。如果局部最大值不是全局最大值怎么办?是否可以?这是一个非常好的家庭作业,你可以花点时间考虑一下:-)
  • +1 用于更详细地解释 2^k 形式的多个整数的乘法。
  • 虽然我不确定,但直觉上认为赋值允许利用最优性原则。
  • 这是一个示例列表,其中局部最大值不是解决方案的一部分:4,4,4,-1,8。 8 是最大的数,但结果是 4*4*4。我猜 O(N) 太乐观了,也许 O(N*logN) 是一个更现实的目标。
【解决方案4】:

嗯.. 因为它们都是 2 的幂,所以您可以只添加指数而不是乘以数字(相当于取乘积的对数)。例如,2^3 * 2^7 是 2^(7+3)=2^10。 我将把处理标志作为练习留给读者。

关于子列表问题,有少于 n^2 对 (begin,end) 索引。您可以全部检查,或尝试dynamic programming 解决方案。

【讨论】:

  • +1 非常好的提示,适用于希望有效处理作业中描述的特定乘法或接近解决它的算法设计或两者兼而有之的人。
【解决方案5】:

编辑:我调整了算法大纲以匹配实际的伪代码并将复杂性分析直接放入答案中:

算法概述

依次遍历序列并存储自最后一个 0 以来产品的值和第一个/最后一个索引(正)。对另一个产品(负)执行相同操作,该产品仅包含自序列的第一个符号更改以来的数字.如果您遇到负序列元素,则交换两个产品(正数和负数)以及关联的起始索引。每当正积达到新的最大值时,就会存储它和相关的开始和结束索引。遍历整个序列后,结果存储在最大变量中。

为了避免溢出以二进制对数和附加符号计算。

伪代码

maxProduct = 0
maxProductStartIndex = -1
maxProductEndIndex = -1
sequence.push_front( 0 ) // reuses variable intitialization of the case n == 0

for every index of sequence
   n = sequence[index]
   if n == 0
       posProduct = 0
       negProduct = 0
       posProductStartIndex = index+1
       negProductStartIndex = -1
   else
       if n < 0
           swap( posProduct, negProduct )
           swap( posProductStartIndex, negProductStartIndex )
           if -1 == posProductStartIndex // start second sequence on sign change
               posProductStartIndex = index
           end if
           n = -n;
       end if
       logN = log2(n) // as indicated all arithmetic is done on the logarithms
       posProduct += logN
       if -1 < negProductStartIndex // start the second product as soon as the sign changes first
          negProduct += logN
       end if
       if maxProduct < posProduct // update current best solution
          maxProduct = posProduct
          maxProductStartIndex = posProductStartIndex
          maxProductEndIndex = index
       end if
   end if
end for

// output solution

print "The maximum product is " 2^maxProduct "."
print "It is reached by multiplying the numbers from sequence index " 
print maxProductStartIndex " to sequence index " maxProductEndIndex

复杂性

该算法在序列上使用单个循环,因此其 O(n) 乘以循环体的复杂度。 body最复杂的操作是log2。因此,它的 O(n) 是 log2 的复杂度的倍数。一些有界大小的 log2 是 O(1),因此得到的复杂度是 O(n),也就是线性的。

【讨论】:

  • 实际的算术应该使用 egbokul 描述的对数方法来完成,但是至于找到最大的子列表,那么在连续的 0 之间查找绝对是这样做的方法。关键是任何包含 0 的子列表都不能有除 0 以外的产品。此外,不包含 0 的子列表的任何子列表都不能具有更大的乘积绝对值,因为你不能使乘以非零整数,总和更小。您需要进行一些调整来处理负面产品,但可能不会很多。
  • 嗯,maxProductStartIndexmaxProductEndIndex 做过什么吗?我没有看到他们的价值观被使用。
  • @Free Styler。该算法在序列上使用单个循环,因此它的 O(n) 乘以循环体的复杂度。 body最复杂的操作是log2。因此,它的 O(n) 是 log2 的复杂度的倍数。机器大小的 log2 通常是 O(1),因此在这种情况下,产生的复杂度将是 O(n),也就是线性的。 @Carl变量是结果的一部分,算法本身只写入它们。我用结果的显式输出替换了关于结果的结束注释。
  • 啊,我明白了。它们让您打印出开始和结束位置。我没有向下滚动 :)
  • @Free Styler 太好了,你试过了。在纸上执行后,我发现在 +-1 情况下检测第一个符号变化时存在错误。我现在在代码中解决了这个问题。
【解决方案6】:

我想将 Amnon 关于 2 的乘方的观察与我关于子列表的观察结合起来。

列表以 0 硬终止。我们可以将问题分解为在每个子列表中找到最大的产品,然后找到其中的最大值。 (其他人已经提到了这一点)。

这是我对这篇文章的第三次修订。但 3 的魅力...

接近

给定一个非 0 数字的列表,(这是花了很多时间思考的)有 3 个子案例:

  1. 该列表包含偶数个负数(可能为 0)。这是微不足道的情况,最佳结果是所有数字的乘积,保证为正。
  2. 该列表包含奇数个负数,因此所有数字的乘积都是负数。为了改变符号,有必要牺牲一个包含负数的子序列。两个子案例:

    一个。牺牲从左到右的数字,包括最左边的负数;或

    b.牺牲从右到右的数字,包括最右边的负数。

    在任何一种情况下,返回剩余数字的乘积。只牺牲了一个负数,结果肯定是正数。选出 (a) 和 (b) 的获胜者。

实施

输入需要拆分为以 0 分隔的子序列。如果构建驱动程序方法来循环遍历列表并挑选非 0 序列的开头和结尾,则可以就地处理列表。

以多头形式进行数学运算只会使可能的范围增加一倍。转换为 log2 使大乘积的算术运算更容易。它可以防止在大数的大序列上出现程序故障。或者,也可以在 Bignums 中进行所有数学运算,但这可能会表现不佳。

最后,最终的结果,仍然是 log2 数字,需要转换成可打印的形式。 Bignum 在那里派上用场。 new BigInteger("2").pow(log); 将提高 2 的 log 的幂。

复杂性

该算法通过子列表顺序工作,每个子列表只处理一次。在每个子列表中,都有将输入转换为 log2 并将结果返回的烦人工作,但工作量与列表的大小成线性关系。在最坏的情况下,列表的大部分总和被计算了两次,但这也是线性复杂度。

【讨论】:

  • 耗时超过 3 分钟。我会直接在 Java 中完成,但那是作弊:)
【解决方案7】:

查看此代码。在这里,我实现了大量的精确阶乘。我只是使用整数数组来制作大数字。从Planet Source Code下载代码。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2016-12-12
    • 1970-01-01
    相关资源
    最近更新 更多