找到所有4位数的吸血鬼号码

时间:2013-06-27 19:57:12

标签: algorithm

我正在解决一个问题,找出所有4位数的吸血鬼号码。

吸血鬼数 v = x * y定义为通过乘以一对'n / 2'位数字形成的'n'偶数位数的数字(其中数字为取决于任何顺序的原始数字)x和y在一起。如果v是吸血鬼号码,则x& y被称为“f牙”。

吸血鬼号码的例子如下:

    1.    1260=21*60
    2.    1395=15*93
    3.    1530=30*51

我尝试过强力算法来组合给定数字的不同数字并将它们相乘。但是这种方法效率很低,占用了很多时间。

这个问题是否有更有效的算法解决方案?

16 个答案:

答案 0 :(得分:35)

或者您可以使用this page(从维基百科链接)中描述的吸血鬼号码属性:

  

Pete Hartley发现的重要理论结果:

      If x·y is a vampire number then x·y == x+y (mod 9) 
     

证明:令mod为二进制模运算符,d(x)为小数和   x的数字。众所周知,对于所有x,d(x)mod 9 = x mod 9。   假设x·y是吸血鬼。然后它包含与x和y相同的数字,   特别是d(x·y)= d(x)+ d(y)。这导致:             (x·y)mod 9 = d(x·y)mod 9 =(d(x)+ d(y))mod 9 =(d(x)mod 9 + d(y)mod 9)mod 9               =(x mod 9 + y mod 9)mod 9 =(x + y)mod 9

     

{{0}}中congruence的解是(x mod 9,y mod 9),   (2,2),(3,6),(5,8),(6,3),(8,5)}

所以你的代码看起来像这样:

for(int i=18; i<100; i=i+9){          // 18 is the first multiple of 9 greater than 10
   for(int j=i; j<100; j=j+9){        // Start at i because as @sh1 said it's useless to check both x*y and y*x
       checkVampire(i,j);
   }
}

for(int i=11; i<100; i=i+9){          // 11 is the first number greater than 10 which is = 2 mod 9
   for(int j=i; j<100; j=j+9){
       checkVampire(i,j);
   }
}

for(int i=12; i<100; i=i+9){
   for(int j=i+3; j<100; j=j+9){
       checkVampire(i,j);
   }
}

for(int i=14; i<100; i=i+9){
   for(int j=i+3; j<100; j=j+9){
       checkVampire(i,j);
   }
}

// We don't do the last 2 loops, again for symmetry reasons

由于它们是每个集合中的40个元素,如{(x mod 9, y mod 9) = (0,0); 10 <= x <= y <= 100},因此只有蛮力为您提供104次迭代时才会进行4*40 = 160次迭代。如果考虑>= 1000约束,则可以执行更少的操作,例如,您可以避免检查是否j < 1000/i

现在,您可以轻松扩展以查找超过4位数的吸血鬼=)

答案 1 :(得分:9)

迭代所有可能的f牙(100 x 100 = 10000种可能性),并找出他们的产品是否与f牙的数字相同。

答案 2 :(得分:5)

另一个暴力(C)版本,带有免费的冒泡排序......

#include <stdio.h>

static inline void bubsort(int *p)
{ while (1)
  { int s = 0;

    for (int i = 0; i < 3; ++i)
      if (p[i] > p[i + 1])
      { s = 1;
        int t = p[i]; p[i] = p[i + 1]; p[i + 1] = t;
      }

    if (!s) break;
  }
}

int main()
{ for (int i = 10; i < 100; ++i)
    for (int j = i; j < 100; ++j)
    { int p = i * j;

      if (p < 1000) continue;

      int xd[4];
      xd[0] = i % 10;
      xd[1] = i / 10;
      xd[2] = j % 10;
      xd[3] = j / 10;

      bubsort(xd);
      int x = xd[0] + xd[1] * 10 + xd[2] * 100 + xd[3] * 1000;

      int yd[4];
      yd[0] = p % 10;
      yd[1] = (p / 10) % 10;
      yd[2] = (p / 100) % 10;
      yd[3] = (p / 1000);

      bubsort(yd);
      int y = yd[0] + yd[1] * 10 + yd[2] * 100 + yd[3] * 1000;

      if (x == y)
        printf("%2d * %2d = %4d\n", i, j, p);
    }

  return 0;
}

