问题 一些数字之间最大的GCD


我们有一些非负数。我们希望找到最大gcd对。实际上这个最大值比这对更重要! 例如,如果我们有:

2 4 5 15

GCD(2,4)= 2

GCD(2,5)= 1

GCD(2,15)= 1

GCD(4,5)= 1

GCD(4,15)= 1

GCD(5,15)= 5

答案是5。


9760
2019-06-06 00:14:55


起源

(如果有人编辑此内容,请删除标题中的请求。) - greybeard


答案:


有一个解决方案需要O(n):

让我们的数字 a_i。首先,计算 m=a_0*a_1*a_2*...。对于每个数字a_i,计算 gcd(m/a_i, a_i)。您要查找的数字是这些值的最大值。

我没有证明这一直是真的,但在你的例子中,它有效:

m=2*4*5*15=600,

max(gcd(m/2,2), gcd(m/4,4), gcd(m/5,5), gcd(m/15,15))=max(2, 2, 5, 5)=5


注意:这是不正确的。如果是这个号码 a_i 有一个因素 p_j 重复两次,如果另外两个数字也包含这个因素, p_j,那么你得到的结果不正确 p_j^2 绝对的 p_j。例如,对于集合 3, 5, 15, 25, 你得到 25 作为答案而不是 5

但是,您仍然可以使用它来快速过滤掉数字。例如,在上述情况下,一旦确定25,就可以先进行详尽的搜索 a_3=25 同 gcd(a_3, a_i) 找到真正的最大值, 5,然后过滤掉 gcd(m/a_i, a_i), i!=3 小于或等于 5 (在上面的示例中,这会过滤掉所有其他内容)。


添加了澄清和理由

要了解为什么这应该有效,请注意 gcd(a_i, a_j) 分歧 gcd(m/a_i, a_i) 对全部 j!=i

我们打电话吧 gcd(m/a_i, a_i) 如 g_i,和 max(gcd(a_i, a_j),j=1..n, j!=i) 如 r_i。我上面说的是 g_i=x_i*r_i,和 x_i 是一个整数。很明显 r_i <= g_i,所以 n gcd操作,我们得到一个上限 r_i 对全部 i

上述说法不是很明显。让我们更深入地研究一下为什么它是真的:gcd of a_i 和 a_j 是两者中出现的所有主要因素的产物 a_i 和 a_j (根据定义)。现在,成倍增加 a_j 用另一个数字, b。的gcd a_i 和 b*a_j 要么等于 gcd(a_i, a_j),或者是它的倍数,因为 b*a_j 包含所有主要因素 a_j,以及一些更多的主要因素 b,也可以包括在分解中 a_i。事实上, gcd(a_i, b*a_j)=gcd(a_i/gcd(a_i, a_j), b)*gcd(a_i, a_j), 我认为。但我看不出有办法利用这个。 :)

无论如何,在我们的建设中, m/a_i 只是计算所有产品的捷径 a_j,哪里 j=1..1, j!=i。结果是, gcd(m/a_i, a_i) 包含所有 gcd(a_i, a_j) 作为一个因素。因此,显然,这些个别gcd结果的最大值将会分开 g_i

现在,最大的 g_i 我们特别感兴趣:它是最大的gcd本身(如果 x_i 是1),或是一个很好的候选人。为此,我们做另一件事 n-1 gcd操作,并计算 r_i 明确。然后,我们放弃所有 g_j 小于或等于 r_i 作为候选人。如果我们没有剩下任何其他候选人,我们就完成了。如果没有,我们会选择下一个最大的 g_k,并计算 r_k。如果 r_k <= r_i,我们放弃 g_k,并与另一个重复 g_k'。如果 r_k > r_i,我们过滤掉剩余的 g_j <= r_k,并重复。

我认为有可能构造一个数字集,使这个算法在O(n ^ 2)中运行(如果我们没有过滤掉任何东西),但在随机数集上,我认为它将很快摆脱大块的候选人。


0



你有证据吗?
不是证据,不是。只有几个简单的事实,你可以做出上面的NOTE部分的算法。我将编辑答案并添加更多详细信息,说明为什么我认为这应该有效。 - vhallac
我已经编写了一个示例测试程序来查看性能。上述算法确实生成了正确的值,并且在1000到10000范围内有1000个随机数,它执行1999到13000 gcd操作(而不是999000的强力方法)。如果我使用10000个数字,则介于19999和315000 gcd之间。我没有检查10000套的暴力结果。这花了太长时间。 :) - vhallac
@Dysaster:你原来的建议不是O(n)。 n个输入的乘积具有Ω(n)个数字(假设值具有非平凡的分布),因此,对于总Ω(n ^ 2),所执行的n个除法中的每一个本身都是Ω(n)。但有趣的启发式。 - Steve Jessop
@Steve:我不确定我是否跟着你。我们在这里谈论大数字算术的表现吗?如果是这种情况,并且如果数字确实是每个字(例如,32位),那么你是正确的。乘法将生成一个大约n个单词的数字。这是一个我没有考虑过的好点。 n个单词和单个单词之间的gcd的长划分并不像n gcd操作那么慢,但它仍然不是O(n)。 - vhallac


