有效地找到给定数字的最小幂

时间:2016-02-20 23:13:42

标签: java

根据号码X,找到号码AB,以便X = A^BA是最小的号码。 B小于301。

String[] power2(String X) {
    double n = Double.parseDouble(X);
    double i = 1;
    double j = 1;
    String[] result = {"1", "1"};
    boolean flag = false;
    if (n > 1) {
        for (i = 1; i < n; i++) {
            for (j = 1; j < 301; j++) {
                if (Math.pow(i, j) == n) {
                    flag = true;
                    break;
                }
            }
            if (flag) {
                break;
            }
        }
    }
    result[0] = i + "";
    result[1] = j + "";
    return result;
}

这种方法很好但效率不高。我正在寻找更快的算法。

2 个答案:

答案 0 :(得分:1)

有几件事:

  1. 您当前的解决方案有效。在b = 1时,您不会考虑案例,例如100003 = 100003 ^ 1,但您的函数返回[100003.0, 301.0]。由于双精度损失,您的函数不会返回10000000000600000000009 = 100000000003 ^ 2的有效结果。它将持续增加基数很长一段时间,直到它最终返回错误的结果。
  2. 我认为数字是整数。如果它们不是整数,那么我们有无限多种组合,而且我们有比较问题。
  3. 我们将在非常大的数字上运作。 2 ^ 301是一个90位数的数字。因此BigInteger是唯一的选择。不幸的是,这将降低性能。
  4. 您当前最大的问题是即使当前结果大于x,您也会继续内循环(增加指数)。如果x = 9然后a = 2b = 4我们已经16,那么我们可以停止增加b并增加a代替(并重置{{} 1}}到1)。检查b后,您的功能还会检查2 ^ 42 ^ 5和另外300个。
  5. 以下是我提出的建议:

    2 ^ 6

    对于低于20亿的价值,它可能会更慢,但它能够处理任何规模的数字。

    可以做的进一步优化:

    • 只要private static final int MAX_B = 300; public String[] power2(BigInteger x) { increaseBase: for (BigInteger a = BigInteger.ONE; a.compareTo(x) <= 0; a = a.add(BigInteger.ONE)) { for (int b = 2; b <= MAX_B; b++) { BigInteger result = a.pow(b); if (result.equals(x)) { return new String[] {String.valueOf(a), String.valueOf(b)}; } if (result.compareTo(x) == 1) { continue increaseBase; } } } return new String[] {String.valueOf(x), "1"}; } 而不是a
    • ,就会增加a < roundDown(sqrt(x))
    • 恢复循环并从大指数开始,这将允许通过检查结果的接近a < x
    • 来修改不同于1的值。

    另一个可能实际上最有效的想法是找到x的素数因子。如果我们知道素数因子为x,那么我们可以轻松计算出2, 2, 2, 2, 13, 13a = 2 * 2 * 13

    修改

    这是我的素因因素的实现。底部的测试和导入。

    b = 2

    指数的最大公约数是结果的最终指数。如果我们有数字public class PowerCalculator { public String[] power2(String x) { @SuppressWarnings("MismatchedQueryAndUpdateOfCollection") // update via increaseCountFor method PrimeFactorToCount primeFactorToCount = new PrimeFactorToCount(); BigInteger number = new BigInteger(x); BigInteger divisor = new BigInteger("2"); while (!number.equals(BigInteger.ONE)) { if (number.remainder(divisor).equals(BigInteger.ZERO)) { number = number.divide(divisor); primeFactorToCount.increaseCountFor(divisor); } else { if (gcd(primeFactorToCount.values()) == 1) { return new String[] {x, "1"}; } divisor = divisor.add(BigInteger.ONE); } } int gcd = gcd(primeFactorToCount.values()); return primeFactorToCount.entrySet().stream() .map(entry -> new Exponentiation(entry.getKey(), entry.getValue())) .map(exponentiation -> new Exponentiation(exponentiation.base.pow(exponentiation.exponent / gcd), gcd)) .reduce((a, b) -> new Exponentiation(a.base.multiply(b.base), gcd)) .map(Exponentiation::asStringArray) .get(); } private static int gcd(Collection<Integer> values) { return values.stream() .mapToLong(v -> v) .mapToObj(BigInteger::valueOf) .reduce(BigInteger::gcd) .map(BigInteger::intValue) .orElse(0); } private class Exponentiation { private final BigInteger base; private final int exponent; private Exponentiation(BigInteger base, int exponent) { this.base = base; this.exponent = exponent; } private String[] asStringArray() { return new String[] {String.valueOf(base), String.valueOf(exponent)}; } } private class PrimeFactorToCount extends HashMap<BigInteger, Integer> { private void increaseCountFor(BigInteger primeFactor) { if (containsKey(primeFactor)) { put(primeFactor, get(primeFactor) + 1); } else { put(primeFactor, 1); } } } } @RunWith(Parameterized.class) public class PowerCalculatorTest { private final uk.co.jpawlak.maptoobjectconverter.PowerCalculator powerCalculator = new uk.co.jpawlak.maptoobjectconverter.PowerCalculator(); @Parameterized.Parameters(name = "{index}: returns {1} for {0}") public static Collection data() { return asList(new Object[][] { {input(3), expected(3, 1)}, {input(7 * 7), expected(7, 2)}, {input(2 * 2 * 2), expected(2, 3)}, {input(3 * 3 * 5 * 5), expected(3 * 5, 2)}, {input(3 * 3 * 5 * 5 * 5 * 5), expected(3 * 5 * 5, 2)}, {input(3 * 3 * 3 * 3 * 3 * 3 * 5 * 5), expected(3 * 3 * 3 * 5, 2)}, {input(2 * 3 * 3), expected(2 * 3 * 3, 1)}, }); } private final String input; private final String[] expected; public PowerCalculatorTest(String input, List<String> expected) { this.input = input; this.expected = expected.stream().toArray(String[]::new); } @Test public void test() { String[] actual = powerCalculator.power2(input); assertThat(actual, sameBeanAs(expected)); } private static String input(long input) { return String.valueOf(input); } private static List<String> expected(long base, long exponent) { return ImmutableList.of(String.valueOf(base), String.valueOf(exponent)); } } import com.google.common.collect.ImmutableList; import org.junit.Test; import org.junit.runner.RunWith; import org.junit.runners.Parameterized; import java.math.BigInteger; import java.util.Collection; import java.util.HashMap; import java.util.List; import static com.shazam.shazamcrest.MatcherAssert.assertThat; import static com.shazam.shazamcrest.matcher.Matchers.sameBeanAs; import static java.util.Arrays.asList; ,则最终指数只能是108 = 2 ^ 2 * 3 ^ 3。这就是为什么当我们要检查下一个除数时在循环中检查这个 - 所以它“快速失败”。这也是在return语句中1用作指数的原因。我们来看看编号5184:

    gcd

    6和4的最大公约数是2。

    可以做的进一步优化是找到数字的主要因素。如果你在5184 = 2^6 * 3^4 = (2^3)^2 * (3^2)^2 = 8^2 * 9^2 = (8*9)^2 = 72^2等大素数的平方上尝试过它,那么在找到第一个素数因子之前,循环必须进行超过10亿次迭代。然而,这是完全不同的问题,所以我不会在这里描述它。

答案 1 :(得分:0)

试试这个

long[] power2(long x) {
    double logX = Math.log(x);
    for (int b = 300; b > 1; --b) {
        double a = Math.exp(logX / b);
        if (Math.abs(a - Math.round(a)) < 0.0005)
            return new long[] { Math.round(a), b };
    }
    return new long[] { x, 1 };
}