- 1吸血鬼数字介绍
- 2实现思路
- 3代码实例
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