如何设计一种算法来计算倒计时式数学数字拼图

Posted

技术标签:

【中文标题】如何设计一种算法来计算倒计时式数学数字拼图【英文标题】:How to design an algorithm to calculate countdown style maths number puzzle 【发布时间】:2013-03-08 11:44:41 【问题描述】:

我一直想这样做,但每次我开始思考这个问题时,都会因为它的指数性质而大吃一惊。

我希望能够理解的问题解决者和代码是用于倒计时数学问题的:

给定一组数字 X1 到 X5,计算如何使用数学运算将它们组合成 Y。 您可以应用乘法、除法、加法和减法。

那么1,3,7,6,8,3 是如何生成348 的呢?

答案:(((8 * 7) + 3) -1) *6 = 348

如何编写一个算法来解决这个问题?当你试图解决这样的问题时,你从哪里开始?在设计这样的算法时,您必须考虑哪些重要的考虑因素?

【问题讨论】:

蛮力? IE。尝试所有组合,直到你得到正确答案。 +1 真的很酷的问题。最困难的运算符是组合运算,您可以将 2 个或多个数字并排放置以创建一个两位数(或三位数等)的数字,因为该运算符只能用于“原始”数字而不是计算数字.示例:368 - 17 - 3 = 348。 这个答案似乎很相关:***.com/questions/14309515/… 蛮力。只要任何部分结果不是整数,您就可以跳过其余部分。 (至少对于电视节目我知道这是一个限制)。 【参考方案1】:

Java 中非常快速而肮脏的解决方案:

public class JavaApplication1


    public static void main(String[] args)
    
        List<Integer> list = Arrays.asList(1, 3, 7, 6, 8, 3);
        for (Integer integer : list) 
            List<Integer> runList = new ArrayList<>(list);
            runList.remove(integer);
            Result result = getOperations(runList, integer, 348);
            if (result.success) 
                System.out.println(integer + result.output);
                return;
            
        
    

    public static class Result
    

        public String output;
        public boolean success;
    

    public static Result getOperations(List<Integer> numbers, int midNumber, int target)
    
        Result midResult = new Result();
        if (midNumber == target) 
            midResult.success = true;
            midResult.output = "";
            return midResult;
        
        for (Integer number : numbers) 
            List<Integer> newList = new ArrayList<Integer>(numbers);
            newList.remove(number);
            if (newList.isEmpty()) 
                if (midNumber - number == target) 
                    midResult.success = true;
                    midResult.output = "-" + number;
                    return midResult;
                
                if (midNumber + number == target) 
                    midResult.success = true;
                    midResult.output = "+" + number;
                    return midResult;
                
                if (midNumber * number == target) 
                    midResult.success = true;
                    midResult.output = "*" + number;
                    return midResult;
                
                if (midNumber / number == target) 
                    midResult.success = true;
                    midResult.output = "/" + number;
                    return midResult;
                
                midResult.success = false;
                midResult.output = "f" + number;
                return midResult;
             else 
                midResult = getOperations(newList, midNumber - number, target);
                if (midResult.success) 
                    midResult.output = "-" + number + midResult.output;
                    return midResult;
                
                midResult = getOperations(newList, midNumber + number, target);
                if (midResult.success) 
                    midResult.output = "+" + number + midResult.output;
                    return midResult;
                
                midResult = getOperations(newList, midNumber * number, target);
                if (midResult.success) 
                    midResult.output = "*" + number + midResult.output;
                    return midResult;
                
                midResult = getOperations(newList, midNumber / number, target);
                if (midResult.success) 
                    midResult.output = "/" + number + midResult.output;
                    return midResult
                
            

        
        return midResult;
    

更新

它基本上只是具有指数复杂度的简单蛮力算法。 但是,您可以通过利用一些启发式函数来获得一些改进,这将帮助您对数字序列或(和)您将在 getOperatiosn() 函数递归的每个级别中处理的操作进行排序。

这种启发式函数的示例是中间结果和总目标结果之间的差异。

但是,这种方式只有最佳情况和平均情况的复杂性得到改善。最坏情况的复杂性保持不变。

最坏情况的复杂性可以通过某种分支切割来提高。我不确定在这种情况下是否可能。