几乎瞬间运行。变量名称不是太具描述性,但应该非常明显......

基本思路是从两个潜在的尖牙开始,将它们分解为数字,然后对数字进行排序以便于比较。然后我们对产品做同样的事情 - 将其分解为数字和排序。然后我们从排序的数字中重新构成两个整数,如果它们相等,我们就匹配了。

可能的改进:1)在j而不是1000 / i处开始i以避免必须if (p < 1000) ...,2)可能使用插入排序而不是冒泡排序(但是谁会注意到这两个额外的交换?),3)使用真正的swap()实现,4)直接比较数组而不是从中构建合成整数。但是,不确定其中任何一个会产生任何可衡量的差异,除非你在Commodore 64或其他东西上运行它......

编辑:出于好奇,我采用了这个版本,并将其概括为4,6和8位数的情况 - 没有任何重大优化,它可以在&lt;中找到所有8位数的吸血鬼数字。 10秒......

答案 3 :(得分:3)

这是一个丑陋的黑客(蛮力,手动检查排列,不安全的缓冲操作,产生欺骗等)但它完成了这项工作。你的新练习是改进它:P

Wikipedia claims有7个吸血鬼数字,长度为4位数。 full code has found them all,甚至有些重复。

修改: Here's a slightly better comparator function.

编辑2: Here's a C++ version使用std::map显示结果(因此避免重复)(并存储特定吸血鬼号码的最后一次出现及其因素)它)。它还符合以下标准:至少有一个因素不应以0结束,i。即如果两个被乘数都可以被整除,则数字不是吸血鬼数字。根据维基百科的观点,这个测试会查找6位数的吸血鬼数字,确实可以找到其中的148个。


原始代码:

#include <stdio.h>

void getdigits(char buf[], int n)
{
    while (n) {
        *buf++ = n % 10;
        n /= 10;
    }
}

int is_vampire(const char n[4], const char i[2], const char j[2])
{
    /* maybe a bit faster if unrolled manually */
    if (i[0] == n[0]
     && i[1] == n[1]
     && j[0] == n[2]
     && j[1] == n[3])
        return 1;

    if (i[0] == n[1]
     && i[1] == n[0]
     && j[0] == n[2]
     && j[1] == n[3])
            return 1;

    if (i[0] == n[0]
     && i[1] == n[1]
     && j[0] == n[3]
     && j[1] == n[2])
            return 1;

    if (i[0] == n[1]
     && i[1] == n[0]
     && j[0] == n[3]
     && j[1] == n[2])
            return 1;

    // et cetera, the following 20 repetitions are redacted for clarity
    // (this really should be a loop, shouldn't it?)

    return 0;
}

int main()
{
    for (int i = 10; i < 100; i++) {
        for (int j = 10; j < 100; j++) {
            int n = i * j;
            if (n < 1000)
                continue;

            char ndigits[4];
            getdigits(ndigits, n);

            char idigits[2];
            char jdigits[2];
            getdigits(idigits, i);
            getdigits(jdigits, j);

            if (is_vampire(ndigits, idigits, jdigits))
                printf("%d * %d = %d\n", i, j, n);
        }
    }

    return 0;
}

答案 4 :(得分:1)

我不会轻易放弃蛮力。您有一组不同的数字,必须经过1000到9999。我将该集合分成若干个子集,然后旋转线程来处理每个子集。

你可以进一步划分工作,提出每个数字的各种组合; IIRC我的离散数学,你可以尝试4 * 3 * 2或24种组合。

生产者/消费者的方法可能是值得的。

答案 5 :(得分:1)

编辑:完全暴力破坏相同的X和Y值...

import java.util.ArrayList;
import java.util.Collections;
import java.util.List;

