前言:在看Java编程思想(第四版)第四章的时候,最后一个练习10是求四位数的吸血鬼数字,题目如下:
然后自己照着题目用循环写了一个答案:
/**
* 练习10 :得出四位数中的吸血鬼数字
* (吸血鬼数字是指位数为偶数的数字,可以由一对数字相乘而得到,而这对数字各包含乘积的一半位数的数字,其中从最初的数字中选取的数字可以任意排序,并且以两个0结尾的数字是不允许的)
* @return void
* @exception
*/
public static void tenthExercise() {
int count = 0;
//循环所有四位数被除数
for (int i = 1000; i < 10000; i++) {
String iString = ((Integer)i).toString();
//如果是以00结尾的数字则跳过
if(iString.endsWith("00")) {
continue;
}
//循环所有两位数除数
for (int j = 10; j < 100; j++) {
int result = i/j;
String iResult = ((Integer)result).toString();
//如果被除数/除数没有余数 && 商也为两位数
if(i % j == 0 && iResult.length() == 2) {
count++;
//判断除数和商是否能组成该四位数
String first = ((Integer)(j / 10)).toString();
String second = ((Integer)(j % 10)).toString();
String thrid = ((Integer)(result / 10)).toString();
String fourth = ((Integer)(result % 10)).toString();
String cc = iString;
if(cc.indexOf(first) != -1) {
cc = cc.replaceFirst(first, "");
}
if(cc.indexOf(second) != -1) {
cc = cc.replaceFirst(second, "");
}
if(cc.indexOf(thrid) != -1) {
cc = cc.replaceFirst(thrid, "");
}
if(cc.indexOf(fourth) != -1) {
cc = cc.replaceFirst(fourth, "");
}
if("".equals(cc.trim())) {
System.out.println("除数:" + j + ", 商:" +result);
System.out.println(i);
break;
}
}
}
}
System.out.println("count:" + count);
}
但是发现自己的写的代码效率太低,循环次数太多,然后网上查到一种效率最高的算法,如下:
引用自:http://blog.csdn.net/java2000_net/article/details/3851203
/**
* 吸血鬼数字,高效率版本.<br>
* 一个4位数字,可以拆分2个2位数数字的乘积,顺序不限。<br>
* 比如 1395 =15 * 93
*
* @author 老紫竹(laozizhu.com)
*/
public class Vampire {
public static void main(String[] arg) {
String[] ar_str1, ar_str2;
int sum = 0;
int from;
int to;
int i_val;
int count = 0;
// 双重循环穷举
for (int i = 10; i < 100; i++) {
// j=i+1避免重复
from = Math.max(1000 / i, i + 1);
to = Math.min(10000 / i, 100);
for (int j = from; j < to; j++) {
i_val = i * j;
// 下面的这个代码,我个人并不知道为什么,汗颜
if (i_val % 100 == 0 || (i_val - i - j) % 9 != 0) {
continue;
}
count++;
ar_str1 = String.valueOf(i_val).split("");
ar_str2 = (String.valueOf(i) + String.valueOf(j)).split("");
Arrays.sort(ar_str1);
Arrays.sort(ar_str2);
if (Arrays.equals(ar_str1, ar_str2)) {// 排序后比较,为真则找到一组
sum++;
System.out.println("第" + sum + "组: " + i + "*" + j + "=" + i_val);
}
}
}
System.out.println("共找到" + sum + "组吸血鬼数");
System.out.println(count);
}
}
此段代码刚开始有点看不懂,后面多看几次并配合博客里面的讲解才慢慢的看懂,首先是双重循环穷举:
for (int i = 10; i < 100; i++) {
// j=i+1避免重复
from = Math.max(1000 / i, i + 1);
to = Math.min(10000 / i, 100);
for (int j = from; j < to; j++) {
因为求的是4位数的,所以在i从10开始增长的时候,要使i*j=四位数,则j至少要大于1000/i,并且小于10000/i,所以才有了这双重循环。至于当1000/i小于i+1的时候使用i+1是为了避免j取之前i已经循环过的数,比如20*50和50*20。
然后再是下面这段代码:
// 下面的这个代码,我个人并不知道为什么,汗颜
if (i_val % 100 == 0 || (i_val - i - j) % 9 != 0) {
continue;
}
可以看老紫竹博客下面的解释,很明确了
i_val % 100 == 0 是为了过滤尾数为00的四位数
关于算法的解释,来自网友MT502
假设val = 1000a + 100b + 10c + d, 因为满足val = x * y, 则有x = 10a + b, y = 10c + d
则val - x - y = 990a + 99b + 9c = 9 * (110a + 11b + c), 所以val - x - y能被9整除。
所以满足该条件的数字必定能被9整除,所以可以直接过滤其他数字。
我准许做一下
x*y = val = 1000a + 100b + 10c + d;
我们假设
x = 10a + b, y = 10c + d
则
x*y-x-y
= val - x-y
= (1000a + 100b + 10c + d) - (10a+b) - (10c +d) = 990a + 99b + 9c
= 9 * (110a + 11b + c);
对于别的组合可能性,结果一样,比如
x=10c+a; y=10d+b;
x*y-x-y
= val - x-y
= (1000a + 100b + 10c + d) - (10c+a) - (10d +b) = 999a + 99b -9d
= 9 * (110a + 11b -d);
当然也能被9整除了
当然还有其他的方法,在此就不一一列举了,有兴趣可以深入研究研究。