如何优化此算法以处理大整数?

时间:2018-08-11 17:26:56

标签: java algorithm optimization

我有两个整数x和y。

规则:

  1. 如果x> y:x = x-y且y = 2 * y;
  2. 如果y> x:y = y-x和x = 2 * x;
  3. 如果y == x:不是无限循环

问题是,这两个整数是否为无限循环?

这是我的代码:

private static boolean IsPairLoop(int first, int second)
{
    boolean loop = false;
    HashMap<Integer, Integer> round_record = new HashMap<>();
    int[] first_round = new int[]{first, second};

    while ((first_round[0] != -1 && first_round[1] != -1))
    {
        if (round_record.containsKey(first_round[0]))
        {
            loop = true;
            break;
        }
        round_record.put(first_round[0], first_round[1]);
        PlayRound(first_round);
    }
    return loop;
}

private static void PlayRound(int[] round)
{
    if (round[0] > round[1])
    {

       round[0] -= round[1];
       round[1] += round[1];

    }
    else if (round[0] < round[1])
    {

       round[1] -= round[0];
       round[0] += round[0];

    }
    else
    {
        round[0]  = -1;
        round[1]  = -1;
    }
}

这对于小整数很好用。但是,当整数差很大时,这会非常缓慢。 x和y的整数范围均为1到2 ^ 30。即使整数差异很大,我该怎么做才能使速度更快?

3 个答案:

答案 0 :(得分:3)

在此问题中,总和x + y是不变的,如果它是奇数,则x == y是不可能的。

然后,如果xy具有相同的奇偶校验,则在一次迭代之后,它们都变为偶数并保持不变,如果将两者都除以2,问题就不会改变。

因此是“即时”解决方案:

while (x + y) & 1 == 0:
    if x == y:
        print "Not infinite"
        break
    if x > y:
        x= (x - y) / 2
    else:
        y= (y - x) / 2
else:
    print "Infinite"

由于其中一个自变量在每次迭代中都会损失至少一位,因此对于32位整数,迭代次数绝不会超过64次(实际上,更少的情况是,大多数情况下为0!)。


可能存在变体,并且对大数有意义:

  • 如果x == y,则为有限结论。

  • 如果xy的尾随零个数不同,则得出无限大结论。

  • 否则,丢弃尾随的零并根据x > yy > x进行归约并循环。

答案 1 :(得分:1)

使用Floyd的循环检测算法,而不是使用哈希图。这样不仅可以避免占用大量内存,还可以避免在intInteger之间进行昂贵的装箱和拆箱。

第二个优化是通过更改变量来重写递归关系:

s = x+y
t = x-y

则递归关系变为:

if t=0, stop
if t>0, s'=s, t'=2t-s
if t<0, s'=s, t'=2t+s

请注意,在此公式中,仅t变量会更改。

代码(未经测试)将如下所示:

private static int step(int s, int t) {
    if (t>0) return 2*t - s;
    if (t<0) return 2*t + s;
    return 0;
}

private static boolean IsPairLoop(int first, int second) {
    int s = first+second;
    int t_slow = first-second;
    int t_fast = t_slow;
    while(t_slow != t_fast) {
        t_slow = step(s, t_slow);
        t_fast = step(s, step(s, s_fast));
    }
    return t_slow != 0;
}

如果int可能溢出,则可能需要用long替换first+second

认为,在满足前提条件的情况下,您永远不会遇到t变为无穷大的情况(因为|t| < s总是如此)。但是您可能希望仔细检查,也许在代码中添加某种断言。

答案 2 :(得分:1)

让我们往后一点点。什么可以产生x == y?前一个x和y之间的差必须等于两者中较小者的两倍,即较大者必须是较小者的三倍。到目前为止,不会无限循环的事情:

  • {n,n}
  • {n,3n}

{n,3n}可以从哪里来?要么

  • n是一些a> b的差a − b,并且3n = 2b
    3(a-b)= 2b
    3a-3b = 2b
    3a = 5b
    a = 5/3 b

    一对{m,5/3 m}会在下一步产生{n,3n}。 (m必须被3整除,但是没关系。)

  • 3n对于a> b来说是a − b的差,并且n = 2b
    (a − b)/ 3 = 2b
    a − b = 6b
    a = 7b

    另一对{m,7m}是下一步可以产生{n,3n}的其他东西。

更新的列表:

  • {n,n}
  • {n,3n}
  • {n,7n}
  • {n,5/3 n}

似乎是总结这些最后步骤的好时机。

{n,qn}在以下情况下发生:

  • n是一些a> b的差a − b,并且qn = 2b
    q(a − b)= 2b
    qa − qb = 2b
    qa =(2 + q)b
    a =(2 + q)/ q b

  • 对于某些a> b,
  • 或qn是a − b的差,并且n = 2b
    (a − b)/ q = 2b
    a − b = 2qb
    a =(2q +1)b

因此,如果q = m / n在列表中,则它们也在列表中:

  • (2n + m)/ m
  • (2m + n)/ n

q = 1生成:

  • (2 +1)/ 1 = 3
  • 2×1 +1 = 3

q = 3生成:

  • 3
  • (2 + 3)/ 3 = 5/3
  • 2×3 +1 = 7

q = 5/3生成:

  • 3
  • 5/3
  • 7
  • (2 + 5/3)/(5/3)=(6 + 5)/ 5 = 11/5
  • (2×5/3)+1 = 10/3 +1 = 13/3

q = 7生成:

  • 3
  • 5/3
  • 7
  • 11/5
  • 13/3
  • (2 + 7)/ 7 = 9/7
  • 2×7 +1 = 15

嗯...这很有趣。让我们按分子对列表进行排序:

  • 3/1
  • 5/3
  • 7/1
  • 9/7
  • 11/5
  • 13/3
  • 15/1

基于此模式,我希望接下来会是17/15。用计算机生成按分母排序的列表:

3/1
7/1
15/1
31/1
63/1
5/3
13/3
29/3
61/3
11/5
27/5
59/5
9/7
25/7
57/7
23/9
55/9
21/11
53/11
19/13
51/13
17/15
49/15
47/17
45/19
43/21
41/23
39/25
37/27
35/29
33/31

看起来很像m / n,其中n为奇数,m> n,而m + n为2的幂。因此,一种优化算法的方法是:

private static boolean isPairLoop(int first, int second)
{
    if (first == second) return false;
    if (first > second) return isPairLoop(second, first);
    if (first == 0) return true;

    int d = gcd(first, second);
    return Integer.bitCount(first / d + second / d) != 1;
}

private static int gcd(int a, int b)
{
    return b == 0 ? a : gcd(b, a % b);
}

对bigints的位数取二次时间。

现在,您只需要证明它可以工作。 我希望它能起作用。