问题:
将所有回文数从小到大排列,求第N个回文数。
一个正数如果顺着和反过来都是一样的(如13431,反过来也是13431),就称为回文数。约束:
回文数不能以0开头。
最小回文数是1。
思路:
许多朋友(包括我自己)一开始就思考使用循环:从1开始,判断该数是否是回文数,然后用一
个计数器记下回文数,一直到计数器得到N,返回第N个回文数。比较常用的是以下这种方法来判断是
否回文数:
int o = num;
int tmp = 0 ;
// 使用循环把数字顺序反转
while (num != 0 ) {
tmp *= 10 ;
tmp += num % 10 ;
num /= 10 ;
}
// 如果原始数与反转后的数相等则返回true
if (tmp == o)
return true ;
return false ;
}
这种思路的确可得到正确结果,但随着用来测试的N的增大,效率的问题就浮现了。因为是一重
循环,效率是O(n)。所以当N非常大时,所花的时间还是十分大的。
另一种思路:
回文数的个数其实是有规律的。如:
1位回文数: 9个
2位回文数: 9个
3位回文数: 90个
4位回文数: 90个
5位回文数: 900个
6位回文数: 900个
…
我们看到9、90、900,是不是很有规律,那是什么原因?很简单,我们把回文数拆开两半
[123321]来看。两半的变化一样的,那我们只算其中一半就行了。首位不能是0,所以左半最小为
100,最大为999,共有999-100+1=900个,如此类推。
所以我们可以基于以下原则:
1、 回文数的数位每增长2,回文数的个数为原来的10倍。如从个位回文数到百位回文数,个数
从9个变为90个。
2、 个位回文数的个数是9,1、2、3、…、9。
总之理解原理后一切好办,步骤就偷懒不写了,看代码吧!
核心代码:
int count = 0 ;
int number = 9 ; // 记录数位上的回文数,如个位回文数为9
int w = 0 ; // 记录数位
long half; // 保存回文数的左半边的结果
long h = 1 ; // 回文数的左半边的起始基数
long res; // 结果
while ( true ) {
if (w > 0 && w % 2 == 0 ) { // 每进两个数位,回文数乘以10
number *= 10 ;
}
w ++ ; // 数位加一
if (count + number > index) // 回文数大于查找的回数,跳出
break ;
count += number; // 回文数加上当前数位上的回文数
}
index -= count; // 在当前数位上的位置。如w=5,index=50,则万位上的第50个回文数是我们所求
for ( int i = 0 ; i < (w - 1 ) / 2 ; i ++ ) { // 求回文数的左半边的基数,如回文数在万位上,则为100
h *= 10 ;
}
half = h + index; // 回文数的左半边,如100 + 50 = 150
res = half;
if (w % 2 != 0 ) // 如果为奇数,则中间那个数不必算入右半边了!
half /= 10 ;
while (half != 0 ) { // 拼接回文数
res = res * 10 + half % 10 ;
half /= 10 ;
}
return res;
}
全部代码:
public class PalindromicNumber { public static void main(String[] args) { int input = Integer.parseInt(args[0]); long res = find(input); System.out.println("res:" + res); } static long find(int index) { int count = 0; int number = 9; //记录数位上的回文数,如个位回文数为9 int w = 0; //记录数位 long half; //保存回文数的左半边的结果 long h = 1; //回文数的左半边的起始基数 long res; //结果 while(true) { if(w > 0 && w%2 == 0) { //每进两个数位,回文数乘以10 number *= 10; } w++; //数位加一 if(count + number > index) //回文数大于查找的回数,跳出 break; count += number; //回文数加上当前数位上的回文数 } index -= count; //在当前数位上的位置。如w=5,index=50,则万位上的第50个回文数是我们所求 for(int i = 0; i < (w-1) / 2; i++) { //求回文数的左半边的基数,如回文数在万位上,则为100 h *= 10; } half = h + index; //回文数的左半边,如100 + 50 = 150 res = half; if(w%2 != 0) //如果为奇数,则中间那个数不必算入右半边了! half /=10; while(half != 0) { //拼接回文数 res = res *10 + half % 10; half /= 10; } return res; } }