您可以使用欧几里德算法来查找两个数字的GCD。

while (b != 0) 
{
    int m = a % b;
    a = b;
    b = m;
}
return a;

5



我知道这个。但是两个以上呢?我想要一对这些数字之间的最大gcd。例如,如果数字是:2,4,5,10答案是5,但当然计算每对的gcd效率不高
对于两个以上你可以使用递归公式gcd(a [1],...,a [n])= gcd(gcd(a [1],...,a [n - 1]),a [n ])。 - Wildcat
也可能存在一些其他算法。 springerlink.com/content/gm5wjjcaaxx544w1 - Wildcat
这将是一个O(n ^ 2)解决方案。 GCD每对数字,保持最高发现。我认为答案是提高效率。 - Ishtar
@kemiisto - 我认为你的算法将返回最小值,而不是最大值。 - kunigami


如果你想要替代明显的算法,那么假设你的数字在有界范围内,并且你有足够的内存,你可以超过O(N ^ 2)时间,N是值的数量:

  • 创建一个小整数类型的数组,索引1到最大输入。 O(1)
  • 对于每个值,增加索引的每个元素的计数,这是数字的一个因子(确保不进行环绕)。上)。
  • 从数组末尾开始,向后扫描,直到找到值> = 2. O(1)

这告诉你最大gcd,但不告诉你哪一对产生了它。对于您的示例输入,计算出的数组如下所示:

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
4 2 1 1 2 0 0 0 0 0  0  0  0  0  1

我不知道这对你必须处理的输入来说是否真的更快。涉及的常数因素很大:您的值的界限以及在该界限内分解值的时间。

您不必将每个值分解 - 您可以使用memoisation和/或预生成的素数列表。这给了我一个想法,如果你记住因子分解,你不需要数组:

  • 创建一组空的int,以及最好的值1。
  • 对于每个输入整数:
    • 如果它小于或等于最佳 - 那么,继续。
    • 检查它是否在集合中。如果是这样,那么最好的那么远=最大(最好的,最远的,这个值),继续。如果不:
      • 将它添加到集合中
      • 重复所有因素(大于最佳 - 迄今为止)。

虽然它取决于您使用的数据结构,但在集合中添加/查找可能是O(日志N)。每个值都有O(f(k))因子,其中k是最大值,我不记得函数f是什么...

在集合中遇到值时,您完成值的原因是您找到了一个数字,它是两个输入值的公因子。如果你保持因子分解,你只能找到较小的这样的数字,这些数字并不有意思。

我不太清楚重复更大因素的最佳方法是什么。我认为在实践中你可能需要取得平衡:你不想按降序顺序完成它们,因为生成有序因子很尴尬,但你也不想真正找到所有因素。

即使在O(N ^ 2)的领域,你也许能够击败欧几里德算法的使用:

将每个数字完全分解,将其存储为素数的指数序列(例如,2为{1},4为{2},5为{0,0,1},15为{0,1,1}} 。然后你可以通过获取每个索引的最小值并将它们相乘来计算gcd(a,b)。不知道这是否比欧几里德平均快,但可能是。显然它使用了更多的内存。


4



这是一个很好的解决方案!
绑定应该是一个变量 M。第二步有复杂性 O(N*M)。因此,如果您的列表相对于您的范围内的整数数量很长,这只是一个好主意。但这意味着必须重复相同的数字,你可能更好的基数排序和寻找重复(O(N+M))! - Rex Kerr
实际上,因为GCD通常需要 log(M) 时间,我猜第二步有复杂性 O(N*M) 代替 O(N*N*log(M)),所以这是一个很好的策略 M/log(M) < N。 - Rex Kerr
一个数的除数 k 受到约束 2*sqrt(k) (不确定它是否紧张)。 - kunigami
进一步的优化是对输入进行排序,这样您就可以在达到小于最佳效果的数字时停止算法。 - kunigami


我能想到的优化是

