繁体   English   中英

如何乘以两个大数字

[英]How to multiply two big big numbers

您将获得n个数字L=<a_1, a_2,...a_n> 它们中的每一个都是0或+/- 2 k形式,0 <= k <= 30.描述并实现一个算法,该算法返回连续子列表的最大乘积p=a_i*a_i+1*...*a_j, 1 <= i <= j <= n

例如,对于输入<8 0 -4 -2 0 1>它应返回8(8或(-4)*( - 2))。

您可以使用任何标准编程语言,并且可以假定列表以任何标准数据结构给出,例如int[]vector<int>List<Integer>等。

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

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

“我仍然没有到达我算法的最后骨架,我想知道你是否可以帮我解决这个问题。”

(参见问题描述的问题)

我要做的就是解释Amnon提出的更详细的方法,所以所有的功劳都归功于他。

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

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

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

现在,对于每个子列表,您必须计算其元素的乘积 - 这是一个方法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]);       
}

由于k <= 30,任何整数i = 2 k都适合Java int 然而,这两个整数的乘积可能不一定适合Java int因为2 k * 2 k = 2 2 * k <= 2 60 ,这填补了Java long 这应该回答你关于“(乘以两个数字......)的问题”。

如果您可能希望乘以两个以上的数字,这是由您的任务所暗示的“......连续子列表的最大产品......”(子列表的长度可能> 2),请查看Java的BigInteger类。

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

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

解析输入可能是一个切换案例的问题,因为你只需要30个数字来处理。 加上否定。

那是无聊的部分。 有趣的是如何获得产生最大数量的子列表。 你可以通过检查每个变化来采取愚蠢的方法,但在最坏的情况下(IIRC),这将是一个O(N ^ 2)算法。 这对于更长时间的投入来说真的不太好。

你能做什么? 我可能从列表中最大的非负数开始作为子列表,并增加子列表以尽可能多地在每个方向上获得非负数。 然后,在达到所有正面的情况下,在两边进行负对,例如。 只有你能在名单的两边都成长,才会成长。 如果你无法在两个方向上成长,请尝试使用两个(四个,六个,甚至是偶数)连续负数的一个方向。 如果你不能以这种方式成长,那就停下来吧。

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

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

算法概要

从序列和存储值以及自上次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)乘以循环体的复杂性。 身体最复杂的操作是log2。 因此它的O(n)倍于log2的复杂性。 多个有界大小的log2是O(1),因此得到的复杂度是O(n)又名线性。

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

关于子列表问题,少于n ^ 2对(开始,结束)索引。 您可以检查它们,或尝试动态编程解决方案。

我想将Amnon关于2的倍增权的观察与我的一个关于子列表的观察结合起来。

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

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

途径

给出一个非0数字的列表,(这是需要很多思考的),有3个子案例:

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

    一种。 牺牲从左到右的数字,包括最左边的负数; 要么

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

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

履行

输入需要拆分为由0分隔的子序列。如果构建了一个驱动程序方法来遍历它并选出非0序列的开头和结尾,则可以在适当的位置处理该列表。

长时间进行数学计算只会使可能的范围加倍。 转换为log2可以更轻松地使用大型产品进行算术运算。 它可以防止大数字序列上的程序失败。 或者可以在Bignums中进行所有数学计算,但这可能表现不佳。

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

复杂

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

看到这段代码。 在这里,我实现了一个巨大的数字的精确因子。 我只是使用整数数组来制作大数字。 Planet Source Code下载代码

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM