4位吸血鬼数字的java实现思路与实例讲解

这个问题来源于Java编程思想一书,所谓“吸血鬼数字”就是指位数为偶数的数字,可以由一对数字相乘而得到,而这对数字各包含乘积的一半位数字,其中从偶数位数字中选取的数字可以任意排列。例如:

1260=21*60,1827=21*87,2187=27*81……

先列出结果:

一共7个:

1260=21*60,1395=15*93,1435=41*35,1530=51*30,1827=87*21,2187=27*81,6880=86*80

第一种思路对所有的4位数进行穷举,假设这个4位数是abcd,可以表示为1000a+100b+10c+d。那么由这个4位数中抽出的2位数的组合有如下12种(abcd分别为0-9的数字,能作为X、Y的十位上的数字必为1-9):

  • ①X=10*a + b, Y=10*c + d ---不可能
  • ②X=10*a + b, Y=10*d + c ---不可能
  • ③X=10*a + c, Y=10*b + d ---不可能
  • ④X=10*a + c, Y=10*d + b ---不可能
  • ⑤X=10*a + d, Y=10*b + c ---不可能
  • ⑥X=10*a + d, Y=10*c + b
  • ⑦X=10*b + a, Y=10*c + d
  • ⑧X=10*b + a, Y=10*d + c
  • ⑨X=10*b + c, Y=10*d + a ---不可能
  • ⑩X=10*b + d, Y=10*c + a
  • ⑾X=10*c + a, Y=10*d + b
  • ⑿X=10*c + b, Y=10*d + a

这12种组合中,有没有可能其中某些情况是不可能满足1000a+100b+10c+d=X*Y的?如果能直接去掉就能减少不必要的计算。

假设①可能找出匹配的吸血鬼数字,那么存在等式:(10*a + b)*(10*c + d) = 1000a+100b+10c+d = 100*(10*a + b) + 10*c + d
<=>(10*a + b)*(10*c + d - 100) = 10*c + d

因为左边(10*c + d - 100)是负数,而右边为正数,不可能相等;又因为在上式中c/d具有互换性,故不可能通过①②找到吸血鬼数。

假设③可能找出匹配的吸血鬼数字,那么存在等式:(10*a + c)*(10*b + d) = 1000a+100b+10c+d
<=>100*a*b + 10*(a*d+b*c) + cd = 100*a*b - 100*a*b + 1000*a + 100*b + 10*c +d = 100*a*b + 10*[10*a*(10-b) + 10*b] + 10*c +d
<=>10*(a*d+b*c) + cd = 10*[10*a*(10-b) + 10*b] + 10*c +d

因为两边的子项都有a*d<10*a*(10-b),b*c<10*b,cd<10*c +d,所以右边恒大于左边;又因为在上式中b/d具有互换性,故不可能通过③④找到吸血鬼数。

假设10*b + c的组合⑤能找到吸血鬼数字,那么存在等式:(10*a + d)*(10*b + c) = 1000*a + 10*(10*b + c) + d
<=>(10*b + c)*(10*a + d - 10) = 1000*a + d = 100*(10a + d/100)

因为左边(10*b + c)<100,(10*a + d - 10)<(10a + d/100),所以右边恒大于左边;又因为在上式中a/d具有互换性,故不可能通过⑤⑨找到吸血鬼数。

另外4位数中包含两个及以上0的是不可能为吸血鬼数字,原因:假如包含2个零,则只能拆出10*a和10*b形式,乘积的结果后两位必为ZZ00的形式;于是等式就退化为a*b =10*a + b,变换为b=10*a/(a-1) >10,而b不可能大于10。

实现代码如下:

/**
 * VampireNumbers<br />
 * 求所有的4位吸血鬼数
 * @author South Park
 */
public final class VampireNumbers {
 private static final StringBuilder outputStr = new StringBuilder(14);
 private static final String equalSign = " = ";
 private static final String multiSign = " * ";
 /**
 * 如果这两个2位数的乘积等于原来的数则成功,打印该数字
 * @param i 4位数的值
 * @param m 其中一个2位数
 * @param n 另外一个2位数
 */
 private static final void productTest (final int i, final int m, final int n) {
 // 如果满足条件,就输出
 if(m * n == i) {
  outputStr.append(i).append(equalSign).append(m).append(multiSign).append(n);
  System.out.println(outputStr.toString());
  outputStr.delete(0, 14);
 }
 }
 /**
 * 从1011开始到9998,循环尝试每个4位数的各种分拆组合
 * @param args
 */
 public static void main(String[] args) {
 int count = 0;// 计数循环次数
 long startTime = System.nanoTime();
 // 分别表示千百十各位
 int a,b,c,d;
 // 4位数中含零的个数
 int zeroCount = 0;
 for(short i = 1011; i < 9999; i++) {
  zeroCount = 0;
  String value = ""+i;
  // 获取各位中的值(0-9)
  a = value.charAt(0) - 48;//千位
  b = value.charAt(1) - 48;//百位
  c = value.charAt(2) - 48;//十位
  d = value.charAt(3) - 48;//个位
  if (b == 0)
  zeroCount++;
  if (c == 0)
  zeroCount++;
  if (d == 0)
  zeroCount++;
  // 数字中含有2个以上的零排除
  if (zeroCount >= 2) {
  continue;
  }
  count++;
  //productTest(i, 10*a + b, 10*c + d);
  //productTest(i, 10*a + b, 10*d + c);
  //productTest(i, 10*a + c, 10*b + d);
  //productTest(i, 10*a + c, 10*d + b);
  //productTest(i, 10*a + d, 10*b + c);
  productTest(i, 10*a + d, 10*c + b);
  productTest(i, 10*b + a, 10*c + d);
  productTest(i, 10*b + a, 10*d + c);
  //productTest(i, 10*b + c, 10*d + a);
  productTest(i, 10*b + d, 10*c + a);
  productTest(i, 10*c + a, 10*d + b);
  productTest(i, 10*c + b, 10*d + a);
 }
 System.out.println(System.nanoTime() - startTime);
 // 输出循环次数
 System.out.println("loop count:" + count);
 }
}

