簡體   English   中英

第n個具有有效時間復雜度的二元回文

[英]nth Binary palindrome with efficient time complexity

給定一個整數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);
}

嘗試類似的東西嗎?

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長度的均勻長度回文leftHalf 隨時提供更好的解決方案。

由於int在Java中具有32位,因此對N有限制。

int -Version上ideone.com

還有一個BigInteger支持大價值。 它不如int -version快,而存儲BigInteger值的byte[] -arrays會產生一些開銷。

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);
    }
}

如果您閱讀過相關的OEIS條目(A006995),則有很多不錯的技巧。 例如, a(2^n-1)=2^(2n-2)-1可以讓您真正快速地跳至第(2 n -1) 回文。

它還提供了幾種實現。 例如,Smalltalk實現的工作方式如下(請注意,第一個回文數0的輸入值n1開頭):

public static final int nthBooleanPalindrome(int n) {
    if (n == 1) return 0;
    if (n == 2) return 1;
    int m = 31 - Integer.numberOfLeadingZeros(n);
    int c = 1 << (m - 1);
    int b;
    if (n >= 3*c) {
        int a = n - 3*c;
        int d = 2*c*c;
        b = d + 1;
        int k2 = 1;
        for (int i = 1; i < m; i++) {
            k2 <<= 1;
            b += a*k2/c%2*(k2 + d/k2);
        }
    }
    else {
        int a = n - 2*c;
        int d = c*c;
        b = d + 1 + (n%2*c);
        int k2 = 1;
        for (int i = 1; i < m - 1; i++) {
            k2 <<= 1;
            b += a*k2/c%2*(k2 + d/k2);
        }
    }
    return b;
}

優化的想法,讓我們看看回文序列0、1、11、101、111、1001等。

所有數字都必須以1開始和結束,因此中間位僅會發生變化,並且中間子串應為回文,以使整個字符串成為回文,

  • 因此,讓我們采用2位二進制數-一個回文是可能的。

    十進制3的二進制是回文。 11

  • 對於3位二進制數,可能有2個回文, 2 *(1位回文中沒有)

    十進制的二進制5是回文。 101

    十進制的7的二進制是回文。 111

  • 對於5位數的二進制數,可能有4個回文數2 *(3位數的回文數沒有)

    10001、10101、11011、11111

    等等,

因此它將是2 + 2 0 + 2 1 + 2 2 + ...... +2 iN

我們為我求解並找出回文數。

因此,通過分析此序列,我們得到了一個等式2 (i / 2)+1 -1 = N

其中N是回文數

我是第n個回文字符串中的位數,

使用它我們可以找到字符串的長度,從中我們可以盡早找到字符串。

這可能很復雜,但是有助於快速解決更高的N值。

我對@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)));
}

如果我們在[count(L), count(L+1))找到N范圍,則可以將其與剩余數字連接起來:

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);
    }
}

可以在我的git repo中找到完整的帶有測試的完整代碼,以產生可能的long時間。

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM