我正在解决一个问题,找出所有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
我尝试过强力算法来组合给定数字的不同数字并将它们相乘。但是这种方法效率很低,占用了很多时间。
这个问题是否有更有效的算法解决方案?
答案 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++;
}
}
}