您将获得n个数字L=<a_1, a_2,...a_n>
的列表。他们每个人都是
0或者+/- 2 k 形式,0 <= k <= 30。描述并实现
返回CONTINUOUS SUBLIST的最大乘积的算法
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>
等
算法的计算复杂度是多少?
答案 0 :(得分:6)
在我的第一个回答中,我解决了OP“两个大数字倍增”的问题。事实证明,这个愿望只是我要解决的一个更大问题的一小部分:
“我还没有到达算法的最终骨架,我想知道你是否可以帮我解决这个问题。”
(参见问题描述的问题)
我要做的就是更详细地解释the approach Amnon proposed,所以所有的功劳都归功于他。
你必须从2的幂的整数列表中找到连续子列表的最大乘积。想法是:
您可以通过其start
和end
索引表示子列表。对于start=0
,end
有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]);
}
答案 1 :(得分:4)
由于k <= 30,任何整数i = 2 k 都适合Java int
。然而,这两个整数的乘积可能不一定适合Java int
,因为2 k * 2 k = 2 2 * k &lt; = 2 60 ,填入Java long
。这应该回答你关于“(乘以两个数字......)的问题。”
如果你想要乘以两个以上的数字,这是你的作业所说的“......连续子列表的最大产品......”(一个子列表的长度可能> 2),看看Java的BigInteger
类。
答案 2 :(得分:4)
实际上,最有效的乘法方式是做加法。在这种特殊情况下,你拥有的是2的幂数,你可以通过简单地将指数加在一起来获得子列表的乘积(并计算产品中的负数,并在奇数的情况下将其作为负数)底片)。
当然,要存储结果,如果用完了比特,可能需要BigInteger。或者根据输出的外观,只需说(+/-)2 ^ N,其中N是指数的总和。
解析输入可能是一个切换案例的问题,因为你只需要30个数字来处理。再加上否定因素。
这是无聊的部分。有趣的是如何获得产生最大数量的子列表。你可以通过检查每个变化来采取愚蠢的方法,但在最坏的情况下(IIRC),这将是一个O(N ^ 2)算法。这对于更长时间的输入来说真的不太好。
你能做什么?我可能从列表中最大的非负数开始作为子列表,并增加子列表以尽可能多地在每个方向上获得非负数。然后,在达到所有正面的情况下,在两边进行负对,例如。只有你能在名单的两边都成长,才会成长。如果你无法在两个方向上成长,请尝试使用两个(四个,六个,甚至是偶数)连续负数的一个方向。如果你不能以这种方式成长,请停止。
好吧,我不知道这个算法是否有效,但是如果它(或类似的东西)做了,它的O(N)算法,这意味着很好的性能。让我们试一试! : - )
答案 3 :(得分:3)
编辑:我调整了算法大纲以匹配实际的伪代码,并将复杂性分析直接放入答案:
算法概要
在序列和存储值以及产品的第一个/最后一个索引(正数)之后的顺序执行自上一个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)又称线性。
答案 4 :(得分:3)
嗯..因为它们都是2的幂,你可以添加指数而不是乘以数字(相当于取产品的对数)。例如,2 ^ 3 * 2 ^ 7是2 ^(7 + 3)= 2 ^ 10。 我会把这个标志作为练习留给读者。
关于子列表问题,少于n ^ 2对(开始,结束)索引。您可以全部检查,或尝试dynamic programming解决方案。
答案 5 :(得分:1)
我想将Amnon关于2的倍增权的观察与我的一个关于子列表的观察结合起来。
列表以0结尾。我们可以将问题分解为找到每个子列表中最大的产品,然后是最大的产品。 (其他人已提到这一点)。
这是我对本文的第3次修订。但是3的魅力......
<强>方法强>
给出一个非0数字的列表(这需要经过深思熟虑),有3个子案例:
该列表包含奇数个负数,因此所有数字的乘积都为负数。要改变符号,有必要牺牲包含负数的子序列。两个子案例:
一个。牺牲从左到右的数字,包括最左边的负数;或
湾牺牲从右到右的数字,包括最右边的负数。
在任何一种情况下,都要返回剩余数字的乘积。在牺牲了一个负数之后,结果肯定是正面的。选择(a)和(b)的获胜者。
<强>实施强>
需要将输入拆分为由0分隔的子序列。如果构建了一个驱动程序方法来遍历它并选择非0序列的开头和结尾,则可以在适当的位置处理该列表。
以long为单位进行数学计算只会使可能的范围加倍。转换为log2可以更轻松地使用大型产品进行算术运算。它可以防止大数字序列上的程序失败。或者可以在Bignums中进行所有数学计算,但这可能表现不佳。
最后,最终结果仍然是log2号,需要转换为可打印的形式。 Bignum在那里派上用场。有new BigInteger("2").pow(log);
会使log
的力量增加2。
<强>复杂性强>
此算法按顺序通过子列表工作,仅处理每一个。在每个子列表中,将输入转换为log2和返回结果是令人烦恼的工作,但是工作量与列表的大小呈线性关系。在最坏的情况下,列表的大部分总和被计算两次,但这也是线性复杂度。
答案 6 :(得分:1)
请参阅此代码。在这里,我实现了一个巨大的数字的精确因子。我只是使用整数数组来制作大数字。从Planet Source Code下载代码。