吸血鬼数字(多种解法)

最近在读《Thinking in JAVA》,在里面发现一个很有意思的题,该题在75页,第四章第十题

原题:

吸血鬼数字是指位数为偶数的数字,可以由一堆数字想乘而得到。而这对数字各包含乘积的一半位数的数字,其中从最初的数字中选取的数字可以任意排序。以两个0结尾的数字是不允许的,例如,下列的数字都是“吸血鬼”数字:

1260=21*60

1827=21*87

2187=27*81

写出一个程序,找出4位数的所有吸血鬼数字

先列出一种书上给的答案,程序我稍微优化了一下,思想没变

public static int a(int i) {
        return i/1000;
    }
    public static int b(int i) {
        return (i%1000)/100;
    }
    public static int c(int i) {
        return ((i%1000)%100)/10;
    }
    public static int d(int i) {
        return i%10;
    }
    public static int com(int i, int j) {
        return (i * 10) + j;
    }
    public static void productTest (int i, int m, int n) {
        num++;
        if(m * n == i) System.out.println(i + " = " + m + " * " + n);
    }

 public static void main(String[] args) {

    for(int i = 1001; i < 9999; i++) {
            int a=a(i);
            int b=b(i);
            int c=c(i);
            int d=d(i);
            productTest(i, com(a, b), com(c, d));
            productTest(i, com(a, b), com(d, c));
            productTest(i, com(a, c), com(b, d));
            productTest(i, com(a, c), com(d, b));
            productTest(i, com(a, d), com(b, c));
            productTest(i, com(a, d), com(c, b));
            productTest(i, com(b, a), com(c, d));
            productTest(i, com(b, a), com(d, c));
            productTest(i, com(b, c), com(d, a));
            productTest(i, com(b, d), com(c, a));
            productTest(i, com(c, a), com(d, b));
            productTest(i, com(c, b), com(d, a));
        }

}

 该方法共运算了107976次 用时26ms

每个人电脑不一样用时可能不一样,但是运算次数肯定是一样的

这种方法简单粗暴,一个遍历,然后就是挑,因为这是前章的内容,所以作者也没用太高明的方法,主要演示循流程控制

我一寻思,不行啊,我写一个更好的

四个数两两组合想乘等于一个四位数,那这个四位数肯定不能有两个0

解释一下:首先千位不能是0,两个零都放到低位想乘得0,只能左边一个右边一个,如果是百位和十位是0,那么想乘出来个位不是0,与条件相反,如果百位和个位是0,那么想乘出来十位和个位是0,与条件想法,所以说四位数肯定不能有两个0

加上这个过滤跑一下试试

 for(int i = 1001; i < 9999; i++) {
            //得到百位数、十位数、个位数
            int a=a(i);
            int b=b(i);
            int c=c(i);
            int d=d(i);
            //如果个十百千四个数中有两个是0,那么就不可能是吸血鬼数字
            // 千位肯定不是0,剩下的三个数俩是0,所以三个数的和等于其中一个数
            if (b+c+d==b||b+c+d==c||b+c+d==d){
                continue;
            }
            productTest(i, com(a, b), com(c, d));
            productTest(i, com(a, b), com(d, c));
            productTest(i, com(a, c), com(b, d));
            productTest(i, com(a, c), com(d, b));
            productTest(i, com(a, d), com(b, c));
            productTest(i, com(a, d), com(c, b));
            productTest(i, com(b, a), com(c, d));
            productTest(i, com(b, a), com(d, c));
            productTest(i, com(b, c), com(d, a));
            productTest(i, com(b, d), com(c, a));
            productTest(i, com(c, a), com(d, b));
            productTest(i, com(c, b), com(d, a));
        }

该方法共运算了104964次,时间26ms

好像并没啥变化哈,只有运算少了一点点

那我们反过来想,不拆四位数了,我们两个两位数想乘等于四位数,然后拼接两个两位数成数组,经过排序后和四位数的数组对比

for (int i=10;i<100;i++){
            for (int j=i+1;j<100;j++){
                int target=i*j;
                if (target<1000||target>9999){
                    continue;
                }
                num++;
                int[] targetNum = { target / 1000, target / 100 % 10, target / 10 % 100 % 10, target%10 };
                int[] strNum = { i % 10, i / 10, j % 10, j / 10 };
                Arrays.sort(targetNum);
                Arrays.sort(strNum);
                if (Arrays.equals(targetNum,strNum)){
                    System.out.println(target + " = " + i + " * " + j);
                }
            }
        }

该方法共运算了3271次 时间13ms

确实有效,无论是运算次数还是时间都缩短了不少

想不到更好的办法就百度,找到一种不错的,它用了一种很巧妙的过滤,大体思路是这样的:

首先2个数的乘积是4位数(大于等于1000,小于10000),一个数确定时,另一个数范围随之确定

随后不能有两个数为0,千位不是0,所以说后两位不能是0

最后四位数减去前两位和后两位可以被9整除

前面两个条件没问题,主要是最后条件很奇怪:

假设a,b,c,d分别代表千位数、百位数、十位数、个位数,x,y分别代表两个两位数,那么a*1000+b*100+c*10+d=四位数

10x+y=四位数,x=10*a+b,y=10*c+d,四位数-x-y=990a+99b+9c=9*(110a+11b+c)

所以说四位数减两个两位数的结果肯定可以被9整除

String[] ar_str1, ar_str2;
        int from;
        int to;
        int i_val;
        for (int i = 10; i < 100; i++) {
            from = Math.max(1000 / i, i + 1);
            to = Math.min(10000 / i, 100);
            // 2个数的乘积是4位数(大于等于1000,小于10000),i确定时,另一个数范围随之确定
            for (int j = from; j < to; j++) {
                i_val = i * j;
                if (i_val % 100 == 0 || (i_val - i - j) % 9 != 0) {
                    continue;
                }
                num++;
                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)) {
                    System.out.println(i_val + " = " + i + " * " + j);
                }
            }
        }

该方法共运算了232次,时间60ms

运算次数一下子减少了很多,但是时间却上涨了不少,我想可能是后面字符串导致的,整形数组比较远比字符串数组操作快速,因为字符串数组的底层实现也是整形数组(我猜的,有错误请指正)

int[] startDigit = new int[4];
        int[] productDigit = new int[4];
        int target;
        for (int i = 10; i <= 99; i++) {
            for (int j = i; j <= 99; j++) {
                target = i * j;
                if (target % 100 == 0 || (target - i - j) % 9 != 0) {
                    continue;
                }
                startDigit[0] = i / 10;
                startDigit[1] = i % 10;
                startDigit[2] = j / 10;
                startDigit[3] = j % 10;
                productDigit[0] = target / 1000;
                productDigit[1] = (target % 1000) / 100;
                productDigit[2] = target % 1000 % 100 / 10;
                productDigit[3] = target % 10;
                int count = 0;
                for (int x = 0; x < 4; x++) {
                    for (int y = 0; y < 4; y++) {
                        num++;
                        if (productDigit[x] == startDigit[y]) {
                            count++;
                            productDigit[x] = -1;
                            startDigit[y] = -2;
                            if (count == 4) {
                                System.out.println(target + " = " + i + " * " + j);
                            }
                        }
                    }
                }
            }
        }

该方法共运算了4712次,时间1ms

过滤后有232个数,每个数颠倒顺序比对16遍

前面那个范围过滤没什么必要,可以减少一定次数,但是时间也会增加

如果你有更好的方法请留言给我

 

  • 27
    点赞
  • 25
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 5
    评论
评论 5
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

幽蓝丶流月

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值