【讨论】:

那个源代码不能在我的机器上编译(javac 1.6.0_41)。 是的,有 Diamond 表示法 - List&lt;Integer&gt; runList = new ArrayList&lt;&gt;(list); 你必须使用 Java 7 或用经典的 java 语法替换 Diamond 表示法。 我相信这个解决方案并不详尽。对于最初的问题,它只返回 5 个结果,而我的版本返回 500。我不确定它是否会为每个可能的问题找到有效的解决方案,如果它似乎错过了一些有效的匹配项。 我做了一些测试,例如,这确实无法找到382 1,3,7,6,8,3 的匹配项。 (有效匹配是382 = ((7 * ((3 + 8) * (6 - 1))) - 3) 我认为应该有隐式括号,如示例中所述 - (((8 * 7) + 3) -1) *6 = 348 不是吗?你能确认我们也可以使用括号吗?在这种情况下,我可以扩展我的解决方案。【参考方案2】:

当然它是指数级的,但它很小,所以一个好的(足够)天真的实现将是一个好的开始。我建议你放弃通常的带括号的中缀符号,并使用后缀,它更容易编程。您始终可以将输出美化为单独的阶段。

首先列出并评估所有(有效)数字和运算符序列。例如(在后缀中):

1 3 7 6 8 3 + + + + + -> 28
1 3 7 6 8 3 + + + + - -> 26

我的 Java 很可笑,我不是来这里被嘲笑的,所以我将把代码交给你。

致所有阅读本文的聪明人:是的,我知道即使是像这样的小问题,也有可能更快的更聪明的方法,我只是将 OP 指向一个初步的工作解决方案。其他人可以用更智能的解决方案写出答案。

所以,回答你的问题:

我从一个算法开始,我认为该算法会很快引导我找到一个可行的解决方案。在这种情况下,(对我而言)显而易见的选择是对所有可能的计算进行详尽的枚举和测试。 如果明显的算法由于性能原因看起来没有吸引力,我将开始更深入地思考它,回想我知道的其他可能提供更好性能的算法。我可能会先开始编写其中一个代码。 如果我坚持使用穷举算法并发现运行时间实际上太长,那么我可能会返回上一步并再次编码。但这必须是值得我花时间的,需要进行成本/收益评估——只要我的代码能够胜过 Rachel Riley,我就会感到满意。 重要的考虑因素包括我的时间 vs 计算机时间,我的成本要高得多。

【讨论】:

这绝对是要做的事情。问题的顺序是 4^5 运算符组合 * (9 选择 4) 运算符的位置。 (字符串中的第一个元素必须是数字,最后一个元素必须是运算符)。其他序列可能会产生非法树。那只是大约 1 亿个组合? 同样,方程表示的选择是正确的——后缀或反向润色可以表示方程中括号的所有组合,尽管它可能导致重复表示。组合分析仍然表明该任务是可行的。 我有一种感觉,就是6!按此顺序与 4^5 运算符连接的数字不会处理带括号的情况——因为该顺序仅产生左或右关联表达式。我认为无法用“N N N N op op op”评估 (1+2)/(3+4)。 我能想到的最简单的反例是(1-3)-7,不能通过排列1,3,7后跟'--'来计算。 我可能不明白你们在这里。我不推荐后缀表达式。我推荐的只是要应用的数字列表和操作列表。这解决了大多数问题,但是像'(a + b)/(c + d)'这样的技巧。因为它需要在内存中存储 2 个位置。【参考方案3】:

以下 c++11 中的工作解决方案。

基本思想是使用基于堆栈的评估(请参阅RPN)并将可行的解决方案转换为infix notation,仅用于显示目的。

如果我们有N 输入数字,我们将使用(N-1) 运算符,因为每个运算符都是二进制的。

首先我们创建操作数和运算符的有效排列selector_ 数组)。有效的排列是一种可以在没有堆栈下溢的情况下进行评估并且以堆栈上的一个值(结果)结束的排列。因此1 1 + 有效,但1 + 1 无效。

我们使用操作数的每个排列(values_ 数组)和运算符的每个组合(ops_ 数组)来测试每个这样的操作数-运算符排列。匹配的结果打印得很漂亮。

