具有有效时间复杂度的二元回文

时间:2016-12-24 09:42:33

标签: java

给定一个整数N,我试图找到第n个二元回文。我编写了下面的代码,但效率不高。在时间复杂度方面有一种更有效的方法。 我在线尝试它是一个问题,我应该在1秒或更短的时间内输出,但每次输入需要2秒。

public static Boolean Palind(String n){
    String reverse = "";
    int length = n.length();
    for(int i = length - 1; i >=0;i--){
        reverse = reverse + n.charAt(i);
    }
    if(n.equals(reverse)){
        return true;
    }
    else{
        return false;
    }
}

public static int Magical(int n){
    ArrayList<Integer> res = new ArrayList<Integer>();
    for(int i = 1; i < Math.pow(2, n);i++){
        if(Palind(Integer.toBinaryString(i))){
            res.add(i);
        }
    }
    return res.get(n-1);
}

4 个答案:

答案 0 :(得分:1)

尝试这样的事情吗?

public static void main(String[] args) {
    for (int i = 1; i < 65535; i++) {
        System.out.println(
                i + ": " + getBinaryPalindrom(i) + " = " + Integer.toBinaryString(getBinaryPalindrom(i)));
    }
}

public static int getBinaryPalindrom(int N) {
    if (N < 4) {
        switch (N) {
            case 1:
                return 0;
            case 2:
                return 1;
            case 3:
                return 3;
        }
        throw new IndexOutOfBoundsException("You need to supply N >= 1");
    }
    // second highest to keep the right length (highest is always 1)
    final int bitAfterHighest = (N >>> (Integer.SIZE - Integer.numberOfLeadingZeros(N) - 2)) & 1;
    // now remove the second highest bit to get the left half of our palindrom
    final int leftHalf = (((N >>> (Integer.SIZE - Integer.numberOfLeadingZeros(N) - 1)) & 1) << (Integer.SIZE -
            Integer.numberOfLeadingZeros(N) - 2)) | ((N << (Integer.numberOfLeadingZeros(N) + 2)) >>> (Integer.numberOfLeadingZeros(N) + 2));
    // right half is just the left reversed
    final int rightHalf = Integer.reverse(leftHalf);
    if (Integer.numberOfLeadingZeros(leftHalf) < Integer.SIZE / 2) {
        throw new IndexOutOfBoundsException("To big to fit N=" + N + " into an int");
    }
    if (bitAfterHighest == 0) {
        // First uneven-length palindromes
        return (leftHalf << (Integer.SIZE - Integer.numberOfLeadingZeros(leftHalf)) - 1) | (rightHalf
                >>> Integer.numberOfTrailingZeros(rightHalf));
    } else {
        // Then even-length palindromes
        return (leftHalf << (Integer.SIZE - Integer.numberOfLeadingZeros(leftHalf))) | (rightHalf
                >>> Integer.numberOfTrailingZeros(rightHalf));
    }
}

这个想法是,一旦反向添加,每个数字将成为回文。为了使两半正确对齐,只需要将两半移动到位。

为什么这有点复杂的问题是,给定leftHalf长度的所有不均匀长度的回文都出现在给定leftHalf长度的所有偶数长度的回文之前。随意提供更好的解决方案。

由于int在Java中有32位,因此N有一个限制。

int - ideone.com上的版本

还有一个BigInteger版本来支持大值。它不如int - 版本那样快byte[] - 存储BigInteger值的数组会产生一些开销。

public static void main(String[] args) {
    for (BigInteger i = BigInteger.valueOf(12345678); i.compareTo(BigInteger.valueOf(12345778)) < 0; i = i
            .add(BigInteger
            .ONE)) {
        final BigInteger curr = getBinaryPalindrom(i);
        System.out.println(i + ": " + curr + " = " + curr.toString(2));
    }
}

