Off-by-one on range boundaries
Wrong move: Loop endpoints miss first/last candidate.
Usually fails on: Fails on minimal arrays and exact-boundary answers.
Fix: Re-derive loops from inclusive/exclusive ranges before coding.
Break down a hard problem into reliable checkpoints, edge-case handling, and complexity trade-offs.
Given an equation, represented by words on the left side and the result on the right side.
You need to check if the equation is solvable under the following rules:
words[i] and result are decoded as one number without leading zeros.words) will equal to the number on the right side (result).Return true if the equation is solvable, otherwise return false.
Example 1:
Input: words = ["SEND","MORE"], result = "MONEY" Output: true Explanation: Map 'S'-> 9, 'E'->5, 'N'->6, 'D'->7, 'M'->1, 'O'->0, 'R'->8, 'Y'->'2' Such that: "SEND" + "MORE" = "MONEY" , 9567 + 1085 = 10652
Example 2:
Input: words = ["SIX","SEVEN","SEVEN"], result = "TWENTY" Output: true Explanation: Map 'S'-> 6, 'I'->5, 'X'->0, 'E'->8, 'V'->7, 'N'->2, 'T'->1, 'W'->'3', 'Y'->4 Such that: "SIX" + "SEVEN" + "SEVEN" = "TWENTY" , 650 + 68782 + 68782 = 138214
Example 3:
Input: words = ["LEET","CODE"], result = "POINT" Output: false Explanation: There is no possible mapping to satisfy the equation, so we return false. Note that two different characters cannot map to the same digit.
Constraints:
2 <= words.length <= 51 <= words[i].length, result.length <= 7words[i], result contain only uppercase English letters.10.Problem summary: Given an equation, represented by words on the left side and the result on the right side. You need to check if the equation is solvable under the following rules: Each character is decoded as one digit (0 - 9). No two characters can map to the same digit. Each words[i] and result are decoded as one number without leading zeros. Sum of numbers on the left side (words) will equal to the number on the right side (result). Return true if the equation is solvable, otherwise return false.
Start with the most direct exhaustive search. That gives a correctness anchor before optimizing.
Pattern signal: Array · Math · Backtracking
["SEND","MORE"] "MONEY"
["SIX","SEVEN","SEVEN"] "TWENTY"
["LEET","CODE"] "POINT"
Source-backed implementations are provided below for direct study and interview prep.
// Accepted solution for LeetCode #1307: Verbal Arithmetic Puzzle
class Solution {
private boolean isAnyMapping(List<String> words, int row, int col, int bal,
HashMap<Character, Integer> letToDig, char[] digToLet, int totalRows, int totalCols) {
// If traversed all columns.
if (col == totalCols) {
return bal == 0;
}
// At the end of a particular column.
if (row == totalRows) {
return (bal % 10 == 0
&& isAnyMapping(
words, 0, col + 1, bal / 10, letToDig, digToLet, totalRows, totalCols));
}
String w = words.get(row);
// If the current string 'w' has no character in the ('col')th index.
if (col >= w.length()) {
return isAnyMapping(words, row + 1, col, bal, letToDig, digToLet, totalRows, totalCols);
}
// Take the current character in the variable letter.
char letter = w.charAt(w.length() - 1 - col);
// Create a variable 'sign' to check whether we have to add it or subtract it.
int sign = (row < totalRows - 1) ? 1 : -1;
// If we have a prior valid mapping, then use that mapping.
// The second condition is for the leading zeros.
if (letToDig.containsKey(letter)
&& (letToDig.get(letter) != 0 || (letToDig.get(letter) == 0 && w.length() == 1)
|| col != w.length() - 1)) {
return isAnyMapping(words, row + 1, col, bal + sign * letToDig.get(letter), letToDig,
digToLet, totalRows, totalCols);
} else {
// Choose a new mapping.
for (int i = 0; i < 10; i++) {
// If 'i'th mapping is valid then select it.
if (digToLet[i] == '-'
&& (i != 0 || (i == 0 && w.length() == 1) || col != w.length() - 1)) {
digToLet[i] = letter;
letToDig.put(letter, i);
// Call the function again with the new mapping.
if (isAnyMapping(words, row + 1, col, bal + sign * letToDig.get(letter),
letToDig, digToLet, totalRows, totalCols)) {
return true;
}
// Unselect the mapping.
digToLet[i] = '-';
letToDig.remove(letter);
}
}
}
// If nothing is correct then just return false.
return false;
}
public boolean isSolvable(String[] wordsArr, String result) {
// Add the string 'result' in the list 'words'.
List<String> words = new ArrayList<>();
for (String word : wordsArr) {
words.add(word);
}
words.add(result);
int totalRows = words.size();
// Find the longest string in the list and set 'totalCols' with the size of that string.
int totalCols = 0;
for (String word : words) {
if (totalCols < word.length()) {
totalCols = word.length();
}
}
// Create a HashMap for the letter to digit mapping.
HashMap<Character, Integer> letToDig = new HashMap<>();
// Create a char array for the digit to letter mapping.
char[] digToLet = new char[10];
for (int i = 0; i < 10; i++) {
digToLet[i] = '-';
}
return isAnyMapping(words, 0, 0, 0, letToDig, digToLet, totalRows, totalCols);
}
}
// Accepted solution for LeetCode #1307: Verbal Arithmetic Puzzle
// Auto-generated Go example from java.
func exampleSolution() {
}
// Reference (java):
// // Accepted solution for LeetCode #1307: Verbal Arithmetic Puzzle
// class Solution {
// private boolean isAnyMapping(List<String> words, int row, int col, int bal,
// HashMap<Character, Integer> letToDig, char[] digToLet, int totalRows, int totalCols) {
// // If traversed all columns.
// if (col == totalCols) {
// return bal == 0;
// }
//
// // At the end of a particular column.
// if (row == totalRows) {
// return (bal % 10 == 0
// && isAnyMapping(
// words, 0, col + 1, bal / 10, letToDig, digToLet, totalRows, totalCols));
// }
//
// String w = words.get(row);
//
// // If the current string 'w' has no character in the ('col')th index.
// if (col >= w.length()) {
// return isAnyMapping(words, row + 1, col, bal, letToDig, digToLet, totalRows, totalCols);
// }
//
// // Take the current character in the variable letter.
// char letter = w.charAt(w.length() - 1 - col);
//
// // Create a variable 'sign' to check whether we have to add it or subtract it.
// int sign = (row < totalRows - 1) ? 1 : -1;
//
// // If we have a prior valid mapping, then use that mapping.
// // The second condition is for the leading zeros.
// if (letToDig.containsKey(letter)
// && (letToDig.get(letter) != 0 || (letToDig.get(letter) == 0 && w.length() == 1)
// || col != w.length() - 1)) {
//
// return isAnyMapping(words, row + 1, col, bal + sign * letToDig.get(letter), letToDig,
// digToLet, totalRows, totalCols);
//
// } else {
// // Choose a new mapping.
// for (int i = 0; i < 10; i++) {
// // If 'i'th mapping is valid then select it.
// if (digToLet[i] == '-'
// && (i != 0 || (i == 0 && w.length() == 1) || col != w.length() - 1)) {
// digToLet[i] = letter;
// letToDig.put(letter, i);
//
// // Call the function again with the new mapping.
// if (isAnyMapping(words, row + 1, col, bal + sign * letToDig.get(letter),
// letToDig, digToLet, totalRows, totalCols)) {
// return true;
// }
//
// // Unselect the mapping.
// digToLet[i] = '-';
// letToDig.remove(letter);
// }
// }
// }
//
// // If nothing is correct then just return false.
// return false;
// }
//
// public boolean isSolvable(String[] wordsArr, String result) {
// // Add the string 'result' in the list 'words'.
// List<String> words = new ArrayList<>();
// for (String word : wordsArr) {
// words.add(word);
// }
// words.add(result);
//
// int totalRows = words.size();
//
// // Find the longest string in the list and set 'totalCols' with the size of that string.
// int totalCols = 0;
// for (String word : words) {
// if (totalCols < word.length()) {
// totalCols = word.length();
// }
// }
//
// // Create a HashMap for the letter to digit mapping.
// HashMap<Character, Integer> letToDig = new HashMap<>();
//
// // Create a char array for the digit to letter mapping.
// char[] digToLet = new char[10];
// for (int i = 0; i < 10; i++) {
// digToLet[i] = '-';
// }
//
// return isAnyMapping(words, 0, 0, 0, letToDig, digToLet, totalRows, totalCols);
// }
// }
# Accepted solution for LeetCode #1307: Verbal Arithmetic Puzzle
class Solution:
def isAnyMapping(
self, words, row, col, bal, letToDig, digToLet, totalRows, totalCols
):
# If traversed all columns.
if col == totalCols:
return bal == 0
# At the end of a particular column.
if row == totalRows:
return bal % 10 == 0 and self.isAnyMapping(
words, 0, col + 1, bal // 10, letToDig, digToLet, totalRows, totalCols
)
w = words[row]
# If the current string 'w' has no character in the ('col')th index.
if col >= len(w):
return self.isAnyMapping(
words, row + 1, col, bal, letToDig, digToLet, totalRows, totalCols
)
# Take the current character in the variable letter.
letter = w[len(w) - 1 - col]
# Create a variable 'sign' to check whether we have to add it or subtract it.
if row < totalRows - 1:
sign = 1
else:
sign = -1
# If we have a prior valid mapping, then use that mapping.
# The second condition is for the leading zeros.
if letter in letToDig and (
letToDig[letter] != 0
or (letToDig[letter] == 0 and len(w) == 1)
or col != len(w) - 1
):
return self.isAnyMapping(
words,
row + 1,
col,
bal + sign * letToDig[letter],
letToDig,
digToLet,
totalRows,
totalCols,
)
# Choose a new mapping.
else:
for i in range(10):
# If 'i'th mapping is valid then select it.
if digToLet[i] == "-" and (
i != 0 or (i == 0 and len(w) == 1) or col != len(w) - 1
):
digToLet[i] = letter
letToDig[letter] = i
# Call the function again with the new mapping.
if self.isAnyMapping(
words,
row + 1,
col,
bal + sign * letToDig[letter],
letToDig,
digToLet,
totalRows,
totalCols,
):
return True
# Unselect the mapping.
digToLet[i] = "-"
if letter in letToDig:
del letToDig[letter]
# If nothing is correct then just return false.
return False
def isSolvable(self, words, result):
# Add the string 'result' in the list 'words'.
words.append(result)
# Initialize 'totalRows' with the size of the list.
totalRows = len(words)
# Find the longest string in the list and set 'totalCols' with the size of that string.
totalCols = max(len(word) for word in words)
# Create a HashMap for the letter to digit mapping.
letToDig = {}
# Create a list for the digit to letter mapping.
digToLet = ["-"] * 10
return self.isAnyMapping(
words, 0, 0, 0, letToDig, digToLet, totalRows, totalCols
)
// Accepted solution for LeetCode #1307: Verbal Arithmetic Puzzle
/**
* [1307] Verbal Arithmetic Puzzle
*
* Given an equation, represented by words on the left side and the result on the right side.
* You need to check if the equation is solvable under the following rules:
*
* Each character is decoded as one digit (0 - 9).
* No two characters can map to the same digit.
* Each words[i] and result are decoded as one number without leading zeros.
* Sum of numbers on the left side (words) will equal to the number on the right side (result).
*
* Return true if the equation is solvable, otherwise return false.
*
* Example 1:
*
* Input: words = ["SEND","MORE"], result = "MONEY"
* Output: true
* Explanation: Map 'S'-> 9, 'E'->5, 'N'->6, 'D'->7, 'M'->1, 'O'->0, 'R'->8, 'Y'->'2'
* Such that: "SEND" + "MORE" = "MONEY" , 9567 + 1085 = 10652
* Example 2:
*
* Input: words = ["SIX","SEVEN","SEVEN"], result = "TWENTY"
* Output: true
* Explanation: Map 'S'-> 6, 'I'->5, 'X'->0, 'E'->8, 'V'->7, 'N'->2, 'T'->1, 'W'->'3', 'Y'->4
* Such that: "SIX" + "SEVEN" + "SEVEN" = "TWENTY" , 650 + 68782 + 68782 = 138214
* Example 3:
*
* Input: words = ["LEET","CODE"], result = "POINT"
* Output: false
* Explanation: There is no possible mapping to satisfy the equation, so we return false.
* Note that two different characters cannot map to the same digit.
*
*
* Constraints:
*
* 2 <= words.length <= 5
* 1 <= words[i].length, result.length <= 7
* words[i], result contain only uppercase English letters.
* The number of different characters used in the expression is at most 10.
*
*/
pub struct Solution {}
// problem: https://leetcode.com/problems/verbal-arithmetic-puzzle/
// discuss: https://leetcode.com/problems/verbal-arithmetic-puzzle/discuss/?currentPage=1&orderBy=most_votes&query=
// submission codes start here
use std::iter::FromIterator;
impl Solution {
// Credit: https://leetcode.com/problems/verbal-arithmetic-puzzle/solutions/2088909/faster-than-100-of-rust-solutions-best-explanation/
pub fn is_solvable(words: Vec<String>, result: String) -> bool {
let mut equation: Vec<Vec<char>> = Vec::new();
for w in words {
if w.len() > result.len() {
return false;
}
equation.push(w.chars().rev().collect());
}
let solution = &mut std::collections::HashMap::new();
let ans: Vec<char> = result.chars().rev().collect();
if Self::can_solve(&equation, &ans, 0, 0, 0, solution) {
true
} else {
false
}
}
fn can_solve(
equation: &[Vec<char>],
result: &[char],
row: usize,
col: usize,
carry: u32,
solution: &mut std::collections::HashMap<char, u8>,
) -> bool {
let addend = row < equation.len();
if addend && col >= equation[row].len() {
// No leading zero for multicharacter words
let word = &equation[row];
if solution[&word[word.len() - 1]] == 0 && word.len() > 1 {
return false;
}
return Self::can_solve(equation, result, row + 1, col, carry, solution);
}
if col == result.len() && !addend {
return carry == 0 && (col == 1 || solution[&result[col - 1]] > 0);
}
let digit = if addend {
equation[row][col]
} else {
result[col]
};
let assigned = solution.contains_key(&digit);
if addend {
if assigned {
Self::can_solve(
equation,
result,
row + 1,
col,
carry + (solution[&digit] as u32),
solution,
)
} else {
let used: std::collections::HashSet<&u8> =
std::collections::HashSet::from_iter(solution.values());
let unused: Vec<u8> = (0..=9).filter(|x| !used.contains(x)).collect();
for i in unused {
solution.insert(digit, i);
if Self::can_solve(equation, result, row + 1, col, carry + (i as u32), solution)
{
return true;
}
solution.remove(&digit);
}
false
}
} else {
let sum_digit = (carry % 10) as u8;
if assigned {
(solution[&digit] == sum_digit)
&& Self::can_solve(equation, result, 0, col + 1, carry / 10, solution)
} else {
let used = solution.values().any(|&x| x == sum_digit);
if used {
return false;
}
solution.insert(digit, sum_digit);
if Self::can_solve(equation, result, 0, col + 1, carry / 10, solution) {
return true;
}
solution.remove(&digit);
false
}
}
}
}
// submission codes end
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_1307_example_1() {
let words = vec_string!["SEND", "MORE"];
let result = "MONEY".to_string();
let res = true;
assert_eq!(Solution::is_solvable(words, result), res);
}
#[test]
fn test_1307_example_2() {
let words = vec_string!["SIX", "SEVEN", "SEVEN"];
let result = "TWENTY".to_string();
let res = true;
assert_eq!(Solution::is_solvable(words, result), res);
}
#[test]
fn test_1307_example_3() {
let words = vec_string!["LEET", "CODE"];
let result = "POINT".to_string();
let res = false;
assert_eq!(Solution::is_solvable(words, result), res);
}
}
// Accepted solution for LeetCode #1307: Verbal Arithmetic Puzzle
// Auto-generated TypeScript example from java.
function exampleSolution(): void {
}
// Reference (java):
// // Accepted solution for LeetCode #1307: Verbal Arithmetic Puzzle
// class Solution {
// private boolean isAnyMapping(List<String> words, int row, int col, int bal,
// HashMap<Character, Integer> letToDig, char[] digToLet, int totalRows, int totalCols) {
// // If traversed all columns.
// if (col == totalCols) {
// return bal == 0;
// }
//
// // At the end of a particular column.
// if (row == totalRows) {
// return (bal % 10 == 0
// && isAnyMapping(
// words, 0, col + 1, bal / 10, letToDig, digToLet, totalRows, totalCols));
// }
//
// String w = words.get(row);
//
// // If the current string 'w' has no character in the ('col')th index.
// if (col >= w.length()) {
// return isAnyMapping(words, row + 1, col, bal, letToDig, digToLet, totalRows, totalCols);
// }
//
// // Take the current character in the variable letter.
// char letter = w.charAt(w.length() - 1 - col);
//
// // Create a variable 'sign' to check whether we have to add it or subtract it.
// int sign = (row < totalRows - 1) ? 1 : -1;
//
// // If we have a prior valid mapping, then use that mapping.
// // The second condition is for the leading zeros.
// if (letToDig.containsKey(letter)
// && (letToDig.get(letter) != 0 || (letToDig.get(letter) == 0 && w.length() == 1)
// || col != w.length() - 1)) {
//
// return isAnyMapping(words, row + 1, col, bal + sign * letToDig.get(letter), letToDig,
// digToLet, totalRows, totalCols);
//
// } else {
// // Choose a new mapping.
// for (int i = 0; i < 10; i++) {
// // If 'i'th mapping is valid then select it.
// if (digToLet[i] == '-'
// && (i != 0 || (i == 0 && w.length() == 1) || col != w.length() - 1)) {
// digToLet[i] = letter;
// letToDig.put(letter, i);
//
// // Call the function again with the new mapping.
// if (isAnyMapping(words, row + 1, col, bal + sign * letToDig.get(letter),
// letToDig, digToLet, totalRows, totalCols)) {
// return true;
// }
//
// // Unselect the mapping.
// digToLet[i] = '-';
// letToDig.remove(letter);
// }
// }
// }
//
// // If nothing is correct then just return false.
// return false;
// }
//
// public boolean isSolvable(String[] wordsArr, String result) {
// // Add the string 'result' in the list 'words'.
// List<String> words = new ArrayList<>();
// for (String word : wordsArr) {
// words.add(word);
// }
// words.add(result);
//
// int totalRows = words.size();
//
// // Find the longest string in the list and set 'totalCols' with the size of that string.
// int totalCols = 0;
// for (String word : words) {
// if (totalCols < word.length()) {
// totalCols = word.length();
// }
// }
//
// // Create a HashMap for the letter to digit mapping.
// HashMap<Character, Integer> letToDig = new HashMap<>();
//
// // Create a char array for the digit to letter mapping.
// char[] digToLet = new char[10];
// for (int i = 0; i < 10; i++) {
// digToLet[i] = '-';
// }
//
// return isAnyMapping(words, 0, 0, 0, letToDig, digToLet, totalRows, totalCols);
// }
// }
Use this to step through a reusable interview workflow for this problem.
Generate every possible combination without any filtering. At each of n positions we choose from up to n options, giving nⁿ total candidates. Each candidate takes O(n) to validate. No pruning means we waste time on clearly invalid partial solutions.
Backtracking explores a decision tree, but prunes branches that violate constraints early. Worst case is still factorial or exponential, but pruning dramatically reduces the constant factor in practice. Space is the recursion depth (usually O(n) for n-level decisions).
Review these before coding to avoid predictable interview regressions.
Wrong move: Loop endpoints miss first/last candidate.
Usually fails on: Fails on minimal arrays and exact-boundary answers.
Fix: Re-derive loops from inclusive/exclusive ranges before coding.
Wrong move: Temporary multiplications exceed integer bounds.
Usually fails on: Large inputs wrap around unexpectedly.
Fix: Use wider types, modular arithmetic, or rearranged operations.
Wrong move: Mutable state leaks between branches.
Usually fails on: Later branches inherit selections from earlier branches.
Fix: Always revert state changes immediately after recursive call.