The algorithm proposed here will not go through all permutations of numbers. This will allow eliminating the possibilities as quickly as possible so that only a part of the permutations is actually checked.
Algorithm Explained by Example
Here's how it works with number 125460 as an example. If you are good at reading the code directly, you can skip this (long) part:
At first, two fangs (i.e. vampire factors) are obviously unknown, and the problem can be represented as follows:
?** X ?** ------- =125460
For the leftmost digit of the first factor (indicated by ? ), We can choose any digit 0,1,2,5,4 or 6. But with a closer analysis, 0 will not be a viable opportunity, since the product will never reach more than a 5-digit number. Thus, it would be a waste of time to go through all the permutations of numbers starting from zero.
For the leftmost digit of the second factor (also marked ? ), The same is true. However, looking at the combinations, we can again filter out some pairs that cannot contribute to the achievement of the target product. For example, this combination should be discarded:
1** X 2** ------- =125460
The largest number that can be achieved using these numbers is 199x299 = 59501 (ignoring the fact that we donโt even have 9), which does not even make up half the desired number. Therefore, we must abandon the combination (1, 2). For the same reason, the pair (1, 5) can be discarded for these positions. Similarly, pairs (4, 5), (4, 6) and (5, 6) can also be dropped because they produce too much product (> = 200000). I will call this type of test - where it will be determined whether the target number is within reach for a specific selected pair of digits, the "range test".
At this stage, there is no difference between the first and second fangs, so we also do not need to examine pairs where the second digit is less than the first, since they reflect a pair that has already been investigated (or rejected).
So, of all the possible pairs that could take up this first position (there are 30 possibilities to take 2 digits from a set of 6 digits), you only need to examine the following 4:
(1, 6), (2, 4), (2, 5), (2, 6)
In more detailed notations, this means that we restrict the search for these number patterns:
1** 2** 2** 2** X 6** X 4** X 5** X 6** ------- ------- ------- ------- =125460 =125460 =125460 =125460 ABCD
It is clear that this reduction in opportunities, even if you look at other positions, significantly reduces the search tree.
The algorithm takes each of these 4 possibilities in order, and each of them checks the possibilities for the next digit position. Therefore, the first configuration A is analyzed:
1?* X 6?* ------- =125460
Couples available for line items with ? -markets are the following 12:
(0, 2), (0, 4), (0, 5) (2, 0), (2, 4), (2, 5) (4, 0), (4, 2), (4, 5) (5, 0), (5, 2), (5, 4)
Again, we can eliminate the pairs by applying a range test. Take, for example, a pair (5, 4). This would mean that we had the coefficients 15 * and 64 * (where * is an unknown figure at this point). The product of these two will be maximum with 159 * 649, i.e. 103191 (again ignoring the fact that we donโt even have 9 available): this is too little to achieve the goal, so this pair can be ignored. With further use of the range test, all of these 12 pairs can be discarded, and therefore the search in configuration A stops here: there is no solution.
Then the algorithm goes to configuration B:
2?* X 4?* ------- =125460
Again, the range test is applied to possible pairs for the second position, and, again, it turns out that none of these pairs passes the test: for example, (5, 6) can never represent a larger product than 259 * 469 = 121471 which is (only) too small.
Then the algorithm goes to option C:
2?* X 5?* ------- =125460
Of all 12 possible pairs, only the following tests survive: (4, 0), (4, 1), (6, 0), (6, 1). So, now we have the following second level configurations:
24* 24* 26* 26* X 50* X 51* X 50* X 51* ------- ------- ------- ------- =125460 =125460 =125460 =125460 Ca Cb Cc Cd
There is no pair in the Ca configuration that passes the range test.
In the Cb configuration, the pair (6, 0) passes and leads to the solution:
246 X 510 ------- =125460
At this point, the algorithm stops the search. The result is clear. In general, the number of configurations considered is very small compared with the brute force permutation verification algorithm. Here's a visualization of the search tree:
*-+-- (1, 6) | +-- (2, 4) | +-- (2, 5) -+-- (4, 0) | | | +-- (4, 1) ---- (6, 0) = success: 246 * 510 / / | +-- (6, 0) | | | +-- (6, 1) | +-- (2, 6) ---- (0, 1) ---- (4, 5) = success: 204 * 615
The options below / are intended only to show what else the algorithm would do if a solution were not found. But in this actual case, this part of the search tree was not actually executed.
I donโt have a clear idea of โโtime complexity, but it seems to work well for large numbers, showing that removing digits at an early stage makes the width of the search tree pretty narrow.
Here is a real JavaScript implementation that also runs some test cases when it is activated (and has several other optimizations - see code comments).
/* Function: vampireFangs Arguments: vampire: number to factorise into two fangs, if possible. Return value: Array with two fangs if indeed the argument is a vampire number. Otherwise false (not a vampire number) or null (argument too large to compute) */ function vampireFangs(vampire) { /* Function recurse: for the recursive part of the algorithm. prevA, prevB: partial, potential fangs based on left-most digits of the given number counts: array of ten numbers representing the occurrence of still available digits divider: power of 100, is divided by 100 each next level in the search tree. Determines the number of right-most digits of the given number that are ignored at first in the algorithm. They will be considered in deeper levels of recursion. */ function recurse(vampire, prevA, prevB, counts, divider) { if (divider < 1) { // end of recursion // Product of fangs must equal original number and fangs must not both // end with a 0. return prevA * prevB === vampire && (prevA % 10 + prevB % 10 > 0) ? [prevA, prevB] // Solution found : false; // It not a solution } // Get left-most digits (multiple of 2) of potential vampire number var v = Math.floor(vampire/divider); // Shift decimal digits of partial fangs to the left to make room for // the next digits prevA *= 10; prevB *= 10; // Calculate the min/max A digit that can potentially contribute to a // solution var minDigA = Math.floor(v / (prevB + 10)) - prevA; var maxDigA = prevB ? Math.floor((v + 1) / prevB) - prevA : 9; if (maxDigA > 9) maxDigA = 9; for (var digA = minDigA; digA <= maxDigA; digA++) { if (!counts[digA]) continue; // this digit is not available var fangA = prevA + digA; counts[digA]--; // Calculate the min/max B digit that can potentially contribute to // a solution var minDigB = Math.floor(v / (fangA + 1)) - prevB; var maxDigB = fangA ? (v + 1) / fangA - prevB : 9; // Don't search mirrored AB digits when both fangs are equal until now. if (prevA === prevB && digA > minDigB) minDigB = digA; if (maxDigB > 9) maxDigB = 9; for (var digB = minDigB; digB <= Math.min(maxDigB, 9); digB++) { if (!counts[digB]) continue; // this digit is not available var fangB = prevB + digB; counts[digB]--; // Recurse by considering the next two digits of the potential // vampire number, for finding the next digits to append to // both partial fangs. var result = recurse(vampire, fangA, fangB, counts, divider / 100); // When one solution is found: stop searching & exit search tree. if (result) return result; // solution found // Restore counts counts[digB]++; } counts[digA]++; } } // Validate argument if (typeof vampire !== 'number') return false; if (vampire < 0 || vampire % 1 !== 0) return false; // not positive and integer if (vampire > 9007199254740991) return null; // beyond JavaScript precision var digits = vampire.toString(10).split('').map(Number); // A vampire number has an even number of digits if (!digits.length || digits.length % 2 > 0) return false; // Register per digit (0..9) the frequency of that digit in the argument var counts = [0,0,0,0,0,0,0,0,0,0]; for (var i = 0; i < digits.length; i++) { counts[digits[i]]++; } return recurse(vampire, 0, 0, counts, Math.pow(10, digits.length - 2)); } function Timer() { function now() { // try performance object, else use Date return performance ? performance.now() : new Date().getTime(); } var start = now(); this.spent = function () { return Math.round(now() - start); } } // I/O var button = document.querySelector('button'); var input = document.querySelector('input'); var output = document.querySelector('pre'); button.onclick = function () { var str = input.value; // Convert to number var vampire = parseInt(str); // Measure performance var timer = new Timer(); // Input must be valid number var result = vampire.toString(10) !== str ? null : vampireFangs(vampire); output.textContent = (result ? 'Vampire number. Fangs are: ' + result.join(', ') : result === null ? 'Input is not an integer or too large for JavaScript' : 'Not a vampire number') + '\nTime spent: ' + timer.spent() + 'ms'; } // Tests (numbers taken from wiki page) var tests = [ // Negative test cases: [1, 999, 126000, 1023], // Positive test cases: [1260, 1395, 1435, 1530, 1827, 2187, 6880, 102510, 104260, 105210, 105264, 105750, 108135, 110758, 115672, 116725, 117067, 118440, 120600, 123354, 124483, 125248, 125433, 125460, 125500, 13078260, 16758243290880, 24959017348650] ]; tests.forEach(function (vampires, shouldBeVampire) { vampires.forEach(function (vampire) { var isVampire = vampireFangs(vampire); if (!isVampire !== !shouldBeVampire) { output.textContent = 'Unexpected: vampireFangs(' + vampire + ') returns ' + JSON.stringify(isVampire); throw 'Test failed'; } }); }); output.textContent = 'All tests passed.';
N: <input value="1047527295416280"><button>Vampire Check</button> <pre></pre>
Because JavaScript uses a 64-bit floating-point representation, the above snippet accepts only numbers up to 2 53 -1. Above this limit, there will be a loss of accuracy and therefore unreliable results.
Since Python has no such restriction, I also add a Python implementation to eval.in. This site has a run time limit, so you have to run it elsewhere if this becomes a problem.