参数取自命令行[-s] &lt;target&gt; &lt;digit&gt;[ &lt;digit&gt;...]-s 开关防止穷举搜索,只打印第一个匹配的结果。

(使用./mathpuzzle 348 1 3 7 6 8 3获取原问题的答案)

此解决方案不允许连接输入数字以形成数字。这可以作为额外的外部循环添加。

工作代码可以从here下载。 (注意:我更新了该代码,支持连接输入数字以形成解决方案)

更多解释参见代码 cmets。

#include <iostream>
#include <vector>
#include <algorithm>
#include <stack>
#include <iterator>
#include <string>

namespace 

enum class Op 
    Add,
    Sub,
    Mul,
    Div,
;

const std::size_t NumOps = static_cast<std::size_t>(Op::Div) + 1;
const Op FirstOp = Op::Add;

using Number = int;

class Evaluator 
    std::vector<Number> values_; // stores our digits/number we can use
    std::vector<Op> ops_; // stores the operators
    std::vector<char> selector_; // used to select digit (0) or operator (1) when evaluating. should be std::vector<bool>, but that's broken

    template <typename T>
    using Stack = std::stack<T, std::vector<T>>;

    // checks if a given number/operator order can be evaluated or not
    bool isSelectorValid() const 
        int numValues = 0;
        for (auto s : selector_) 
            if (s) 
                if (--numValues <= 0) 
                    return false;
                
            
            else 
                ++numValues;
            
        
        return (numValues == 1);
    

    // evaluates the current values_ and ops_ based on selector_
    Number eval(Stack<Number> &stack) const 
        auto vi = values_.cbegin();
        auto oi = ops_.cbegin();
        for (auto s : selector_) 
            if (!s) 
                stack.push(*(vi++));
                continue;
            
            Number top = stack.top();
            stack.pop();
            switch (*(oi++)) 
                case Op::Add:
                    stack.top() += top;
                    break;
                case Op::Sub:
                    stack.top() -= top;
                    break;
                case Op::Mul:
                    stack.top() *= top;
                    break;
                case Op::Div:
                    if (top == 0) 
                        return std::numeric_limits<Number>::max();
                    
                    Number res = stack.top() / top;
                    if (res * top != stack.top()) 
                        return std::numeric_limits<Number>::max();
                    
                    stack.top() = res;
                    break;
            
        
        Number res = stack.top();
        stack.pop();
        return res;
    

    bool nextValuesPermutation() 
        return std::next_permutation(values_.begin(), values_.end());
    

    bool nextOps() 
        for (auto i = ops_.rbegin(), end = ops_.rend(); i != end; ++i) 
            std::size_t next = static_cast<std::size_t>(*i) + 1;
            if (next < NumOps) 
                *i = static_cast<Op>(next);
                return true;
            
            *i = FirstOp;
        
        return false;
    

    bool nextSelectorPermutation() 
        // the start permutation is always valid
        do 
            if (!std::next_permutation(selector_.begin(), selector_.end())) 
                return false;
            
         while (!isSelectorValid());
        return true;
    

    static std::string buildExpr(const std::string& left, char op, const std::string &right) 
        return std::string("(") + left + ' ' + op + ' ' + right + ')';
    

    std::string toString() const 
        Stack<std::string> stack;
        auto vi = values_.cbegin();
        auto oi = ops_.cbegin();
        for (auto s : selector_) 
            if (!s) 
                stack.push(std::to_string(*(vi++)));
                continue;
            
            std::string top = stack.top();
            stack.pop();
            switch (*(oi++)) 
                case Op::Add:
                    stack.top() = buildExpr(stack.top(), '+', top);
                    break;
                case Op::Sub:
                    stack.top() = buildExpr(stack.top(), '-', top);
                    break;
                case Op::Mul:
                    stack.top() = buildExpr(stack.top(), '*', top);
                    break;
                case Op::Div:
                    stack.top() = buildExpr(stack.top(), '/', top);
                    break;
            
        
        return stack.top();
    

