项目Euler#36的更好解决方案?

时间:2012-02-13 23:15:19

标签: algorithm binary decimal

项目Euler problem 36声明:

  

十进制数,585 = 1001001001(二进制),两个基数都是回文。

     

找出所有数字的总和,少于一百万,在基数10和基数2中是回文。

     

(请注意,任一基地的回文数字可能不包括前导零。)

堆栈溢出已经存在solution,但我想要一个更有效的解决方案

例如,由于回文不能有前导0,所以不需要检查偶数,只有二进制的最后一位为1的奇数。这个简单的观察已经加速了蛮力“检查每个数字范围“按因子2。

但我希望比这更聪明。理想情况下,我想要一个算法,其运行时间与总和中的数字数成正比。我认为不可能做得更好。但也许这是不可能的。例如,我们可以生成与满足该属性的十进制数的比例小于一百万的所有回文十进制数吗? (我认为答案是肯定的)。

解决这个回文求和问题最有效的算法是什么?我想考虑N参数化的运行时间:数字范围的大小(在这种情况下为1百万), D:范围内的十进制回文的集合,B:该范围内的二元回文的集合。我希望运行时为o(N)+ O(| D与B |相交),或者失败,O(min(| D |,| B |))

注意: binarydecimal回文的序列众所周知。

e.g。二元回文< 100:0,1,3,5,7,9,15,17,21,27,31,33,45,51,63,65,73,85,93,99

。 。 .decimal palindromes< 100:0,1,2,3,4,5,6,7,8,9,11,22,33,44,55,66,77,88,99,

两个碱基中的回文:0,1,3,5,7,9,33,99

33和99的二进制表示分别是100011100011。 两个回文的下一个数字是585 = 1001001001

5 个答案:

答案 0 :(得分:4)

长度为b的基础2*k的回文数量为(b-1)*b^(k-1),长度为2*k-1的回文数量也是N。因此,任何碱基中不超过N的回文数是O(sqrt(N))1。因此,如果您在一个碱基中生成所有回文(不超过N)并检查它们是否也是另一个碱基中的回文,则您有一个O(sqrt(N)* log(N))算法(对数因子)来自回文检查)。那是o(N),但我还不知道它是否也是O(| D与B |相交)。

它不是O(| D与B |相交):(在两个碱基中只有32个数字,最多10个 10 ,它们是回文。我没有看到任何只允许构建的模式那些。

¹如果db位数(基数为N),则不超过d-1的回文数量介于最多{{1}的回文数之间数字和最多d个数字(包括两个限制)的回文数量。 (b-1)*b^(k-1)个数字有k个数字(基数为b),其中(b-1)*b^(floor((k-1)/2)))为回文数。 Summing给出最多b个数字的基数 - k回文数,为2*(b^(k/2)-1)(如果k为偶数)或(b-1)*b^((k-1)/2) + 2*(b^((k-1)/2)-1)(如果{{1}很奇怪)。因此,给予或取一个因子k,最多d位数的回文数是2*b。因此,不超过b^(d/2)的回文数量约为N,其中一个因子受所考虑基数的倍数限制。

答案 1 :(得分:3)

考虑到1到1000000之间只有大约2000个十进制回文。从1到999,您可以将数字和反向串联起来,无论是否重复“中间”数字(左侧部分的最后一位) )。对于每一个,你检查它是否也是二元回文,如果是,它是总和的一部分。

答案 2 :(得分:2)

(不是你的问题的答案,而是Project Euler 36的一个可爱的递归式小巧解决方案)

这可能不是最有效的算法,但我喜欢它的样子。我在读完 Daniel Fischer 的答案之后写了这篇文章,建议在一个基地产生所有回文,然后在另一个基地检查它是否也是一个回文。

在18行代码(包括括号)中,它生成基数2中的所有回文,然后检查它们是否也是基数10中的回文。

我的系统大约需要6毫秒。

这可能是优化的(根据我的口味进行过多的位移操作,这里可能有一些不必要的垃圾)并且可能有更好的算法,但我“喜欢”(+)我的代码的外观; )

@Test
public void testProjectEuler36() {
    final int v = rec(1, 1);
    assertEquals( 872187, v );
}

