1、吸血鬼数字介绍
吸血鬼数字是指位数为偶数的数字,可以由一对数字相乘而得到,而这对数字各包含乘积的一半位数的数字,其中从最初的数字中选取的数字可以任意排序。–引自百度百科
举几个例子就明白了:
1260 = 21 * 60 1827 = 21 * 87 2187 = 27 * 81等等
吸血鬼数字都是偶数位的,两个因子的组成数字都是从吸血鬼数字中得到的,且两个因子的所有数字等于吸血鬼数字中的所有数字(是完全相同,不能重复使用)。此外还有一个规定是吸血鬼数字不能是100的整数倍。
2、实现思路
首先来分析一下吸血鬼数字的必要条件:
- 必须是偶数位的数字,121肯定不是;
- 不能是100的整数倍,1000肯定不是;
这些都是吸血鬼数字的定义告诉我们的,除此之外还有哪些可以利用的条件呢?
- 末尾数字相乘%10等于吸血鬼数字的末尾数字,考虑到最后一位是0时,采用倒数第二个数字检验。比如上面的1827,最后一位是7,那么组成该数字的两个数字末尾乘积取模10肯定是7,1260中,两个因子的最后一位必为1或6,且1和6不能在同一个数字中。
通过上面这些条件我们就可以减少一些比较量,尤其是前两个条件,可以直接判断一些非吸血鬼数字,而第三个条件就需要进行转化和判断,尤其是遇到0结尾的数字。
如果上面三个条件都满足,就需要直接寻找两个因子来判断是否是吸血鬼数字了,
- 穷举法 比较简单地方式就是穷举所有可能的数字,先判断两个因子乘积是否等于该数字,等于则判断组成两个因子的数字是否与要判断的吸血鬼数字组成完全一致,只有在满足这两个条件的情况下才能进行判断该数字是吸血鬼数字。比如判断一个四位数字是否是吸血鬼数字,则需要验证所有的两位数字乘积。
- 排列法 我自己暂且叫这个名字吧,因为因子是吸血鬼数字的再排列,因此我们找到所有的排列形式,然后一分为二,判断前后两部分的乘积是否等于原来的数字,等于则是吸血鬼数字,反之则不是。比如1827=21*87,因此其排列是2187的形式,2187是该数字的一个排列形式,我们取到21和87进行判断就可以断定了该数字是吸血鬼数字了。
下面代码就是用排列法进行的实现,具体过程如下:
- 判断该数字是否是100的整数倍,是则返回false;
- 判断数字的位数是否是偶数,否则返回false;
- 寻找所有可能的末尾数字组合,如果不存在两个数字相乘取模10后等于要判断的数字末尾或倒数第二位(末尾为0时)则返回false;
- 获取所有可能的排列,将排列从中间截断,并比较第一部分和第二部分末尾(或只有一部分的倒数第二位)是否满足乘积取模10后等于末尾数字或倒数第二位数字(末尾为0时),满足时如果两部分的乘积等于要判断的数字,则返回true,否则返回false。
3、代码实例
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
/** * @author jqs 吸血鬼数字识别算法 * */
public class VampireNumber {
/** * 组成吸血鬼数字的第一个数字 */
private int sonNum1;
/** * 组成吸血鬼数字的第二个数字 */
private int sonNum2;
private boolean isVampireNumber = false;
private int value;
public int getSonNum1() {
return sonNum1;
}
public void setSonNum1(int sonNum1) {
this.sonNum1 = sonNum1;
}
public int getSonNum2() {
return sonNum2;
}
public void setSonNum2(int sonNum2) {
this.sonNum2 = sonNum2;
}
public VampireNumber(int value) {
this.value = value;
initSonNum();
}
/** * 判断输入的数值是否是吸血鬼数字 */
private void initSonNum() {
// 以00结尾的不是
if (value % 100 == 0) {
setVampireNumber(false);
return;
}
// 首先将数字分成n个int值,n代表value的数字长度
String string = value + "";
int length = string.length();
if (length % 2 != 0) {
// 非偶数位数一定不是
setVampireNumber(false);
return;
}
Integer[] rawNum = new Integer[length];
for (int i = 0; i < rawNum.length; i++) {
rawNum[i] = Integer.parseInt(string.charAt(i) + "");
}
// 记录该数字的最后一位不是0的值,以便于根据这个值来找可能的两个数字乘积取模10后等于该值
boolean lastZero = rawNum[length - 1] == 0;
int last = lastZero ? rawNum[length - 2] : rawNum[length - 1];
// 构建两个list来存放吸血鬼数字可能的两个值的最后一位(或倒数第二位),因为吸血鬼数字的两个值的最后一位(或其中的倒数第二位)相乘必等于该值的最后一位,如果吸血鬼数字末尾是0的话,相乘必等于倒数第二位
// 下面的两个list是成对出现的,且其index是一一对应的关系
List<Integer> oneLastList = new ArrayList<Integer>();
List<Integer> otherLastList = new ArrayList<Integer>();
// 需要再检查一下任意两位相乘的情况
for (int i = 0; i < rawNum.length; i++) {
for (int j = i + 1; j < rawNum.length; j++) {
if (rawNum[i] * rawNum[j] % 10 == last) { // 满足两个值相乘取模10等于last,则这两个值可能是组成吸血鬼数字的两个值中的最后一位或倒数第二位(最后一位是0的情况)
oneLastList.add(rawNum[i]);
otherLastList.add(rawNum[j]);
}
}
}
if (otherLastList.size() == 0) {
// 找不到两个数相乘取模10等于最后一位或倒数第二位(最后一位是0的情况),直接返回,比如2220,肯定不是,2222肯定不是
setVampireNumber(false);
return;
}
fastMethod(length, rawNum, lastZero, oneLastList, otherLastList);
}
/** * 最快的判断数字是否是吸血鬼数字的方法 * * @param length * 数字长度 * @param rawNum * 原始数字每一位组成的数组 * @param lastZeroFlag * 最后以为是否是0 * @param oneLastList * @param otherLastList */
public void fastMethod(int length, Integer[] rawNum, boolean lastZeroFlag,
List<Integer> oneLastList, List<Integer> otherLastList) {
int size = oneLastList.size();
if (lastZeroFlag) {
for (int i = 0; i < size; i++) {
otherLastList.add(oneLastList.get(i));
oneLastList.add(otherLastList.get(i));
}
}
int count = 0;
int oneLast = 0;
int otherLast = 0;
int half = length / 2;
// 对输入的数字进行全排列,得到所有的排列可能,并放入到这个list中。
List<Object[]> posNumList = new ArrayList<Object[]>();
permutation(rawNum, 0, length - 1, posNumList);
while (count < oneLastList.size() && !isVampireNumber) {
if (lastZeroFlag) {
oneLast = 0;
} else {
oneLast = oneLastList.get(count);
}
otherLast = otherLastList.get(count);
int two = 0;
int one = 0;
for (int m = 0; m < posNumList.size(); m++) {
Integer[] list = (Integer[]) posNumList.get(m);
two = 0;
one = 0;
// 将数组分为两半,每一半的末位为oneLast或otherLast时继续进行判断,否则continue
if (list[length - 1] == oneLast && list[half - 1] == otherLast) {
two += oneLast;
for (int i = half; i < length - 1; i++) {
int tmp = 1;
for (int j = 0; j < length - 1 - i; j++) {
tmp *= 10;
}
two += list[i] * tmp;
}
one += otherLast;
for (int i = 0; i < half - 1; i++) {
int tmp = 1;
for (int j = 0; j < half - 1 - i; j++) {
tmp *= 10;
}
one += list[i] * tmp;
}
} else if (list[length - 1] == otherLast
&& list[half - 1] == oneLast) {
two += otherLast;
for (int i = half; i < length - 1; i++) {
int tmp = 1;
for (int j = 0; j < length - 1 - i; j++) {
tmp *= 10;
}
two += list[i] * tmp;
}
one += oneLast;
for (int i = 0; i < half - 1; i++) {
int tmp = 1;
for (int j = 0; j < half - 1 - i; j++) {
tmp *= 10;
}
one += list[i] * tmp;
}
}
if (two * one == value) {
isVampireNumber = true;
sonNum1 = one;
sonNum2 = two;
break;
}
}
count++;
}
}
/** * 耗时最短的全排列算法<br> * 从集合中依次选出每一个元素,作为排列的第一个元素,然后对剩余的元素进行全排列,如此递归处理, * 从而得到所有元素的全排列。以对字符串abc进行全排列为例,我们可以这么做:以abc为例: * 固定a,求后面bc的排列:abc,acb,求好后,a和b交换,得到bac * 固定b,求后面ac的排列:bac,bca,求好后,c放到第一位置,得到cba 固定c,求后面ba的排列:cba,cab。 * * @param str * 原始元素列表 * @param first * 排列的起始位置 * @param end * 排列的结束位置 * @param posNumList * 所有的排列结果 */
public void permutation(Object[] str, int first, int end,
List<Object[]> posNumList) {
if (first == end) { // 输出一个排列方式
Object[] result = Arrays.copyOf(str, str.length);
posNumList.add(result);
}
for (int i = first; i <= end; i++) {
swap(str, i, first);
permutation(str, first + 1, end, posNumList); // 固定好当前一位,继续排列后面的
swap(str, i, first);
}
}
/** * 交换两个位置i和first的元素 * * @param str * @param i * @param first */
private void swap(Object[] str, int i, int first) {
if (str instanceof Integer[]) {
Integer tmp;
tmp = (Integer) str[first];
str[first] = str[i];
str[i] = tmp;
} else if (str instanceof String[]) {
String tmp;
tmp = (String) str[first];
str[first] = str[i];
str[i] = tmp;
}
}
public int[] getSonNum() {
return new int[] { sonNum1, sonNum2 };
}
public boolean isVampireNumber() {
return isVampireNumber;
}
public void setVampireNumber(boolean isVampireNumber) {
this.isVampireNumber = isVampireNumber;
}
public static void main(String[] args) {
VampireNumber vn = null;
int count = 0;
long l = System.currentTimeMillis();
for (int i = 0; i < 10000; i++) {
vn = new VampireNumber(i);
if (vn.isVampireNumber()) {
System.out.println(i + "=" + vn.getSonNum1() + "*"
+ vn.getSonNum2());
count++;
}
}
System.out.println((System.currentTimeMillis() - l) + "共计:" + count);
}
}
测试结果:
- 10000以下的吸血鬼数字共7个,耗时133毫秒
- 1000000以下的吸血鬼数字共147个,耗时29362毫秒
详细输出如下:
找到0-10000之间所有的吸血鬼数字: 1260=21*60 1395=15*93 1435=41*35 1530=51*30 1827=87*21 2187=27*81 6880=86*80 耗时:133毫秒,找到吸血鬼数字共计:7 找到0-1000000之间所有的吸血鬼数字: 1260=21*60 1395=15*93 1435=41*35 1530=51*30 1827=87*21 2187=27*81 6880=86*80 102510=201*510 104260=401*260 105210=501*210 105264=516*204 105750=150*705 108135=135*801 110758=158*701 115672=152*761 116725=161*725 117067=167*701 118440=141*840 123354=231*534 124483=281*443 125248=152*824 125433=231*543 125460=246*510 126027=201*627 126846=261*486 129640=140*926 129775=179*725 131242=311*422 132430=323*410 133245=315*423 134725=317*425 135828=588*231 135837=351*387 136525=635*215 136948=146*938 140350=401*350 145314=414*351 146137=461*317 146952=156*942 152608=251*608 152685=585*261 153436=356*431 156240=651*240 156289=581*269 156915=165*951 162976=176*926 163944=396*414 172822=782*221 173250=750*231 174370=470*371 175329=759*231 180225=801*225 180297=897*201 182250=810*225 182650=281*650 186624=864*216 190260=906*210 192150=915*210 193257=327*591 193945=395*491 197725=719*275 201852=252*801 205785=255*807 211896=216*981 213466=341*626 215860=251*860 216733=671*323 217638=678*321 218488=248*881 226498=269*842 226872=276*822 229648=248*926 233896=338*692 241564=461*524 245182=422*581 251896=296*851 253750=350*725 254740=542*470 260338=323*806 262984=284*926 263074=602*437 284598=489*582 284760=420*678 286416=612*468 296320=926*320 304717=431*707 312475=431*725 312975=321*975 315594=534*591 319059=351*909 319536=336*951 326452=623*524 329346=342*963 329656=356*926 336550=635*530 336960=360*936 338296=392*863 341653=641*533 346968=366*948 361989=369*981 362992=392*926 365638=686*533 368550=630*585 369189=381*969 371893=383*971 378418=878*431 378450=870*435 384912=891*432 386415=831*465 392566=593*662 404968=446*908 414895=491*845 416650=641*650 416988=468*891 428980=482*890 429664=464*926 447916=476*941 456840=540*846 458640=546*840 475380=570*834 486720=624*780 489159=891*549 489955=899*545 498550=845*590 516879=681*759 529672=572*926 536539=563*953 538650=855*630 559188=588*951 567648=657*864 568750=650*875 629680=680*926 638950=650*983 673920=720*936 729688=788*926 736695=765*963 738468=876*843 769792=776*992 789525=825*957 792585=927*855 794088=984*807 809919=891*909 809964=894*906 815958=858*951 829696=896*926 841995=891*945 939658=953*986 耗时:29462毫秒,找到吸血鬼数字共计:147