给定一个整数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);
}
答案 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
二进制格式。
总结每个较短的长度数字,我们得到len
到sum
映射:
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
产品测试的完整代码。