public class Vampire {

    public static void main(String[] args) {
        for (int x = 10; x < 100; x++) {
            String sx = String.valueOf(x);
            for (int y = x; y < 100; y++) {
                int v = x * y;
                String sy = String.valueOf(y);
                String sv = String.valueOf(v);
                if (sortVampire(sx + sy).equals(sortVampire(sv))) {
                    System.out.printf("%d * %d = %d%n", x, y, v);
                }
            }
        }
    }

    private static List<Character> sortVampire(String v) {
        List<Character> vc = new ArrayList<Character>();
        for (int j = 0; j < v.length(); j++) {
            vc.add(v.charAt(j));
        }
        Collections.sort(vc);
        return vc;
    }
}

答案 6 :(得分:1)

迭代对我来说似乎很好,因为你只需要这样做一次就可以找到所有的值,然后你就可以将它们缓存了。 Python(3)版本需要大约1.5秒:

# just some setup
from itertools import product, permutations
dtoi = lambda *digits: int(''.join(str(digit) for digit in digits))
gen = ((dtoi(*digits), digits) for digits in product(range(10), repeat=4) if digits[0] != 0)
l = []

for val, digits in gen:
    for check1, check2 in ((dtoi(*order[:2]), dtoi(*order[2:])) for order in permutations(digits) if order[0] > 0 and order[2] > 0):
        if check1 * check2 == val:
            l.append(val)
            break

print(l)

哪个会给你[1260, 1395, 1435, 1530, 1827, 2187, 6880]

答案 7 :(得分:1)

使用LINQ的C#中的暴力版本:

class VampireNumbers
{
    static IEnumerable<int> numberToDigits(int number)
    {
        while(number > 0)
        {
            yield return number % 10;
            number /= 10;
        }
    }

    static bool isVampire(int first, int second, int result)
    {
        var resultDigits = numberToDigits(result).OrderBy(x => x);

        var vampireDigits = numberToDigits(first)
                             .Concat(numberToDigits(second))
                             .OrderBy(x => x);                                  

        return resultDigits.SequenceEqual(vampireDigits);
    }

    static void Main(string[] args)
    {
        var vampires = from fang1 in Enumerable.Range(10, 89)
                       from fang2 in Enumerable.Range(10, 89)
                       where fang1 < fang2
                             && isVampire(fang1, fang2, fang1 * fang2)       
                       select new { fang1, fang2 };

        foreach(var vampire in vampires)
        {
            Console.WriteLine(vampire.fang1 * vampire.fang2 
                              + " = " 
                              + vampire.fang1 
                              + " * " 
                              + vampire.fang2);
        }
    }
}

答案 8 :(得分:1)

与上面提到的人类似,我的方法是首先找到一个数字的所有排列,然后将它们分成两半以形成两个2位数字,并测试它们的乘积是否等于原始数字。

上面另一个有趣的讨论是一个数字可以有多少个排列。这是我的意见: (1)四位数相同的数字有1个排列; (2)只有两个不同数字的数字有6个排列(如果它包含零则无关紧要,因为如果它仍然是4位数字,我们不会在排列后关心); (3)具有三个不同数字的数字有12个排列; (4)具有所有四个不同数字的数字具有24个排列。

public class VampireNumber {

// method to find all permutations of a 4-digit number
public static void permuta(String x, String s, int v)
{for(int i = 0; i < s.length(); i++)
{permuta( x + s.charAt(i), s.substring(0,i) + s.substring(i+1), v);
if (s.length() == 1)
    {x = x + s;
    int leftpart = Integer.parseInt(x.substring(0,2));
    int rightpart = Integer.parseInt(x.substring(2));
    if (leftpart*rightpart == v) 
        {System.out.println("Vampir = " + v);
        }
    }
  }
}

public static void main(String[] args){
for (int i = 1000; i < 10000; i++) {
permuta("", Integer.toString(i), i); //convert the integer to a string
}
}
}

答案 9 :(得分:0)