public static int rec( final int n, final int z ) {
    if ( n > 1000000 )
        return 0;
    if ( z % 2 == 0 ) {
        final int l = n << 1 & -1 << z / 2 + 1;
        final int r = n & -1 >>> 32 - z / 2;
        return v(n) + rec( l | 1 << z / 2 | r, z + 1 ) + rec( l | r, z + 1 );
    } else
        return v(n) + rec( n << 1 & -1 << z / 2 + 1 | n & -1 >>> 31 - z / 2, z + 1 );
}

public static int v( final int n ) {
    final String s = "" + n;
    boolean ok = true;
    for ( int j = s.length(), i = j / 2 - 1; i >= 0 && ok; i--)
        ok = s.charAt(i) == s.charAt(j-(i+1));
    return ok ? n : 0;
}

答案 3 :(得分:0)

我的第一个假设是完全错误的,所以我已经修好了。我提供了两种算法,一种迭代,一种递归。它们显然远远不如user988052那么令人印象深刻和高效,但它们肯定更容易阅读!第一种算法是迭代的,运行时间为9ms。第二种算法是递归的,运行时间为16ms。虽然第二种解决方案更清晰,但递归调用可能会降低它的速度。

第一个算法(9ms):

/** Given half a palindrome, construct the rest of the palindrome with
 * an optional string inserted in the middle. The returned string is
 * only guaranteed to be a palindrome if 'mid' is empty or a palindrome. */
public static String getPalindrome(String bin_part, String mid) {
    return bin_part + mid + (new StringBuilder(bin_part)).reverse();
}

/** Check if the specified string is a palindrome. */
public static boolean isPalindrome(String p) {
    for (int i=0; i<p.length()/2; i++)
        if (p.charAt(i) != p.charAt(p.length()-1-i))
            return false;
    return true;
}

public static void main(String[] args) {

    String[] mids = {"0","1"};
    long total = 0;
    boolean longDone = false; // have the numbers with extra digits been tested

    long start = System.currentTimeMillis();
    for (long i=0; i<1000; i++) {
        String bin_part = Long.toBinaryString(i);

        String bin = getPalindrome(bin_part, "");
        long dec = Long.valueOf(bin, 2);
        if (dec >= 1000000) break; // totally done

        if (isPalindrome(Long.toString(dec)))
            total += dec;

        if (!longDone) {
            for (int m=0; m<mids.length; m++)  {
                bin = getPalindrome(bin_part, mids[m]);
                dec = Long.valueOf(bin, 2);
                if (dec >= 1000000) {
                    longDone = true;
                    break;
                }
                if (isPalindrome(Long.toString(dec)))
                    total += dec;
            }
        }
    }
    long end = System.currentTimeMillis();
    System.out.println("Total: " + total + " in " + (end-start) + " ms");
}

第二种算法(16ms)

public long total = 0;
public long max_value = 1000000;
public long runtime = -1;

public static boolean isPalindrome(String s) {
    for (int i=0; i<s.length()/2; i++)
        if (s.charAt(i) != s.charAt(s.length()-1-i))
            return false;
    return true;
}

public void gen(String bin, boolean done) {
    if (done) { // generated a valid binary number
        // check current value and add to total if possible
        long val = Long.valueOf(bin, 2);
        if (val >= max_value)
            return;
        if (isPalindrome(Long.toString(val))) {
            total += val;
        }

        // generate next value
        gen('1' + bin + '1', true);
        gen('0' + bin + '0', false);
    } else { // generated invalid binary number (contains leading and trailing zero)
        if (Long.valueOf('1' + bin + '1', 2) < max_value) {
            gen('1' + bin + '1', true);
            gen('0' + bin + '0', false);
        }
    }
}

public void start() {
    total = 0;
    runtime = -1;
    long start = System.currentTimeMillis();
    gen("",false);
    gen("1",true);
    gen("0",false);
    long end = System.currentTimeMillis();
    runtime = end - start;
}

public static void main(String[] args) {
    Palindromes2 p = new Palindromes2();
    p.start();
    System.out.println("Total: " + p.total + " in " + p.runtime + " ms.");
}

答案 4 :(得分:0)

以下是解决此问题的最佳Python实现:

sum = 0
for i in range(1000000):
    bina = int(str(bin(i)).replace('0b',''))
    if(i==int(str(i)[::-1]))or(bina==int(str(bina)[::-1])):
        #print("i : "+str(i))
        #print("bina : "+str(bina))
        sum+=i
print("Sum of numbers : ",sum)