优化数字< = 2算法(类似于Project Euler 303)

时间:2012-01-09 01:17:36

标签: java algorithm optimization

我正在创建一个解决Project Euler Problem 303的程序 我找到f(n)的方法几乎没有蛮力:

static BigInteger findFn(int n){
    Long Fn = new Long(n); 
    String test = Fn.toString();
    Long multiplier = new Long("1"); 
    long counter = 0;
    boolean done = false; 
    BigInteger fn = new BigInteger("0");
    while(!done){
        counter = 0; 
        BigInteger tempOne = new BigInteger(multiplier.toString());
        BigInteger tempTwo = new BigInteger(Fn.toString());
        BigInteger product = tempOne.multiply(tempTwo);
        test = product.toString();
        for(int i = 0; i < test.toString().length(); i++){
            if(Character.getNumericValue(test.toString().charAt(i)) <= 2){
                counter++;
            }else{
                             //Is it better to set done = true here as opposed to breaking?
                break; //breaks if it encounters any number in the multiple >2. 
            }

        }

        if(counter == test.length()){
            fn = product;
            done = true;
            break;              
        }
        multiplier++;
    }
    return fn;
}

它适用于大多数数字,但有一些(通常是那些以9结尾的那些)它会被卡住。 我认为BigIntegers会降低它的速度,所以首先,我是否曾在任何地方使用BigInteger而没有必要? 其次,必须有一种替代方法或某种其他技巧来减少我没有想到的循环次数。 有什么想法让我朝着正确的方向努力?

谢谢!

7 个答案:

答案 0 :(得分:2)

我想你可以通过查看测试数字中的数字来减少67%的试验,因为如果不是0,1或2那么无关紧要剩下的就是。

考虑如果数字以1结尾,那么它乘以的数字必须以0,1或2结尾,以便结果的最后一位数字<= 2.所以你测试1然后2,如果那些不起作用,那么你测试10,11,12,然后20,21,22。所以如果测试数字以1结束,你现在已经减少了70%的试验。

对于XXX2,乘数必须以0,1,5或6结束。这将删除60%。你可以继续3-9。

答案 1 :(得分:1)

尝试另一种方式呢?

我通过从最小到大来生成由0,1和2组成的数字来解决这个问题,并查看这个数字是谁的倍数。此外,我已经使用了9,99等模式。(顺便说一下,除了它们之外你不需要BigInteger。因为你会预先计算它们,摆脱BigInteger)但我发现它们的值就像你的暴力方法一样通过递增如前所述,感兴趣的数字的倍数。结果在大约6秒内弹出。如果您想查看我的解决方案here it is

答案 2 :(得分:0)

test是一个字符串,因此无需在其上调用toString()。不是一个很大的改进,但它使代码更清洁。

这是一个完全避免BigInteger的算法。

For each multiplier
  Set carry = 0
  Set multiple = ""
  Iterate through the digits of the multiplier from right to left
    Set temp = digit * n + carry
    if (right-most digit of temp > 2) 
      break and go to next multiplier
    else
      Set carry = temp / 10                       // drop last digit and carry result

这基本上是一个很长的乘法,只要一个数字&gt;就有机会突破它。找到2。请注意,为了解决这个问题,我们实际上不需要获得F(n),只需要F(n)/n这是上面的数字迭代完成的第一个乘数。然后将每个n的最小乘数相加。

如果没有实际尝试,我很确定这只会以int值运行。

<强>更新

所以我一直在玩一些代码,并让它适用于1 <= n <= 100。看起来像n = 999&gt;的乘数2 ^ 31,所以我们需要使用至少64位的值。不确定乘数是多少。我的代码在LinqPad上运行超过21分钟,已经过了3.2 * 10 ^ 9但还没有结果。当然,我的代码可能存在问题。

答案 3 :(得分:0)

在前进的过程中尝试保持结果。您可以使用它们来进行倍数的下限猜测。

假设某些X的可接受的LCM是Y,Y = RX。然后你知道Y对于所有{X,2X,3X,4X,......(R-1)X}也是一个下界。

答案 4 :(得分:0)