我尝试的方法是循环遍历[1000,9999]中的每个数字,并测试其数字的任何排列(在中间分割)是否相乘以实现它。

这将需要(9999 - 1000)* 24 = 215,976次测试,这些测试应该在现代机器上以可接受的速度执行。

我肯定会分别存储数字,所以你可以避免做一些像一串除法从一个整数中提取数字的东西。

如果你编写代码使得你只进行整数加法和乘法(也许偶尔会进行除法),它应该非常快。您可以通过跳过“显然”不起作用的两位数对来进一步提高速度 - 例如,带有前导零的对(请注意,一位数字和两位数字产生的最大产品是9 * 99,或891)。

另请注意,这种方法非常平行(http://en.wikipedia.org/wiki/Embarrassingly_parallel),因此如果您真的需要加快速度,那么您应该考虑在单独的线程中测试数字。

答案 10 :(得分:0)

在我看来,要在不依赖任何特别抽象的见解的情况下进行尽可能少的测试,你可能想要迭代f牙并剔除任何明显毫无意义的候选人。

例如,由于x*y == y*x只能评估y > x的情况,因此可以消除大约一半的搜索空间for (x = 11; x < 100; x++) { /* start y either at x, or if x is too small then 1000 / x */ for (y = (x * x < 1000 ? 1000 / x : x); y < 100; y++) { int p = x * y; /* if sum of digits in product is != sum of digits in x+y, then skip */ if ((p - (x + y)) % 9 != 0) continue; if (is_vampire(p, x, y)) printf("%d\n", p); } } 。如果最大的两位数牙齿是99,那么最小的四位数可以是11,所以不要低于11。

编辑:

好的,把我想到的所有东西扔进混合物中(尽管它对领先的解决方案看起来很愚蠢)。

int is_vampire(int p, int x, int y)
{
    int h[10] = { 0 };
    int i;
    for (i = 0; i < 4; i++)
    {
        h[p % 10]++;
        p /= 10;
    }
    for (i = 0; i < 2; i++)
    {
        h[x % 10]--;
        h[y % 10]--;
        x /= 10;
        y /= 10;
    }
    for (i = 0; i < 10; i++)
        if (h[i] != 0)
            return 0;
    return 1;
}

和测试,因为我还没有看到有人使用直方图,但是:

{{1}}

答案 11 :(得分:0)

<?php
for ($i = 10; $i <= 99; $j++) {

    // Extract digits
    $digits = str_split($i);

    // Loop through 2nd number
    for ($j = 10; $j <= 99; $j++) {

         // Extract digits
         $j_digits = str_split($j);
         $digits[2] = $j_digits[0];
         $digits[3] = $j_digits[1];

         $product = $i * $j;

         $product_digits = str_split($product);

         // check if fangs

         $inc = 0;

         while (in_array($digits[$inc], $product_digits)) {

            // Remove digit from product table
               /// So AAAA -> doesnt match ABCD
             unset($product_digits[$array_serach($digits[$inc], $product_digits)]);

             $inc++;

             // If reached 4 -> vampire number
             if ($inc == 4) {

                  $vampire[] = $product;
                  break;
             }
         }
    }
}

// Print results
print_r($vampire);
?>

在PHP上花了不到一秒钟。甚至不能告诉它必须运行8100计算...计算机速度很快!

结果:

给你所有4位数字加上一些重复。您可以进一步处理数据并删除重复项。

答案 12 :(得分:0)

1260 1395 1435 1530 1827 2187 6880是吸血鬼

我是编程新手......但是找到所有4位数的吸血鬼数字只有12种组合。我的答案很糟糕:

public class VampNo {

    public static void main(String[] args) {
        for(int i = 1000; i < 10000; i++) {

            int a = i/1000;
            int b = i/100%10;
            int c = i/10%10;
            int d = i%10;

                 if((a * 10 + b) * (c * 10 + d) == i || (b * 10 + a) * (d * 10 + c) == i ||
                    (a * 10 + d) * (b * 10 + c) == i || (d * 10 + a) * (c * 10 + b) == i ||
                    (a * 10 + c) * (b * 10 + d) == i || (c * 10 + a) * (d * 10 + b) == i ||
                    (a * 10 + b) * (d * 10 + c) == i || (b * 10 + a) * (c * 10 + d) == i ||
                    (b * 10 + c) * (d * 10 + a) == i || (c * 10 + b) * (a * 10 + d) == i ||
                    (a * 10 + c) * (d * 10 + b) == i || (c * 10 + a) * (b * 10 + d) == i)
                System.out.println(i + " is vampire");

        }
    }
}

现在的主要任务是简化If()块

中的布尔表达式

答案 13 :(得分:0)

我编辑了Owlstead的算法,使Java初学者/学习者更容易理解。

import java.util.Arrays;

public class Vampire {

  public static void main(String[] args) {
    for (int x = 10; x < 100; x++) {
        String sx = Integer.toString(x);
        for (int y = x; y < 100; y++) {
            int v = x * y;
            String sy = Integer.toString(y);
            String sv = Integer.toString(v);
            if( Arrays.equals(sortVampire(sx + sy), sortVampire(sv))) 
                System.out.printf("%d * %d = %d%n", x, y, v);
        }
    }
}
private static char[] sortVampire (String v){
    char[] sortedArray = v.toCharArray();
    Arrays.sort(sortedArray);
    return sortedArray;
}

}

答案 14 :(得分:0)

这个python代码运行得非常快(O(n2))

result = []
for i in range(10,100):
    for j in range(10, 100):
        list1 = []
        list2 = []
        k = i * j
        if k < 1000 or k > 10000:
            continue
        else:
            for item in str(i):
                list1.append(item)
            for item in str(j):
                list1.append(item)
            for item in str(k):
                list2.append(item)
            flag = 1  
            for each in list1:
                if each not in list2:
                    flag = 0
                else:
                    list2.remove(each)
            for each in list2:
                if each not in list1:
                    flag = 0

            if flag == 1:
                if k not in result:
                    result.append(k)
for each in result:
    print(each)          

答案 15 :(得分:0)

这是我的代码。要生成僵尸数字,我们需要使用Random类:)