1)从两个最大的数字开始,因为它们可能具有最多的素数因子,因此可能具有最共享的素数因子(因此是最高的GCD)。

2)当计算其他对的GCD时,如果低于当前最大GCD,则可以停止欧几里德算法循环。

在我的脑海中,我无法想到一种方法,你可以计算出一对中最大的GCD,而不是试图单独计算每一对(并如上所述进行优化)。

免责声明:我以前从未考虑过这个问题,而且上面的内容已经脱离了我的脑海。可能有更好的方法,我可能是错的。如果有人想要,我很乐意更详细地讨论我的想法。 :)


3



谢谢!这些真的很有帮助。


没有 O(n log n) 一般来说解决这个问题。事实上,最糟糕的情况是 O(n^2) 在列表中的项目数。考虑以下一组数字:

2^20 3^13 5^9 7^2*11^4 7^4*11^3

只有最后两个的GCD大于1,但从查看GCD知道这一点的唯一方法是尝试每一对并注意其中一个大于1。

因此,当你已经找到一个大的GCD(同时确保你不会错过任何东西)时,你可能会坚持使用无聊的暴力尝试每对方法,或者进行一些巧妙的优化以避免做不必要的工作)。


1



谢谢。很有帮助
“......但是通过观察GCD知道这一点的唯一方法是......”,但我们不必看它们,所以我们可以击败 O(n log n)。计算最大的GCD并不意味着要查看每个GCD。就像有更快的排序算法 O(n log n) - Ishtar
@Ishtar - 不幸的是,没有明显的假设会让你做得比看每一个GCD都好。有一些不完全不合理的假设可以让你做得更好(例如,列表中数字的最大大小, M,比不上,比如, N^(3/2) 平均而言),但没有说明这些假设。对于一般算法,其中列表是非巨大的,并且数字的大小通常大于列表长度,我的例子成立。 - Rex Kerr
如果算术在O(1)中完成,你可以检查是否有两个数字a,b with gcd(a,b)!=1 在O(n)gcds。计算所有数字的乘积 P,并检查是否有这样的 gcd(P/a_i,a_i) != 1.如果是,请进行线性搜索 j 对于 gcd(a_i,a_j) != 1。然而,我没有看到如何利用这个技巧以比Theta(n ^ 2)时间更快的速度找到最大GCD。 - sdcvvc


对于某些约束,例如数组中的数字在给定范围内,比如1-1e7,它在O(NlogN)/ O(MAX * logMAX)中是可行的,其中MAX是A中的最大可能值。

灵感来自筛选算法,并在一个 Hackerrank挑战赛  - 在那里完成了两个阵列。检查他们的社论。

  • 找到min(A)和max(A) - O(N)
    对于O(1)查找,创建二进制掩码,以标记A的哪些元素出现在给定范围内; O(N)建造; O(MAX_RANGE)存储。
  • 对于范围内的每个数字a(min(A),max(A)):
    对于aa = a; aa <max(A); aa + = a:
    • 如果aa在A中,则增加aa的计数器,并将其与当前的max_gcd进行比较,如果counter> = 2(即,你有两个可被aa整除的数字);
    • 为每个GCD候选人储存前两名候选人。
    • 也可以忽略小于当前max_gcd的元素;

上一个答案: 仍为O(N ^ 2) - 对数组进行排序;应该消除一些不必要的比较;

max_gcd = 1
# assuming you want pairs of distinct elements.
sort(a) # assume in place
for ii = n - 1: -1 : 0 do
    if a[ii] <= max_gcd
        break
    for jj = ii - 1 : -1 :0 do
        if a[jj] <= max_gcd 
            break
        current_gcd = GCD(a[ii], a[jj])
        if current_gcd > max_gcd:
            max_gcd = current_gcd

这应该节省一些不必要的计算。


1



这是n * n算法 - anshul garg
谢谢!确实,仍然在N ^ 2。 - Mircea


伪代码

function getGcdMax(array[])

    arrayUB=upperbound(array)
    if (arrayUB<1)
        error
    pointerA=0
    pointerB=1

    gcdMax=0

    do
        gcdMax=MAX(gcdMax,gcd(array[pointera],array[pointerb]))
        pointerB++
        if (pointerB>arrayUB)
            pointerA++
            pointerB=pointerA+1
    until (pointerB>arrayUB)

    return gcdMax

0



感谢你写了伪代码,但我相信你说的很明显。我正在寻找一个更快的解决方案。像O(nlgn)一样,O(n ^ 2)并不难!
是的我知道这是暴力。您没有在帖子中指定任何速度要求:) - El Ronnoco
是啊!但我问了一个有效的方法!