输出结果:

1260 = 21 * 60
1395 = 15 * 93
1435 = 41 * 35
1530 = 51 * 30
1827 = 87 * 21
2187 = 27 * 81
6880 = 86 * 80
6880 = 80 * 86
12360961
loop count:8747

第二种方式是对分解后的一对XY入手,从10到99进行双重循环穷举,由于乘积结果必须是4位数,也就是范围为1000到9999,故可对第二层循环进行范围限定,减少循环次数。从结果来看,第二种方式的循环次数较少,时间也更少。

对于4位吸血鬼数,必有X*Y-X-Y为9的倍数,因为X*Y-X-Y只有下列6种结果:

  • 9*(110*a + 11*b)
  • 9*(110*a + 10*b + c)
  • 9*(110*a + 11*b + c - d)
  • 9*(111*a + 10*b)
  • 9*(111*a + 11*b - d)
  • 9*(111*a + 10*b + c - d)

代码实现:

import java.util.Arrays;
/**
 * VampireNumbers2<br />
 * 求所有的4位吸血鬼数
 * @author South Park
 */
public final class VampireNumbers2 {
 private static final StringBuilder outputStr = new StringBuilder(14);
 private static final String equalSign = " = ";
 private static final String multiSign = " * ";
 /**
 * 如果这两个2位数的乘积等于原来的数则成功,打印该数字
 * @param i 4位数的值
 * @param m 其中一个2位数
 * @param n 另外一个2位数
 */
 private static final void printVN (final int i, final int m, final int n) {
 outputStr.append(i).append(equalSign).append(m).append(multiSign).append(n);
 System.out.println(outputStr.toString());
 outputStr.delete(0, 14);
 }
 /**
 * 从11开始到99,双重循环尝试每种组合的4位数乘积结果是否刚好包含原来两个2位数的数字
 * @param args
 */
 public static void main(String[] arg) {
 int count = 0;// 计数循环次数
 long startTime = System.nanoTime();
 String vnValueStr = "";
 String multiValueStr = "";
 char[] vnValueArr = new char[4];
 char[] multiValueArr = new char[4];
 int from;
 int to;
 int vampireNumbers;
 // 双重循环穷举
 for (int i = 11; i < 100; i++) {
  // 通过对from和to的计算,缩小第二重循环的次数;j=i+1避免重复
  from = Math.max(1000 / i + 1, i + 1);
  to = Math.min(10000 / i, 100);
  for (int j = from; j < to; j++) {
  count++;
  vampireNumbers = i * j;
  // 过滤掉非吸血鬼数
  if ((vampireNumbers - i - j) % 9 != 0) {
   continue;
  }
  vnValueStr = "" + vampireNumbers;
  vnValueArr[0] = vnValueStr.charAt(0);
  vnValueArr[1] = vnValueStr.charAt(1);
  vnValueArr[2] = vnValueStr.charAt(2);
  vnValueArr[3] = vnValueStr.charAt(3);
  multiValueStr = "" + i + j;
  multiValueArr[0] = multiValueStr.charAt(0);
  multiValueArr[1] = multiValueStr.charAt(1);
  multiValueArr[2] = multiValueStr.charAt(2);
  multiValueArr[3] = multiValueStr.charAt(3);
  Arrays.sort(vnValueArr);
  Arrays.sort(multiValueArr);
  if (Arrays.equals(vnValueArr, multiValueArr)) {// 排序后比较,为真则找到一个吸血鬼数
   printVN(vampireNumbers, i, j);
  }
  }
 }
 System.out.println(System.nanoTime() - startTime);
 // 输出循环次数
 System.out.println(count);
 }
}

输出结果:

1395 = 15 * 93
1260 = 21 * 60
1827 = 21 * 87
2187 = 27 * 81
1530 = 30 * 51
1435 = 35 * 41
6880 = 80 * 86
3024115
3269

由于没有找到吸血鬼数的产生机理,所以只好用穷举法。在这里提高性能的方法主要是减少循环次数,减少不必要的计算。