public static BigInteger getBinaryPalindrom(BigInteger n) {
    if (n.compareTo(BigInteger.ZERO) <= 0) {
        throw new IndexOutOfBoundsException("You need to supply N >= 1");
    } else if (n.equals(BigInteger.valueOf(1))) {
        return BigInteger.valueOf(0);
    } else if (n.equals(BigInteger.valueOf(2))) {
        return BigInteger.valueOf(1);
    } else if (n.equals(BigInteger.valueOf(3))) {
        return BigInteger.valueOf(3);
    }
    final int bitLength = n.bitLength() - 1;

    // second highest to keep the right length (highest is always 1)
    final boolean bitAfterHighest = n.testBit(bitLength - 1);
    // now remove the second highest bit to get the left half of our palindrom
    final BigInteger leftHalf = n.clearBit(bitLength).setBit(bitLength - 1);
    // right half is just the left reversed
    final BigInteger rightHalf;
    {
        byte[] inArray = leftHalf.toByteArray();
        byte[] outArray = new byte[inArray.length];
        final int shiftOffset = Integer.SIZE - Byte.SIZE;
        for (int i = 0; i < inArray.length; i++) {
            outArray[inArray.length - 1 - i] = (byte) (Integer.reverse(inArray[i]) >>> shiftOffset);
        }
        rightHalf = new BigInteger(1, outArray).shiftRight(outArray.length * Byte.SIZE - bitLength);
    }
    if (!bitAfterHighest) {
        // First uneven-length palindromes
        return leftHalf.shiftLeft(bitLength - 1).or(rightHalf);
    } else {
        // Then even-length palindromes
        return leftHalf.shiftLeft(bitLength).or(rightHalf);
    }
}

答案 1 :(得分:1)

如果你仔细阅读,那么

The relevant OEIS entry (A006995)有很多不错的提示。例如,a(2^n-1)=2^(2n-2)-1可以让你快速直接跳到(2 n - 1) th 回文。

它还提供了几种实现方式。例如,Smalltalk实现的工作原理如下(请注意,输入值n以第一个回文1的{​​{1}}开头:

0

答案 2 :(得分:0)

优化的想法, 让我们来看看回文序列0,1,11,101,111,1001等...所有数字必须以1开始和结束,所以中间的比特只会改变,midle子串应该是回文,因为全字符串成为回文, 所以让我们拿2位数只有11是回文。在3位数字中,2个回文可能是5位数字2 *(3位回文数),依此类推,这不是解决方案,这是一个可以解决的想法。

所以它将是2 + 2 ^ 0 + 2 ^ 1 + 2 ^ 1 + ...... + 2 ^ i~N,求解i并找出回文数。

因此,通过分析这个序列,我得到了一个像2 (i / 2)+1 -1 = N的等式,其中N是回文的No,而i是第n个中的比特数回文字符串,使用这个我们可以找到字符串的长度,从这里我们可以早期找到字符串。

这可能很复杂,但有助于快速解决更高的N值......

答案 3 :(得分:0)

我对@Kiran Kumar有同样的想法:你不应该一个接一个地计算它是否是一个太慢的二元回文,而是找到那个数字的内部模式。

逐个列出二进制字符串中的数字,您可以找到模式:

0
1
11
101
1001
1111
...
1......1

以下是一些数学问题: 我们有2^round_up((L-2)/2)数字回文,长度为L二进制格式。 总结每个较短的长度数字,我们得到lensum映射:

for (int i = 1; i < mapping.length; i++) {
    mapping[i] = (long) (mapping[i - 1] + Math.pow(2, Math.ceil((i - 1) * 1.0 / 2)));
}

如果我们在N中找到[count(L), count(L+1))范围,我们可以将其与剩余数字联系起来:

public static long magical(long n) {
    if (n == 0 || n == 1) {
        return n;
    }
    long N = n - 2;
    return Long.parseLong(concat(N), 2);
}

private static String concat(long N) {
    int midLen = Arrays.binarySearch(indexRange, N);
    if (midLen < 0) {
        midLen = -midLen - 1;
    }
    long remaining = N - indexRange[midLen];
    String mid = mirror(remaining, midLen);
    return '1' + mid + '1';
}

private static String mirror(long n, int midLen) {
    int halfLen = (int) Math.ceil(midLen * 1.0 / 2);
    // produce fixed length binary string
    final String half = Long.toBinaryString(n | (1 << halfLen)).substring(1);
    if (midLen % 2 == 0) {
        return half + new StringBuilder(half).reverse().toString();
    } else {
        return half + new StringBuilder(half).reverse().toString().substring(1);
    }
}

可以在my git repo中找到包含大量可能long产品测试的完整代码。