public:
    Evaluator(const std::vector<Number>& values) :
            values_(values),
            ops_(values.size() - 1, FirstOp),
            selector_(2 * values.size() - 1, 0) 
        std::fill(selector_.begin() + values_.size(), selector_.end(), 1);
        std::sort(values_.begin(), values_.end());
    

    // check for solutions
    // 1) we create valid permutations of our selector_ array (eg: "1 1 + 1 +",
    //    "1 1 1 + +", but skip "1 + 1 1 +" as that cannot be evaluated
    // 2) for each evaluation order, we permutate our values
    // 3) for each value permutation we check with each combination of
    //    operators
    // 
    // In the first version I used a local stack in eval() (see toString()) but
    // it turned out to be a performance bottleneck, so now I use a cached
    // stack. Reusing the stack gives an order of magnitude speed-up (from
    // 4.3sec to 0.7sec) due to avoiding repeated allocations.  Using
    // std::vector as a backing store also gives a slight performance boost
    // over the default std::deque.
    std::size_t check(Number target, bool singleResult = false) 
        Stack<Number> stack;

        std::size_t res = 0;
        do 
            do 
                do 
                    Number value = eval(stack);
                    if (value == target) 
                        ++res;
                        std::cout << target << " = " << toString() << "\n";
                        if (singleResult) 
                            return res;
                        
                    
                 while (nextOps());
             while (nextValuesPermutation());
         while (nextSelectorPermutation());
        return res;
    
;

 // namespace

int main(int argc, const char **argv) 
    int i = 1;
    bool singleResult = false;
    if (argc > 1 && std::string("-s") == argv[1]) 
        singleResult = true;
        ++i;
    
    if (argc < i + 2) 
        std::cerr << argv[0] << " [-s] <target> <digit>[ <digit>]...\n";
        std::exit(1);
    
    Number target = std::stoi(argv[i]);
    std::vector<Number> values;
    while (++i <  argc) 
        values.push_back(std::stoi(argv[i]));
    
    Evaluator evaluatorvalues;
    std::size_t res = evaluator.check(target, singleResult);
    if (!singleResult) 
        std::cout << "Number of solutions: " << res << "\n";
    
    return 0;

【讨论】:

【参考方案4】:

输入显然是一组数字和运算符:D=1,3,3,6,7,8,3 和 Op=+,-,*,/。最直接的算法是brute force 求解器,其中enumerates 这些集合的所有可能组合。集合 Op 的元素可以根据需要多次使用,但集合 D 中的元素只使用一次。伪代码:

D=1,3,3,6,7,8,3
Op=+,-,*,/
Solution=348
for each permutation D_ of D:
   for each binary tree T with D_ as its leafs:
       for each sequence of operators Op_ from Op with length |D_|-1:
           label each inner tree node with operators from Op_
           result = compute T using infix traversal
           if result==Solution
              return T
return nil

除此之外:阅读 jedrus07 和 HPM 的答案。

【讨论】:

【参考方案5】:

到目前为止,最简单的方法是智能地暴力破解它。您只能用 6 个数字和 4 个运算符构建有限数量的表达式,只需遍历所有表达式即可。

有多少?由于您不必使用所有数字并且可以多次使用相同的运算符,因此此问题相当于“您最多可以使用 6 个叶子和四个可能的标签制作多少个严格标记的二叉树(又名完整二叉树)对于每个非叶节点?”。

具有 n 个叶子的完整二​​叉树的数量等于 catalan(n-1)。你可以看到如下:

每棵有n个叶子的满二叉树都有n-1个内部节点,并以一种独特的方式对应于一个有n-1个节点的非满二叉树(只需从满叶子中删除所有叶子即可得到它)。恰好有 catalan(n) 可能的二叉树有 n 个节点,所以我们可以说一棵有 n 个叶子的严格二叉树有 catalan(n-1) 可能的不同结构。

每个非叶节点有 4 种可能的运算符:4^(n-1) 种可能性 叶子可以在 n 中编号! * (6 选择 (n-1)) 不同的方式。 (将其除以 k!对于出现 k 次的每个数字,或者只是确保所有数字都不同)