总结

以上就是这篇文章的全部内容了,希望本文的内容对大家的学习或者工作具有一定的参考学习价值,谢谢大家对我们的支持。如果你想了解更多相关内容请查看下面相关链接

(0)

相关推荐

  • 浅谈Java中static关键字的作用

    static关键字主要有两种作用: 第一,为某特定数据类型或对象分配单一的存储空间,而与创建对象的个数无关. 第二,实现某个方法或属性与类而不是对象关联在一起 具体而言,在Java语言中,static主要有4中使用情况:成员变量.成员方法.代码块和内部类 (1)static成员变量: Java类提供了两种类型的变量:用static关键字修饰的静态变量和不用static关键字修饰的实例变量.静态变量属于类,在内存中只有一个复制,只要静态变量所在的类被加载,这个静态变量就会被分配空间,因此就可以被使

  • 详解关于Windows10 Java环境变量配置问题的解决办法

    关于Windows10 Java环境变量配置问题的解决办法 由于最近有一些时间,所以想要把之前学过一段时间的Java重新捡起来看看,之前的学习环境是Ubuntu,对于环境变量的配置和Windows也没有什么本质的区别,只不过是要用自带的编辑器更改一些东西而已. 那么我先讲讲我对于环境变量的一些自己的理解,由于每次编译源程序的时候需要用到编译工具,而Java的编译工具就是从oracle官网上下载的jdk包中的一些jar文件,所以如果要让系统识别java或者javac命令,那么就必须让系统知道这些文

  • 详解Java在redis中进行对象的缓存

    Java在redis中进行对象的缓存一般有两种方法,这里介绍序列化的方法,个人感觉比较方便,不需要转来转去. 一.首先,在存储的对象上实现序列化的接口 package com.cy.example.entity.system; import java.util.List; import com.baomidou.mybatisplus.annotations.TableField; import com.baomidou.mybatisplus.annotations.TableName; im

  • Java中List add添加不同类型元素的讲解

    问题: 今天看java的list ,list后面的<> 里面可以填多种类型,但是如果不填写类型那就默认为 Object 类型. 所有我门 add 到 list 里的 数据都会被转换成 Object 类型. 而当我门再从list 中取出该数据时,就会发现数据类型已经改变. 解答 java集合中 能添加不同类型的元素其实不同类型的元素,只是地一定层次是不同元素,根本上都继承于Object类,本质上还是同一类型的元素. List<Object> list = new ArrayList&

  • java http token请求代码实例

    本文实例为大家分享了java http token的具体代码,供大家参考,具体内容如下 package com.monitoring.common.util; import java.io.BufferedInputStream; import java.io.BufferedReader; import java.io.ByteArrayOutputStream; import java.io.IOException; import java.io.InputStream; import ja

  • Java实现不同的类的属性之间相互赋值

    在开发的时候可能会出现将一个类的属性值,复制给另外一个类的属性值,这在读写数据库的时候,可能会经常的遇到 ,特别是对于一个有继承关系的类的时候,我们需要重写很多多余的代码,下面有一种简单的方法实现该功能 1.首先有两个类,两个类之间有相同的属性名和类型,也有不同的属性名很类型: public class ClassTestCopy2 { private int id; private String name; private String password; private String sex

  • 详解java解决分布式环境中高并发环境下数据插入重复问题

    java 解决分布式环境中 高并发环境下数据插入重复问题 前言 原因:服务器同时接受到的重复请求 现象:数据重复插入 / 修改操作 解决方案 : 分布式锁 对请求报文生成 摘要信息 + redis 实现分布式锁 工具类 分布式锁的应用 package com.nursling.web.filter.context; import com.nursling.nosql.redis.RedisUtil; import com.nursling.sign.SignType; import com.nu

  • 详解Java虚拟机30个常用知识点之1——类文件结构

    1. Java文件 ClassFileTest.java package com.zxs.ssh.template.service; public class ClassFileTest { int m = 1; public int inc(){ return m+1; } } 2. Class文件ClassFileTest.class javac  ClassFileTest.java  编译.java文件得到.class文件 JDK版本  1.8.0_201 .class文件可以用WinH

  • 详解Java利用同步块synchronized()保证并发安全

    本文实例为大家分享了Java利用同步块synchronized()保证并发安全的具体代码,供大家参考,具体内容如下 package day10; /** * 同步块 * 有效地缩小同步范围 * 可以在保证并发安全的同时尽可能提高并发效率 * * 实例:模拟两个人同时进店买衣服,为提高效率 * 只在试衣服阶段进行同步排队过程,其他阶段无需排队. * @author kaixu * */ public class SyncDemo2 { public static void main(String[

  • Java双重检查加锁单例模式的详解

    什么是DCL DCL(Double-checked locking)被设计成支持延迟加载,当一个对象直到真正需要时才实例化: class SomeClass { private Resource resource = null; public Resource getResource() { if (resource == null) resource = new Resource(); return resource; } } 为什么需要推迟初始化?可能创建对象是一个昂贵的操作,有时在已知的运

随机推荐