计算序列中尾随零的数量

Calculate the number of trailing zeroes in a sequence

考虑序列,其中 s(0)s(1) 是输入,s(n) = s(n-1) * s(n-2) 是所有 n >= 2。我想找到 s(n) 中尾随零的数量。我们可以假设如下:

下面是我的代码尝试。当 n 大于 30(它运行了很长时间)时,它不是 运行。有没有其他方法可以计算尾随零的数量?

public class Soroco {

  public static void main(String[] args) throws IOException {
    BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
    BigInteger n = new BigInteger(br.readLine());
    BigInteger s0 = new BigInteger(br.readLine());
    BigInteger s1 = new BigInteger(br.readLine());
    BigInteger num = s(n, s0, s1);
    System.out.println(num);
    System.out.println(countTrailingZeroes(num));
  }

  static BigInteger s(BigInteger n, BigInteger s0, BigInteger s1) {
    if (n.equals(new BigInteger("0")))
        return s0;
    else if (n.equals(new BigInteger("1")))
        return s1;
    else {
        BigInteger n1=n.subtract(new BigInteger("1"));
        BigInteger n2=n.subtract(new BigInteger("2"));
        BigInteger n3=s(n1, s0, s1).multiply(s(n2, s0, s1));
        return n3;
    }
  }

  static int countTrailingZeroes(BigInteger num) {
    String str = String.valueOf(num);
    int count = 0;
    for (int i = 0; i < str.length(); i++)
        if (str.charAt(i) == '0')
            count++;
    return count;
  }
}

不需要执行整个乘法,您只需要跟踪 2 和 5 的因数。如果一个数字可以写成 N = 2^a * 5^b * (factors other than 2 or 5),那么 [=15= 中尾随零的数量] 是 min(a, b)。 (这是因为尾随零只是 10 的因数,需要一个 2 和一个 5。)

请注意,乘法将因子的指数加在一起。所以,如果你可以写:

s(n-2) = 2^a * 5^b * (factors other than 2 or 5)
s(n-1) = 2^c * 5^d * (factors other than 2 or 5)

那么我们有:

s(n) = s(n-1) * s(n-2)
     = 2^(a+c) * 5^(b+d) * (factors other than 2 or 5)

因此,我们可以把这个问题看成两个Fibonacci sequences。您从 s(0)s(1) 中的 2 和 5 的数量开始,并以斐波那契数列方式计算 s(2), s(3), ..., s(n) 中的 2 和 5 的数量:

#2s in s(n) = (#2s in s(n-1)) + (#2s in s(n-2))
#5s in s(n) = (#5s in s(n-1)) + (#5s in s(n-2))

最后,尾随零的数量是min(#2s in s(n), #5s in s(n))


上述算法(如果用循环或记忆递归实现)是O(n)。您的尝试在 n 中呈指数增长,这就是为什么即使 n = 30 也需要很长时间才能达到 运行。我并不是要 bash 你的尝试,但理解这些错误是有好处的——你的代码很慢主要有两个原因:

首先,将非常大的整数完全精确地相乘(就像您对 BigInteger 所做的那样)非常慢,因为每次乘法的位数都会加倍。如果您只关心尾随零的数量,则不需要完全精确。

其次,忽略乘法成本,s 的递归实现仍然是指数时间,但不一定是。请注意,您多次计算相同的值——s(n-2) 是针对 s(n)s(n-1) 分别计算的,但 s(n-2) 的值显然相同。诀窍是 memoize the recursion 通过记住以前计算的结果来避免重新计算。或者,您可以使用循环计算类似斐波那契的序列:

// Computes the n-th Fibonacci number in O(n) time
int[] fib = new int[n + 1];
fib[0] = 0;
fib[1] = 1;
for (int i = 2; i <= n; i++)
    fib[i] = fib[i-1] + fib[i-2];
return fib[n];

这是一种比记忆化递归更简单的方法,至少对于这个问题而言。

public static void main(String[] args) throws IOException {

    BufferedReader br = new BufferedReader(new InputStreamReader(System.in));

    int n = Integer.parseInt(br.readLine());

    int s0 = Integer.parseInt(br.readLine());

    int s1 = Integer.parseInt(br.readLine());

    int num21 = findNumberOf2s(s0);

    int num22 = findNumberOf2s(s1);

    int num51 = findNumberOf5s(s0);

    int num52 = findNumberOf5s(s1);

    int arr2[] = new int[n + 1];
    arr2[0] = num21;
    arr2[1] = num22;
    for (int i = 2; i <= n; i++)
        arr2[i] = arr2[i - 1] + arr2[i - 2];

    int arr5[] = new int[n + 1];
    arr5[0] = num51;
    arr5[1] = num52;
    for (int i = 2; i <= n; i++)
        arr5[i] = arr5[i - 1] + arr5[i - 2];

    System.out.println(Math.min(arr2[n], arr5[n]));

}

static int findNumberOf2s(int num) {
    int num2 = 0;
    while (num % 2 == 0) {
        num = num / 2;
        num2++;
    }
    return num2;
}

static int findNumberOf5s(int num) {
    int num5 = 0;
    while (num % 5 == 0) {
        num = num / 5;
        num5++;
    }
    return num5;
}