因此,对于 6 个不同的数字和 4 个可能的运算符,您会得到 Sum(n=1...6) [ Catalan(n-1) * 6!/(6-n)! * 4^(n-1) ] 总共 33,665,406 个可能的表达式。不是很多。

你如何枚举这些树?

给定具有 n-1 或更少节点的所有树的集合,您可以通过系统地将所有 n-1 树与空树配对、所有 n-2 树与 1 节点树配对来创建具有 n 个节点的所有树,所有n-3棵树和所有2个节点树等,并将它们用作新形成的树的左右子树。

因此,从一个空集开始,您首先生成只有一个根节点的树,然后从一个新根开始,您可以将其用作左子树或右子树,从而生成如下所示的两棵树:/ 和.以此类推。

您可以动态地将它们转换为一组表达式(只需遍历运算符和数字)并在执行过程中对它们进行评估,直到产生目标数字。

【讨论】:

【参考方案6】:

我已经用 Python 编写了自己的倒计时求解器。

这是代码;它也可以在GitHub:

#!/usr/bin/env python3

import sys
from itertools import combinations, product, zip_longest
from functools import lru_cache

assert sys.version_info >= (3, 6)


class Solutions:

    def __init__(self, numbers):
        self.all_numbers = numbers
        self.size = len(numbers)
        self.all_groups = self.unique_groups()

    def unique_groups(self):
        all_groups = 
        all_numbers, size = self.all_numbers, self.size
        for m in range(1, size+1):
            for numbers in combinations(all_numbers, m):
                if numbers in all_groups:
                    continue
                all_groups[numbers] = Group(numbers, all_groups)
        return all_groups

    def walk(self):
        for group in self.all_groups.values():
            yield from group.calculations