import java.io.PrintStream;
import java.util.Set;
import java.util.HashSet;
import java.util.Iterator;

class VampireNumbers {
    static PrintStream p = System.out;

    private static Set<Integer> findVampireNumber() {
        Set<Integer> vampireSet = new HashSet<Integer>();
        for (int y = 1000; y <= 9999; y++) {
            char[] numbersSeparately = ("" + y).toCharArray();
            int numberOfDigits = numbersSeparately.length;
            for (int i = 0; i < numberOfDigits; i++) {
                for (int j = 0; j < numberOfDigits; j++) {
                    if (i != j) {
                        int value1 = Integer.valueOf("" + numbersSeparately[i] + numbersSeparately[j]);
                        int ki = -1;
                        for (int k = 0; k < numberOfDigits; k++) {
                            if (k != i && k != j) {
                                ki = k;
                            }
                        }
                        int kj = -1;
                        for (int t = 0; t < numberOfDigits; t++) {
                            if (t != i && t != j && t != ki) {
                                kj = t;
                            }
                        }

                        int value21 = Integer.valueOf("" + numbersSeparately[ki] + numbersSeparately[kj]);
                        int value22 = Integer.valueOf("" + numbersSeparately[kj] + numbersSeparately[ki]);
                        if (value1 * value21 == y && !(numbersSeparately[j] == 0 && numbersSeparately[kj] == 0)
                                || value1 * value22 == y
                                        && !(numbersSeparately[j] == 0 && numbersSeparately[ki] == 0)) {
                            vampireSet.add(y);
                        }
                    }
                }
            }
        }
        return vampireSet;
    }

    public static void main(String[] args) {
        Set<Integer> vampireSet = findVampireNumber();
        Iterator<Integer> i = vampireSet.iterator();
        int number = 1;
        while (i.hasNext()) {
            p.println(number + ": " + i.next());
            number++;
        }
    }
}