[Daily Coding Problem 250] Cryptarithmetic Puzzle

本文介绍了一种解决算术谜题的方法,该谜题中字母代表数字,通过递归深度优先搜索和回溯策略找到合法映射,实现从三个单词的加法问题到数字的有效转换。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

A cryptarithmetic puzzle is a mathematical game where the digits of some numbers are represented by letters. Each letter represents a unique digit.

For example, a puzzle of the form:

  SEND
+ MORE
--------
 MONEY

may have the solution:

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

Given a three-word puzzle like the one above, create an algorithm that finds a solution.

 

For simplicity, let's assume all letters are upper-case English letters. A naive way of solving this would be to check every numerical value from 0 to 9 for each letter that appears in the 3 words. Return the first letter-to-number mapping that works. Assuming it takes O(N) to validate a mapping, where N is the number of digits in the sum, this would take O(N * 10!). 

 

This naive solution will check a lot of invalid mappings. To reduce the search space, we can use backtracking to cut down on the number of possible mappings to check. For backtracking to be effective, we should be able to construct a partial solution, verify if that partial solution is invalid and check if a mapping is complete. 

 

1. First check if word3 has more letters than both word1 and word2. If this is the case, we can safely assign 1 to the most significant letter in word3. 

2. Start dfs from the least significant digit and do the following:

  a. at any given column of digit i, if word1[i] is not mapped, assign an unused number. 

  b. dfs on word2[i].

  c. at this point, we've already assigned number to both word1[i] and word2[i]. There will be the following cases.

  c1. if word3[i] has a mapping and it satisfies the sum at this column, advance i to the next higher column and update carry if any, then dfs.

  c2. word3[i] has a mapping and it does not satisfy the sum. return false.

  c3. word3[i] does not have a mapping but the right number that satisfy the sum is already taken by another letter, return false.

  c4. word3[i] does not have a mapping and the right number that satisfy the sum is not taken by another letter, assign this number to word3[i], advance i to the next higher column and update carry if any, then dfs.

 

The terminating case is that i < 0. It means we've checked all columns with a right mapping. return true in this case.

For each dfs recursive call, we need to check if it returns true or false. If true, we just found a correct mapping and the starting dfs will return true. If false and we've assigned a number to a letter, remember to backtrack so the letter is not mapped and the number is marked as unused. Essentially each dfs assigns an unused number to an unmapped letter if needed, then verify if the current column adds up.

 

Runtime analysis: There are 10 total possible numbers so the runtime is O(10!) in the worst case. The recursion stack is also bounded by a depth of 10 as its depth only increases when we need to assign an unused number to a letter.

 

public class CryptarithmeticPuzzle {
    private static int[] res = new int[26];
    public static int[] playPuzzle(String word1, String word2, String word3) {
        Arrays.fill(res, -1);
        boolean[] used = new boolean[10];
        if(word3.length() > word1.length() && word3.length() > word2.length()) {
            res[word3.charAt(0) - 'A'] = 1;
            used[1] = true;
        }
        dfs(word1, word2, word3, word1.length() - 1, word2.length() - 1, word3.length() - 1, 0, used);
        return res;
    }

    private static boolean dfs(String w1, String w2, String w3, int i1, int i2, int i3, int carry, boolean[] used) {
        if(i3 < 0) {
            if(carry == 0) {
                return true;
            }
            else {
                return false;
            }
        }
        if(i1 >= 0 && res[w1.charAt(i1) - 'A'] < 0) {
            for(int d = 0; d <= 9; d++) {
                if(used[d]) {
                    continue;
                }
                res[w1.charAt(i1) - 'A'] = d;
                used[d] = true;
                if(dfs(w1, w2, w3, i1, i2, i3, carry, used)) {
                    return true;
                }
                else {
                    res[w1.charAt(i1) - 'A'] = -1;
                    used[d] = false;
                }
            }
            return false;
        }
        if(i2 >= 0 && res[w2.charAt(i2) - 'A'] < 0) {
            for(int d = 0; d <= 9; d++) {
                if(used[d]) {
                    continue;
                }
                res[w2.charAt(i2) - 'A'] = d;
                used[d] = true;
                if(dfs(w1, w2, w3, i1, i2, i3, carry, used)) {
                    return true;
                }
                else {
                    res[w2.charAt(i2) - 'A'] = -1;
                    used[d] = false;
                }
            }
            return false;
        }
        int sum = carry;
        sum += i1 >= 0 ? res[w1.charAt(i1) - 'A'] : 0;
        sum += i2 >= 0 ? res[w2.charAt(i2) - 'A'] : 0;
        if(res[w3.charAt(i3) - 'A'] >= 0 && sum % 10 == res[w3.charAt(i3) - 'A']) {
            return dfs(w1, w2, w3, i1 - 1, i2 - 1, i3 - 1, sum / 10, used);
        }
        else if(res[w3.charAt(i3) - 'A'] >= 0 || used[sum % 10]) {
            return false;
        }
        used[sum % 10] = true;
        res[w3.charAt(i3) - 'A'] = sum % 10;
        if(dfs(w1, w2, w3, i1 - 1, i2 - 1, i3 - 1, sum / 10, used)) {
            return true;
        }
        used[sum % 10] = false;
        res[w3.charAt(i3) - 'A'] = -1;
        return false;
    }

    public static void main(String[] args) {
        int[] res = playPuzzle("SEND", "MORE", "MONEY");
        System.out.println(Arrays.toString(res));
    }
}

 

转载于:https://www.cnblogs.com/lz87/p/11306841.html

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值