|
4 | 4 | * @return {boolean} |
5 | 5 | */ |
6 | 6 | const isSolvable = function (words, result) { |
7 | | - const firstChars = new Set() |
8 | | - |
9 | | - // this will hold the key as the character and multiple as the value |
10 | | - const map = {} |
11 | | - for (let i = 0; i < result.length; i++) { |
12 | | - const char = result[i] |
13 | | - if (!i) firstChars.add(char) |
14 | | - if (!map.hasOwnProperty(char)) map[char] = 0 |
15 | | - map[char] -= 10 ** (result.length - i - 1) |
16 | | - } |
17 | | - for (let j = 0; j < words.length; j++) { |
18 | | - const word = words[j] |
19 | | - for (let i = 0; i < word.length; i++) { |
20 | | - const char = word[i] |
21 | | - if (!i) firstChars.add(char) |
22 | | - if (!map.hasOwnProperty(char)) map[char] = 0 |
23 | | - map[char] += 10 ** (word.length - i - 1) |
| 7 | + const _isSolvable = (wordIndex, charIndex, wordsSum, resultSum, num) => { |
| 8 | + if (wordIndex >= words.length) { |
| 9 | + return wordsSum === resultSum |
24 | 10 | } |
25 | | - } |
26 | | - |
27 | | - const positives = [] |
28 | | - const negatives = [] |
29 | | - Object.entries(map).forEach((entry) => { |
30 | | - if (entry[1] < 0) negatives.push(entry) |
31 | | - else positives.push(entry) |
32 | | - }) |
33 | | - |
34 | | - const numsUsed = new Set() |
35 | | - const backtrack = (val = 0) => { |
36 | | - // if we have used all the characters and the value is 0 the input is solvable |
37 | | - if (!positives.length && !negatives.length) return val === 0 |
38 | | - |
39 | | - // get the store that we are going to examine depending on the value |
40 | | - const store = |
41 | | - val > 0 || (val === 0 && negatives.length) ? negatives : positives |
42 | | - if (store.length === 0) return false |
43 | | - const entry = store.pop() |
44 | | - const [char, multiple] = entry |
45 | | - |
46 | | - // try every possible value watching out for the edge case that it was a first character |
47 | | - for (let i = firstChars.has(char) ? 1 : 0; i < 10; i++) { |
48 | | - if (numsUsed.has(i)) continue |
49 | | - numsUsed.add(i) |
50 | | - if (backtrack(i * multiple + val)) return true |
51 | | - numsUsed.delete(i) |
| 11 | + const wordLen = words[wordIndex].length |
| 12 | + if (charIndex >= wordLen) { |
| 13 | + if (wordIndex === words.length - 1) { |
| 14 | + return _isSolvable(wordIndex + 1, 0, wordsSum, num, 0) |
| 15 | + } |
| 16 | + return _isSolvable(wordIndex + 1, 0, wordsSum + num, resultSum, 0) |
| 17 | + } |
| 18 | + const char = words[wordIndex][charIndex] |
| 19 | + if (map.get(char) !== undefined) { |
| 20 | + if (map.get(char) === 0 && num === 0 && charIndex >= 1) { |
| 21 | + return false |
| 22 | + } |
| 23 | + return _isSolvable( |
| 24 | + wordIndex, |
| 25 | + charIndex + 1, |
| 26 | + wordsSum, |
| 27 | + resultSum, |
| 28 | + num * 10 + map.get(char) |
| 29 | + ) |
| 30 | + } |
| 31 | + for (let digit = 0; digit <= 9; digit++) { |
| 32 | + if (digit === 0 && num === 0 && wordLen > 1) continue |
| 33 | + if (map.get(digit) !== undefined) continue |
| 34 | + map.set(digit, char) |
| 35 | + map.set(char, digit) |
| 36 | + if ( |
| 37 | + _isSolvable( |
| 38 | + wordIndex, |
| 39 | + charIndex + 1, |
| 40 | + wordsSum, |
| 41 | + resultSum, |
| 42 | + num * 10 + digit |
| 43 | + ) |
| 44 | + ) { |
| 45 | + return true |
| 46 | + } |
| 47 | + map.set(digit, undefined) |
| 48 | + map.set(char, undefined) |
52 | 49 | } |
53 | | - store.push(entry) |
54 | 50 | return false |
55 | 51 | } |
56 | | - return backtrack() |
| 52 | + const map = new Map() |
| 53 | + words = [...words, result] |
| 54 | + return _isSolvable(0, 0, 0, 0, 0) |
57 | 55 | } |
0 commit comments