class Group:

    def __init__(self, numbers, all_groups):
        self.numbers = numbers
        self.size = len(numbers)
        self.partitions = list(self.partition_into_unique_pairs(all_groups))
        self.calculations = list(self.perform_calculations())

    def __repr__(self):
        return str(self.numbers)

    def partition_into_unique_pairs(self, all_groups):
        # The pairs are unordered: a pair (a, b) is equivalent to (b, a).
        # Therefore, for pairs of equal length only half of all combinations
        # need to be generated to obtain all pairs; this is set by the limit.
        if self.size == 1:
            return
        numbers, size = self.numbers, self.size
        limits = (self.halfbinom(size, size//2), )
        unique_numbers = set()
        for m, limit in zip_longest(range((size+1)//2, size), limits):
            for numbers1, numbers2 in self.paired_combinations(numbers, m, limit):
                if numbers1 in unique_numbers:
                    continue
                unique_numbers.add(numbers1)
                group1, group2 = all_groups[numbers1], all_groups[numbers2]
                yield (group1, group2)

    def perform_calculations(self):
        if self.size == 1:
            yield Calculation.singleton(self.numbers[0])
            return
        for group1, group2 in self.partitions:
            for calc1, calc2 in product(group1.calculations, group2.calculations):
                yield from Calculation.generate(calc1, calc2)

    @classmethod
    def paired_combinations(cls, numbers, m, limit):
        for cnt, numbers1 in enumerate(combinations(numbers, m), 1):
            numbers2 = tuple(cls.filtering(numbers, numbers1))
            yield (numbers1, numbers2)
            if cnt == limit:
                return

    @staticmethod
    def filtering(iterable, elements):
        # filter elements out of an iterable, return the remaining elements
        elems = iter(elements)
        k = next(elems, None)
        for n in iterable:
            if n == k:
                k = next(elems, None)
            else:
                yield n

    @staticmethod
    @lru_cache()
    def halfbinom(n, k):
        if n % 2 == 1:
            return None
        prod = 1
        for m, l in zip(reversed(range(n+1-k, n+1)), range(1, k+1)):
            prod = (prod*m)//l
        return prod//2


class Calculation:

    def __init__(self, expression, result, is_singleton=False):
        self.expr = expression
        self.result = result
        self.is_singleton = is_singleton

    def __repr__(self):
        return self.expr

    @classmethod
    def singleton(cls, n):
        return cls(f"n", n, is_singleton=True)

    @classmethod
    def generate(cls, calca, calcb):
        if calca.result < calcb.result:
            calca, calcb = calcb, calca
        for result, op in cls.operations(calca.result, calcb.result):
            expr1 = f"calca.expr" if calca.is_singleton else f"(calca.expr)"
            expr2 = f"calcb.expr" if calcb.is_singleton else f"(calcb.expr)"
            yield cls(f"expr1 op expr2", result)

    @staticmethod
    def operations(x, y):
        yield (x + y, '+')
        if x > y:                     # exclude non-positive results
            yield (x - y, '-')
        if y > 1 and x > 1:           # exclude trivial results
            yield (x * y, 'x')
        if y > 1 and x % y == 0:      # exclude trivial and non-integer results
            yield (x // y, '/')


def countdown_solver():
    # input: target and numbers. If you want to play with more or less than
    # 6 numbers, use the second version of 'unsorted_numbers'.
    try:
        target = int(sys.argv[1])
        unsorted_numbers = (int(sys.argv[n+2]) for n in range(6))  # for 6 numbers
#        unsorted_numbers = (int(n) for n in sys.argv[2:])         # for any numbers
        numbers = tuple(sorted(unsorted_numbers, reverse=True))
    except (IndexError, ValueError):
        print("You must provide a target and numbers!")
        return

    solutions = Solutions(numbers)
    smallest_difference = target
    bestresults = []
    for calculation in solutions.walk():
        diff = abs(calculation.result - target)
        if diff <= smallest_difference:
            if diff < smallest_difference:
                bestresults = [calculation]
                smallest_difference = diff
            else:
                bestresults.append(calculation)
    output(target, smallest_difference, bestresults)


def output(target, diff, results):
    print(f"\nThe closest results differ from target by diff. They are:\n")
    for calculation in results:
        print(f"calculation.result = calculation.expr")


if __name__ == "__main__":
countdown_solver()

算法的工作原理如下:

    数字按降序放入长度为 6 的元组中。然后,创建所有长度为 1 到 6 的唯一子组,首先创建最小的组。

    示例:(75, 50, 5, 9, 1, 1) -> (75), (50), (9), (5), (1), (75, 50), (75, 9), (75, 5), ..., (75, 50, 9, 5, 1, 1)。

    接下来,这些组被组织成一个层次树:每个组都被划分为其非空子组的所有唯一无序对。

    示例:(9, 5, 1, 1) -> [(9, 5, 1) + (1), (9, 1, 1) + (5), (5, 1, 1) + ( 9), (9, 5) + (1, 1), (9, 1) + (5, 1)]。

    在每组数字中,都会执行计算并存储结果。对于长度为 1 的组,结果只是数字本身。对于较大的组,对每对子组进行计算:在每对子组中,使用 +、-、x 和 / 将第一个子组的所有结果与第二个子组的所有结果组合,并存储有效结果。

    示例:(75, 5) 由 ((75), (5)) 对组成。 (75)的结果是75; (5)的结果是5; (75, 5) 的结果是 [75+5=80, 75-5=70, 75*5=375, 75/5=15]。

    以这种方式,从最小的组到最大的组,所有结果都生成了。最后,算法遍历所有结果并选择与目标数字最接近的结果。

对于一组m个数,最大算术运算次数为

comps[m] = 4*sum(binom(m, k)*comps[k]*comps[m-k]//(1 + (2*k)//m) for k in range(1, m//2+1))

对于所有长度为 1 到 6 的组,则最大总计算次数为

total = sum(binom(n, m)*comps[m] for m in range(1, n+1))

这是 1144386。实际上,它会少得多,因为算法重用重复组的结果,忽略了琐碎的操作(加 0,乘以 1 等),并且因为游戏规则规定中间结果必须是正整数(这限制了除法运算符的使用)。

【讨论】:

【参考方案7】:

我认为,您需要先严格定义问题。你被允许做什么,你不能做什么。您可以从简单开始,只允许乘法、除法、减法和加法。

现在您知道了您的问题空间 - 输入集、可用操作集和所需输入。如果您只有 4 个操作和 x 个输入,则组合数小于:

您可以执行操作的顺序数 (x!) 乘以每一步可能的操作选择:4^x。正如你所看到的 6 个数字,它给出了合理的 2949120 操作。这意味着这可能是您对蛮力算法的限制。

一旦你有了蛮力并且知道它有效,你就可以开始使用某种A* algorithm 来改进你的算法,这需要你定义启发式函数。

在我看来,考虑它的最佳方式是搜索问题。主要困难将是找到好的启发式方法,或减少问题空间的方法(如果您的数字与答案相加,则至少需要一个乘法等)。从小处着手,在此基础上构建并在编写代码后提出后续问题。

【讨论】:

【参考方案8】:

我为此编写了一个终端应用程序: https://github.com/pg328/CountdownNumbersGame/tree/main

在里面,我附上了一个解空间大小的计算说明(它是 n*((n-1)!^2)*(2^n-1),所以:n=6 - > 2,764,800。我知道,很严重),更重要的是为什么会这样。如果您想检查一下,我的实现就在那里,但如果您不这样做,我会在这里解释。

本质上,最坏的情况是蛮力,因为据我所知,如果没有明确检查,就无法确定任何特定分支是否会产生有效答案。话虽如此,平均情况只是其中的一小部分;它是那个数字除以有效解决方案的数量(我倾向于在我的程序中看到大约 1000 个,其中 10 个左右是唯一的,其余的是这 10 个的排列)。如果我用手挥动一个数字,我会说大约有 2,765 个分支来检查哪个需要没有时间。 (是的,即使在 Python 中也是如此。)

TL;DR:尽管解决方案空间很大并且需要几百万次操作才能找到所有解决方案,但只需要一个答案。最好的方法是蛮力,直到你找到一个并吐出来。

【讨论】:

【参考方案9】:

我写了一个稍微简单的版本:

    对于列表中 2 个(不同的)元素的每个组合,并使用 +、-、*、/ 组合它们(请注意,由于 a>b,因此只需要 a-b,如果 a%b=0,则只需要 a/b) 如果以组合为目标,则记录解决方案 递归调用精简列表
import sys

def driver():
    try:
        target = int(sys.argv[1])
        nums = list((int(sys.argv[i+2]) for i in range(6)))
    except (IndexError, ValueError):
        print("Provide a list of 7 numbers")
        return
    solutions = list()
    solve(target, nums, list(), solutions)
    unique = set()
    final = list()
    for s in solutions:
        a = '-'.join(sorted(s))
        if not a in unique:
            unique.add(a)
            final.append(s)
    for s in final:     #print them out
        print(s)

def solve(target, nums, path, solutions):
    if len(nums) == 1:
        return
    distinct = sorted(list(set(nums)), reverse = True)
    rem1 = list(distinct)
    for n1 in distinct: #reduce list by combining a pair
        rem1.remove(n1)
        for n2 in rem1:
            rem2 = list(nums)       # in case of duplicates we need to start with full list and take out the n1,n2 pair of elements
            rem2.remove(n1)
            rem2.remove(n2)
            combine(target, solutions, path, rem2, n1, n2, '+')
            combine(target, solutions, path, rem2, n1, n2, '-')
            if n2 > 1:
                combine(target, solutions, path, rem2, n1, n2, '*')
                if not n1 % n2:
                    combine(target, solutions, path, rem2, n1, n2, '//')

def combine(target, solutions, path, rem2, n1, n2, symb):
    lst = list(rem2)
    ans = eval("021".format(n1, n2, symb))
    newpath = path + ["031=2".format(n1, n2, ans, symb[0])]
    if ans == target:
        solutions += [newpath]
    else:
        lst.append(ans)
        solve(target, lst, newpath, solutions)
    
if __name__ == "__main__":
    driver()

【讨论】:

以上是关于如何设计一种算法来计算倒计时式数学数字拼图的主要内容,如果未能解决你的问题,请参考以下文章

java大作业之拼图游戏

JS或者PHP的倒计时程序代码

如何在iphone sdk中使用日期倒计时来计算天数[关闭]

课程设计数字秒表设计 求高手解答。

夏令营倒计时2天 | 霍普杯响应式设计参数化三大研究组,你想要的都在这里

云计算大会倒计时1天 | 共话数字化未来