leetcode
leetcode 1201 ~ 1250
口算难题

口算难题

难度:

标签:

题目描述

代码结果

运行时间: 31 ms, 内存: 16.2 MB


import java.util.HashMap;
import java.util.HashSet;
import java.util.Map;
import java.util.Set;
import java.util.stream.IntStream;

public class VerbalArithmeticStream {
    // Main function to check if the equation is solvable using Streams
    public boolean isSolvable(String[] words, String result) {
        Set<Character> uniqueChars = new HashSet<>();
        for (String word : words) {
            word.chars().mapToObj(c -> (char) c).forEach(uniqueChars::add);
        }
        result.chars().mapToObj(c -> (char) c).forEach(uniqueChars::add);
        if (uniqueChars.size() > 10) {
            return false;
        }
        char[] chars = new char[uniqueChars.size()];
        int index = 0;
        for (char c : uniqueChars) {
            chars[index++] = c;
        }
        return backtrack(words, result, new HashMap<>(), new boolean[10], chars, 0);
    }

    private boolean backtrack(String[] words, String result, Map<Character, Integer> charToDigit, boolean[] usedDigits, char[] chars, int pos) {
        if (pos == chars.length) {
            return isValid(words, result, charToDigit);
        }
        return IntStream.range(0, 10).filter(d -> !usedDigits[d]).anyMatch(d -> {
            charToDigit.put(chars[pos], d);
            usedDigits[d] = true;
            boolean res = backtrack(words, result, charToDigit, usedDigits, chars, pos + 1);
            usedDigits[d] = false;
            charToDigit.remove(chars[pos]);
            return res;
        });
    }

    private boolean isValid(String[] words, String result, Map<Character, Integer> charToDigit) {
        int sum = IntStream.range(0, words.length).map(i -> getNumericValue(words[i], charToDigit)).sum();
        return sum == getNumericValue(result, charToDigit);
    }

    private int getNumericValue(String word, Map<Character, Integer> charToDigit) {
        return word.chars().map(c -> charToDigit.get((char) c)).reduce(0, (a, b) -> a * 10 + b);
    }
}

解释

方法:

这个题解使用回溯算法和剪枝优化来解决口算难题。首先,统计每个字符在words和result中出现的权重,权重表示该字符在数字中的位置。然后对权重进行排序,按照绝对值从大到小的顺序。接着使用深度优先搜索进行回溯,尝试为每个字符分配一个数字。在回溯过程中,通过计算当前分配方案的最小值和最大值,并与当前权重和进行比较,来进行剪枝优化。如果找到一个可行的分配方案,就返回True,否则返回False。

时间复杂度:

O(10^C), C为字符数

空间复杂度:

O(C), C为字符数

代码细节讲解

🦆
在题解中,你提到使用回溯算法和剪枝优化,请问如何确定在哪些情况下进行剪枝?
在题解中,剪枝操作是基于当前的字符分配对后续分配可能造成的影响。在回溯过程中,我们首先计算剩余字符如果按照最小可能值或最大可能值分配后可能形成的总和区间,即计算最小可能总和(mi)和最大可能总和(ma)。如果当前字符分配后的新总和不在这个区间内,那么就没有必要继续这个分支的探索,因为它不可能导致总和为零的有效解。这种基于当前选择对未来可能性限制的思考是剪枝的关键。
🦆
为什么在存储每个字符的权重时,需要将结果result中的字符权重取相反数?
在口算难题中,我们的目标是使得words数组中的字符串所代表的数值之和等于result字符串所代表的数值。在权重计算时,words中的字符应该是正向累加的,因为它们是被加的数。而result中的字符则需要取相反数,因为在等式中result是被减的数(即words的和应该等于result)。这样,我们最终的目标是使所有字符的总权重乘以对应的数字后的和为零。
🦆
题解中提到,如果word的长度超过result,就直接返回False。这种判断是否考虑了所有字符都映射到0的特殊情况?
这个判断实际上没有考虑到所有字符都映射到0的情况。理论上,如果每个字符都映射到0,那么无论words的长度或result的长度如何,都会满足等式。然而,这种情况在实际问题中通常不会被考虑为有效解,特别是当题目或上下文规定不允许所有数字均为0时。因此,如果word的长度超过result,通常意味着无法通过非零的数字映射来达成等式,因此直接返回False是一种合理的简化处理。
🦆
题解中使用了排序策略,按字符权重的绝对值从大到小排序。这种排序方式是如何帮助优化回溯过程的?
按权重的绝对值从大到小排序的目的是优先处理对总和影响最大的字符。这种策略是基于一个观点:权重大的字符对最终的数值有较大影响,因此先为这些字符分配数字可以更快地观察到分配的效果,增加早期剪枝的可能性,从而减少不必要的回溯。例如,一个权重为1000的字符错误分配的影响,远大于一个权重为10的字符。通过这种方式,可以更有效地缩小搜索空间,加快找到解的速度。

相关问题