Verbal Arithmetic Puzzle

Solution For Verbal Arithmetic Puzzle

At the time of writing this answer, there are 4 Verbal Arithmetic Puzzle problems on LeetCode:

  1. Word Ladder II (https://leetcode.com/problems/word-ladder-ii/)
  2. Verbal Arithmetic Puzzle (https://leetcode.com/problems/verbal-arithmetic-puzzle/)
  3. Find Words That Can Be Formed by Characters (https://leetcode.com/problems/find-words-that-can-be-formed-by-characters/)
  4. Word Search II (https://leetcode.com/problems/word-search-ii/)

In this answer, we will focus on the Verbal Arithmetic Puzzle problem.

Problem Statement

Given an equation like “SEND + MORE = MONEY”, where each letter represents a unique digit from 0 to 9, and the equation is guaranteed to have a valid solution, find the digits that each letter represents.

Example:

Input: “SEND + MORE = MONEY”
Output:
{
“S”: 9,
“E”: 5,
“N”: 6,
“D”: 7,
“M”: 1,
“O”: 0,
“R”: 8,
“Y”: 2
}

Solution Approach

The problem can be solved using a recursive backtracking algorithm. We start by creating a mapping of each letter to a digit, and then try to solve the equation.

For example, if we have the equation “SEND + MORE = MONEY”, we can create a mapping like {S: , E: , N: , D: , M: , O: , R: , Y: }.

Next, we try to fill in the numbers for each letter, one by one. We start with the first letter, and try all possible digits from 0 to 9. For each digit, we check if the equation is valid so far. If it is, we recursively try to fill in the next letter. If we reach the end of the equation and all digits have been filled, we have found a solution.

If at any point, we find that the equation is not valid, we backtrack to the previous letter and try a different digit. This is the recursive backtracking algorithm.

One important thing to note is that we have to keep track of the carries while adding two numbers. For example, consider the case where S=1 and E=2 in the equation “SEND + MORE = MONEY”. When we add S+E, we get a carry of 1. So, when we add N+R+1, we have to account for this carry. Similarly, when we add E+O, we have to check if there is a carry from the previous addition.

Code Implementation

The following is a Python implementation of the recursive backtracking algorithm:

“`
class Solution:
def is_valid(self, mapping, s1, s2, s3):
n1 = self.get_number(mapping, s1)
n2 = self.get_number(mapping, s2)
n3 = self.get_number(mapping, s3)
return n1 + n2 == n3

def get_number(self, mapping, s):
    res = 0
    for c in s:
        res = res * 10 + mapping[c]
    return res

def solve(self, idx, carry, mapping, s1, s2, s3):
    if idx == len(s3):
        return carry == 0
    c1 = s1[-1 - idx] if idx < len(s1) else ''
    c2 = s2[-1 - idx] if idx < len(s2) else ''
    c3 = s3[-1 - idx]
    if c3 in mapping:
        if mapping[c3] != carry:
            return False
        return self.solve(idx+1, 0, mapping, s1, s2, s3)
    for d in range(10):
        if str(d) in mapping.values():
            continue
        if d == 0 and (c1 == '' or c2 == '' or c3 == ''):
            continue
        n1 = mapping.get(c1, 0)
        n2 = mapping.get(c2, 0)
        s = n1 + n2 + carry
        if s % 10 != d:
            continue
        mapping[c3] = d
        if self.solve(idx+1, s//10, mapping, s1, s2, s3):
            return True
        del mapping[c3]
    return False

def solvePuzzle(self, words: List[str], result: str) -> Dict[str, int]:
    mapping = {}
    s1, s2, s3 = words[0], words[1], result
    if len(s3) > len(s1) + len(s2):
        return {}
    s1, s2 = s1.zfill(len(s3)), s2.zfill(len(s3))
    if self.solve(0, 0, mapping, s1, s2, s3):
        return mapping
    return {}

“`

The is_valid() function checks if the equation is valid so far. The get_number() function converts a string to the corresponding integer based on the current mapping.

The solve() function is the recursive backtracking algorithm. It takes as input the current index (i.e., the position of the letter/digit that we are trying to fill), the current carry, the current mapping, and the three strings s1, s2, s3. If we have filled all letters, and the equation is valid, we return True. Otherwise, we try to fill the current letter using all possible digits. If we find a valid mapping, we recursively try to fill the next letter. If we reach the end of the equation and all digits have been filled, we have found a solution.

The solvePuzzle() function takes as input the two lists of strings representing the two words and the result, and returns a dictionary mapping each letter to the corresponding digit. It first creates the initial mapping, and then calls the solve() function to find the solution.

Complexity Analysis

The worst-case time complexity of the algorithm is O(10^n), where n is the number of unique letters in the equation. This is because we have to try all possible digits for each letter. However, in practice, the algorithm is much faster because we can prune the search tree by checking if the equation is valid at each point.

The worst-case space complexity of the algorithm is also O(n), where n is the number of unique letters in the equation. This is because we have to store the mapping for each letter. However, in practice, the space complexity is much lower because we can reuse the same mapping for each recursive call.

Conclusion

The Verbal Arithmetic Puzzle problem can be solved using a recursive backtracking algorithm that tries all possible combinations of digits for each letter. The key idea is to use the is_valid() function to check if the equation is valid at each point, and backtrack whenever we encounter an invalid state. The algorithm has a worst-case time complexity of O(10^n) and a worst-case space complexity of O(n), where n is the number of unique letters in the equation.

Step by Step Implementation For Verbal Arithmetic Puzzle

This problem can be solved using a recursive backtracking algorithm.

We start by assigning each letter a digit from 0 to 9. Then, we check if the resulting equation is valid. If it is, we return the solution. If not, we backtrack and try a different assignment for the letter.

Here is the pseudocode for the algorithm:

def solve(letters, equation):
  # base case: all letters have been assigned
  if len(letters) == 0:
    # check if equation is valid
    if check(equation):
      return equation
    else:
      return None

  # recursive case: try all possible assignments for the first letter
  for i in range(10):
    # assign the letter to the digit i
    letters[0] = i
    # solve the subproblem with the remaining letters
    solution = solve(letters[1:], equation)
    # if a solution was found, return it
    if solution != None:
      return solution

  # no solution was found
  return None

def check(equation):
  # TODO: check if equation is valid
  return True
def isSolvable(self, words: List[str], result: str) -> bool:
        def backtrack(i,carry,used):
            if i == len(words[0]):
                return carry == 0
            for d in range(10):
                if used[d]:
                    continue
                used[d] = True
                if backtrack(i+1,carry+d*sum(w[i]-'A'+1 for w in words),used):
                    return True
                used[d] = False
        used = [False]*10
        return backtrack(0,0,used)
// Given an equation like:
//   SEND
// + MORE
// --------
//  MONEY

// We want to determine what digits go in each slot such that the equation is correct. 
// In this case, we would return:

// {
//   S: 9,
//   E: 5,
//   N: 6,
//   D: 7,
//   M: 1,
//   O: 0,
//   R: 8,
//   Y: 2
// }

// This is a backtracking problem. We can start by assigning the first letter 
// (in this case, "S") a digit, then recursively checking if the remaining letters 
// can be assigned valid digits such that the equation remains correct. 
// If at any point we find that there are no valid digits remaining for a letter, 
// we backtrack and try a different digit for the previous letter.

function verbalArithmeticPuzzle(equation) {
  let letters = {};
  let firstLetters = [];

  // We first need to get all the unique letters in the equation
  // and keep track of which letters are the first letters in each word.
  for (let i = 0; i < equation.length; i++) {
    for (let j = 0; j < equation[i].length; j++) {
      const letter = equation[i][j];
      if (!letters[letter]) {
        letters[letter] = true;
        if (j === 0) firstLetters.push(letter);
      }
    }
  }

  // We'll use this array to keep track of which digits have been used.
  // For example, if we've used the digit "9" for the letter "S", then 
  // digitUsed[9] will be true.
  const digitUsed = new Array(10).fill(false);

  // We can start by assigning the first letter of the first word a digit, 
  // then recursively checking if the remaining letters can be assigned valid digits.
  for (let d = 0; d <= 9; d++) {
    // If this digit has already been used, we can skip it.
    if (digitUsed[d]) continue;

    // We'll keep track of which letters have been assigned digits in this object.
    // For example, if we've assigned the digit "9" for the letter "S", then 
    // assigned[S] will be 9.
    const assigned = {};
    assigned[firstLetters[0]] = d;

    // We can mark this digit as used since we'll be assigning it to the first letter.
    digitUsed[d] = true;

    // If all the letters have been assigned digits and the equation is still correct, 
    // then we have found a valid solution and can return it.
    if (solve(1, assigned, digitUsed, equation)) {
      return assigned;
    }

    // If we reach this point, it means that the equation was not correct 
    // even after assigning the digit "d" to the first letter, so we'll try another digit.
    // We'll also need to unmark this digit as used since we didn't actually use it.
    digitUsed[d] = false;
  }

  // If we reach this point, it means we couldn't find a valid solution.
  return null;
}

// This is the recursive function that checks if the remaining letters can be 
// assigned digits such that the equation remains correct.
// 
// "index" represents which letter we are currently assigning a digit to.
// "assigned" is an object that keeps track of which letters have been assigned digits.
// "digitUsed" is an array that keeps track of which digits have been used.
function solve(index, assigned, digitUsed, equation) {
  // If we have assigned digits to all the letters, we need to check if the equation is still correct.
  if (index === equation.length) {
    return isValid(assigned, equation);
  }

  // We'll keep track of which letters are the first letters in each word.
  const firstLetters = [];
  for (let i = 0; i < equation.length; i++) {
    firstLetters.push(equation[i][0]);
  }

  // We can start by assigning the first letter of the current word a digit, 
  // then recursively checking if the remaining letters can be assigned valid digits.
  for (let d = 0; d <= 9; d++) {
    // If this digit has already been used, we can skip it.
    if (digitUsed[d]) continue;

    // We'll keep track of which letters have been assigned digits in this object.
    // For example, if we've assigned the digit "9" for the letter "S", then 
    // assigned[S] will be 9.
    const newAssigned = { ...assigned };
    newAssigned[firstLetters[index]] = d;

    // We can mark this digit as used since we'll be assigning it to the current letter.
    const newDigitUsed = digitUsed.slice();
    newDigitUsed[d] = true;

    // If all the letters have been assigned digits and the equation is still correct, 
    // then we have found a valid solution and can return it.
    if (solve(index + 1, newAssigned, newDigitUsed, equation)) {
      return newAssigned;
    }
  }

  // If we reach this point, it means that the equation was not correct 
  // even after trying all possible digits for the current letter, so we'll backtrack.
  return false;
}

// This function checks if the equation is still correct after assigning all the letters digits.
function isValid(assigned, equation) {
  // We'll keep track of the sum of each word in the equation.
  // For example, if we have assigned the digits {S: 9, E: 5, N: 6, D: 7} to the letters 
  // in the word "SEND", then the sum of that word would be 956.
  const sum = [];

  // We need to loop through each word in the equation and calculate the sum of the digits.
  for (let i = 0; i < equation.length; i++) {
    sum.push(0);
    for (let j = 0; j < equation[i].length; j++) {
      const letter = equation[i][j];
      sum[i] = sum[i] * 10 + assigned[letter];
    }
  }

  // We can check if the equation is correct by comparing the sums of each word.
  // For example, if we have the equation "SEND + MORE = MONEY", then the sums of each word would be:
  // 
  // sum[0] = SEND  => 956
  // sum[1] = MORE  => 567
  // sum[2] = MONEY => 1023
  // 
  // We can check if the equation is correct by comparing the first sum to the sum of the remaining sums.
  // In this case, we would have 956 === 567 + 1023, which is correct.
  for (let i = 1; i < sum.length; i++) {
    sum[0] -= sum[i];
  }
  return sum[0] === 0;
}
The problem is as follows:

Given an equation with numbers and words, determine if the equation is solvable and if so, what the solution is.

The equation will be in the form "NUM1 + NUM2 = WORDSUM". Words can be added together to form new words (e.g. "TWO" + "TWO" = "FOUR"), and each letter has a value associated with it:

A = 1
B = 2
...
Z = 26

For example, given the equation "TWO + TWO = FOUR", we can see that this is solvable because "FOUR" is the sum of the values of the letters in "TWO" and "TWO". The solution to this equation would be "A = 1, B = 2".

Given the equation "SEND + MORE = MONEY", we can see that this is also solvable:

S = 19
E = 5
N = 14
D = 4
M = 13
O = 15
R = 18
Y = 25

The solution to this equation would be "A = 9, B = 5, C = 6, D = 7, E = 8".

However, given the equation "THIS + IS + IRREAL = REALITY", we can see that this is not solvable because there is no combination of values that would sum to the value of "REALITY".
using System; 
using System.Collections.Generic; 

public class Solution { 

// This is a verbal arithmetic puzzle solver. 

// Given a set of words and a sum, 
// it will find a unique assignment of digits to the letters 
// such that the sum is correct and all the words are valid. 

public void SolvePuzzle(string[] words, string sum) { 

// A map from letters to the possible digits they could represent 
Dictionary> letterMap = new Dictionary>(); 

// A map from letters to the number of times they appear in the sum 
Dictionary sumLetterCounts = new Dictionary(); 

// A map from letters to the number of times they appear in the words 
Dictionary wordLetterCounts = new Dictionary(); 

// Add all the letters in the sum to the maps 
foreach (char c in sum) { 
if (!letterMap.ContainsKey(c)) { 
letterMap.Add(c, new HashSet()); 
} 

if (!sumLetterCounts.ContainsKey(c)) { 
sumLetterCounts.Add(c, 0); 
} 

sumLetterCounts[c]++; 
} 

// Add all the letters in the words to the maps 
foreach (string word in words) { 
foreach (char c in word) { 
if (!letterMap.ContainsKey(c)) { 
letterMap.Add(c, new HashSet()); 
} 

if (!wordLetterCounts.ContainsKey(c)) { 
wordLetterCounts.Add(c, 0); 
} 

wordLetterCounts[c]++; 
} 
} 

// We will use a depth-first search to find a solution 
// We need to keep track of the letters we have assigned so far 
// as well as thecarry value for each position in the sum 
Dictionary assignment = new Dictionary(); 
int[] carry = new int[sum.Length]; 

if (SolvePuzzle(letterMap, sumLetterCounts, wordLetterCounts, assignment, carry, 0)) { 
Console.WriteLine("Solution found:"); 

// Print out the assignment 
foreach (KeyValuePair kvp in assignment) { 
Console.WriteLine(kvp.Key + "=" + kvp.Value); 
} 
} else { 
Console.WriteLine("No solution found"); 
} 
} 

// This is the recursive function for the depth-first search 
private bool SolvePuzzle(Dictionary> letterMap, 
Dictionary sumLetterCounts, 
Dictionary wordLetterCounts, 
Dictionary assignment, int[] carry, int position) { 

// If we have assigned all the letters, then check if the sum is correct 
if (position == sum.Length) { 
// Check if the carry values are all 0 
foreach (int c in carry) { 
if (c != 0) { 
return false; 
} 
} 

// Check if the words are all valid 
foreach (string word in words) { 
int wordValue = 0; 

foreach (char c in word) { 
wordValue *= 10; 
wordValue += assignment[c]; 
} 

if (wordValue == 0) { 
return false; 
} 
} 

return true; 
} 

// This function assigns a digit to a letter 
// and then recurses to the next letter 
private bool AssignDigit(Dictionary> letterMap, 
Dictionary sumLetterCounts, 
Dictionary wordLetterCounts, 
Dictionary assignment, int[] carry, int position, char letter) { 

// We only want to try digits that are not already in use 
HashSet usedDigits = new HashSet(); 

// Add all the digits that have already been assigned to other letters 
foreach (KeyValuePair kvp in assignment) { 
usedDigits.Add(kvp.Value); 
} 

// We also need to make sure that the digit we choose 
// does not violate the constraints imposed by the sum 
// For example, if the sum is "SEND+MORE=MONEY" 
// and we have already assigned "S=9" and "E=5", 
// then we know that "N+R=Y" so the only possible digits for "N" and "R" are {0,2,4,6,8}. 
if (position < sum.Length - 1) { 
int sumValue = 0; 

foreach (char c in assignment.Keys) { 
sumValue *= 10; 
sumValue += assignment[c]; 
} 

int targetValue = (carry[position] + sumValue) % 10; 

// If the letter appears in the sum, 
// then we need to make sure the digit we choose 
// is consistent with the target value 
if (sumLetterCounts[letter] > 0) { 
// If the target value is 0 and the letter appears in the sum, 
// then the letter must be the first letter in the sum. 
// This is because if the letter appears later in the sum, 
// then its contribution would be multiplied by 10 
// and the target value would not be 0. 
if (targetValue == 0 && position > 0) { 
return false; 
} 

// We only want to try digits that are less than the target value 
for (int d = 0; d < targetValue; d++) { 
if (!usedDigits.Contains(d)) { 
letterMap[letter].Add(d); 
} 
} 

// If the target value is not 0, 
// then we also want to try the digit that is one greater than the target value 
// (e.g. if the target value is 5, then we want to try the digit 6 as well) 
if (targetValue < 9 && !usedDigits.Contains(targetValue + 1)) { 
letterMap[letter].Add(targetValue + 1); 
} 
} 

// If the letter does not appear in the sum, 
// then we only want to try digits that are less than 10 
else { 
for (int d = 0; d < 10; d++) { 
if (!usedDigits.Contains(d)) { 
letterMap[letter].Add(d); 
} 
} 
} 

// Recurse through all the possible digits for the letter 
foreach (char d in letterMap[letter]) { 
// Assign the digit to the letter 
assignment[letter] = d; 

// If the letter appears in the sum, 
// then we need to update the carry values 
if (sumLetterCounts[letter] > 0) { 
int sumValue = 0; 

foreach (char c in assignment.Keys) { 
sumValue *= 10; 
sumValue += assignment[c]; 
} 

int targetValue = (carry[position] + sumValue) % 10; 

if (d == targetValue) { 
// If the digit is the same as the target value, 
// then we don't need to carry over any value 
if (SolvePuzzle(letterMap, sumLetterCounts, wordLetterCounts, assignment, carry, position + 1)) { 
return true; 
} 
} else if (d == targetValue + 1) { 
// If the digit is one greater than the target value, 
// then we need to carry over a value of 1 
carry[position + 1] = 1; 

if (SolvePuzzle(letterMap, sumLetterCounts, wordLetterCounts, assignment, carry, position + 1)) { 
return true; 
} 

carry[position + 1] = 0; 
} 
} 

// If the letter does not appear in the sum, 
// then we don't need to update the carry values 
else { 
if (SolvePuzzle(letterMap, sumLetterCounts, wordLetterCounts, assignment, carry, position + 1)) { 
return true; 
} 
} 

// Remove the digit from the letter's possible values 
letterMap[letter].Remove(d); 
} 

return false; 
} 
}


Top 100 Leetcode Practice Problems In Java

Get 30% Off Instantly!
[gravityforms id="5" description="false" titla="false" ajax="true"]