不确定以下想法是否会有所帮助:

  
      
  • 允许P(N, M) = M基数10的数字为(<= 2)MN的倍数。
  •   
  • G(N) =某些M使P(N, M)
  •   
  • F(N) = M使P(N, M)M最小化。

         

    重要观察:F(N) <= G(N)

         

    希望G(N)“合理”,因为它不是无偿大而且易于计算。您可以使用F(N') N' N N(可能是G(N)的数字)来有效地计算它。

         

    了解G(N)可能非常有用......   也许你可以用它来倒退。   也许你可以用它进行某种二进制搜索。

         

    如果这种技术非常有用,我想难以找到{{1}}。其余的可能是一些聪明的计算机科学技术。

  •   

答案 5 :(得分:0)

要减少迭代次数,您可以将试验产品增加1而不是将试验乘数递增1.然后检查试验产品是否可以被f()的参数整除。这样您就可以更快地获得更大的值,因为在添加1时,您可以跳过产品中的数字值4到9。

以下C代码在2.4 GHz PC上完成不到5分钟:

#include <stdio.h>
#include <string.h>
#include <assert.h>
#include <stddef.h>
#include <time.h>

typedef unsigned uint;
typedef unsigned char uint8;
typedef unsigned long long uint64;

uint64 f(uint n, uint64* multiplier)
{
  uint8 carry, digits[20]; // 20 digits max for 64-bit values
  uint digcnt = 1, i;
  uint64 result;

  assert(n > 0);

#if 0
  // short cut:
  //
  // f(9) = 12222 = 9 * 1358
  // f(99) = 1122222222 = 99 * 11335578
  // f(999) = 111222222222222 = 999 * 111333555778
  // f(9999) = 11112222222222222222 = 9999 * 1111333355557778

  if (n == 9999)
  {
    *multiplier = 11112222222222222222ULL / 9999;
    return 11112222222222222222ULL;
  }
#endif

  memset(digits, 0, sizeof(digits));

  for (;;)
  {
    carry = 1;
    for (i = 0; carry; i++)
    {
      assert(i < sizeof(digits));
      carry += digits[i];
      digits[i] = carry % 3;
      carry /= 3;
    }
    if (i >= digcnt) digcnt = i;

    result = 0;
    for (i = 0; i < digcnt; i++)
      result = result * 10 + digits[digcnt - 1 - i];

    if (result % n == 0)
    {
      *multiplier = result / n;
      break;
    }
  }

  return result;
}

int main(void)
{
  uint i;
  uint64 sum = 0, product, multiplier;
  time_t t;
  char* p;

  for (i = 1; i <= 10000; i++)
  {
    product = f(i, &multiplier);
    printf("%s ", ((time(&t), p = ctime(&t)), p[strlen(p) - 1] = '\0', p));
    printf("f(%u) = %llu = %u * %llu\n", i, product, i, multiplier);
    sum += multiplier;
  }
  printf("%s ", ((time(&t), p = ctime(&t)), p[strlen(p) - 1] = '\0', p));
  printf("sum(f(n)/n) = %llu\n", sum);

  return 0;
}

输出:

Mon Jan  9 12:18:22 2012 f(1) = 1 = 1 * 1
Mon Jan  9 12:18:22 2012 f(2) = 2 = 2 * 1
Mon Jan  9 12:18:22 2012 f(3) = 12 = 3 * 4
Mon Jan  9 12:18:22 2012 f(4) = 12 = 4 * 3
Mon Jan  9 12:18:22 2012 f(5) = 10 = 5 * 2
Mon Jan  9 12:18:22 2012 f(6) = 12 = 6 * 2
Mon Jan  9 12:18:22 2012 f(7) = 21 = 7 * 3
Mon Jan  9 12:18:22 2012 f(8) = 112 = 8 * 14
Mon Jan  9 12:18:22 2012 f(9) = 12222 = 9 * 1358
...
Mon Jan  9 12:18:39 2012 f(9998) = 111122211112 = 9998 * 11114444
Mon Jan  9 12:22:50 2012 f(9999) = 11112222222222222222 = 9999 * 1111333355557778
Mon Jan  9 12:22:50 2012 f(10000) = 10000 = 10000 * 1
Mon Jan  9 12:22:50 2012 sum(f(n)/n) = 1111981904675169

如果您将#if 0更改为#if 1并启用Peter Lawrey评论中提到的捷径,则会在大约1分钟内完成。

答案 6 :(得分:0)

这是我对这个有趣问题的贡献。请原谅我使用Java和BigInteger,但根据我的松散测试,它毕竟不是这样的阻塞(计算总和[1,100]花费不到一秒,总和[1,10000]大约4,5分钟在我的2.4双重核心)。在4分钟左右的4分钟以上花费在f(9999)上。非常惊讶。

import java.math.BigInteger;

public class Main {
    public static void main(String args[]) {
        BigInteger result = BigInteger.ZERO;
        for (int i = 1; i <= 10000; ++i) {
            BigInteger r = f(BigInteger.valueOf(i));
            System.out.println("i=" + i + "  r=" + r);
            result = result.add(r.divide(BigInteger.valueOf(i)));
        }
        System.out.println("result=" + result);
    }
    // Find smallest x * value which consists only of numbers {0, 1, 2}.
    private static BigInteger f(BigInteger value) {
        BigInteger retVal = value;
        while (!check(retVal)) {
            BigInteger remainder = remainder(retVal);
            BigInteger mult = remainder.subtract(retVal.remainder(remainder))
                    .divide(value);
            retVal = retVal.add(value.multiply(mult.max(BigInteger.ONE)));
        }
        return retVal;
    }
    // Find highest remainder for given value so that value % retVal =
    // XYYYYY.. Where X > 2 and 0 <= Y <= 9.
    private static BigInteger remainder(BigInteger value) {
        BigInteger curVal = BigInteger.TEN;
        BigInteger retVal = BigInteger.TEN;
        while (value.compareTo(BigInteger.TEN) >= 0) {
            curVal = curVal.multiply(BigInteger.TEN);
            value = value.divide(BigInteger.TEN);
            if (value.remainder(BigInteger.TEN).intValue() > 2) {
                retVal = curVal;
            }
        }
        return retVal;
    }
    // Check if given value contains only 0, 1 and 2.
    private static boolean check(BigInteger value) {
        do {
            if (value.remainder(BigInteger.TEN).intValue() > 2) {
                return false;
            }
            value = value.divide(BigInteger.TEN);
        } while (value.compareTo(BigInteger.ZERO) == 1);
        return true;
    }
}