根据集合中的数字计算目标数量

时间:2010-03-06 11:58:46

标签: algorithm numbers graph-algorithm

我正在做一个问我这个问题的作业问题:

根据一组有限的数字和一个目标数字,找出该组是否可用于使用基本数学运算(add,sub,mult,div)计算目标数,并使用集合中的每个数字完全一次(所以我需要耗尽一下)。这必须通过递归来完成。

所以,例如,如果我有集合

{1, 2, 3, 4}

和目标10,然后我可以使用

来实现目标
((3 * 4) - 2)/1 = 10. 

我试图用伪代码来表达算法,但到目前为止还没有走得太远。我认为图表是要走的路,但肯定会感谢你的帮助。感谢。

7 个答案:

答案 0 :(得分:5)

嗯,你没有提到效率,所以我要发布一个非常强力的解决方案,让你优化它,如果你想。既然你可以拥有parantheses,那么很容易使用Reverse Polish Notation来强制它:

首先,如果您的集合有n个数字,则必须使用n - 1个运算符。因此,您的解决方案将由来自{{您给定的集合},{*,/,+, - }}

的2n - 1个符号序列给出。
st = a stack of length 2n - 1
n = numbers in your set
a = your set, to which you add *, /, +, -
v[i] = 1 if the NUMBER i has been used before, 0 otherwise

void go(int k)
{
  if ( k > 2n - 1 ) 
  {
    // eval st as described on Wikipedia. 
    // Careful though, it might not be valid, so you'll have to check that it is   
    // if it evals to your target value great, you can build your target from the given numbers. Otherwise, go on.

    return;
  }

  for ( each symbol x in a )
    if ( x isn't a number or x is a number but v[x] isn't 1 )
    {
      st[k] = x;
      if ( x is a number )
        v[x] = 1;

      go(k + 1);
    }
}

答案 1 :(得分:5)

这并不是最快的解决方案,而是一种有益的解决方案。

  • 以后缀表示法递归生成所有等式
  • 它还提供从后缀到中缀表示法的翻译
  • 没有进行实际的算术计算,因此您必须自己实现
    • 小心除零

有4个操作数,4个可能的操作符,它产生所有7680 = 5 * 4! * 4 ^ 3 可能的表达方式。

  • 5是加泰罗尼亚语(3)。加泰罗尼亚语(N)是对N + 1个操作数进行paranthesize的方法的数量。
  • 4!因为4个操作数是可置换的
  • 4 ^ 3因为3个运营商各有4个选择

这肯定不能很好地扩展,因为N个操作数的表达式数是[1,8,192,7680,430080,30965760,2724986880,...]。

一般情况下,如果您拥有n+1个操作数,并且必须插入从n种可能性中选择的k个运算符,则可能存在(2n)!/n! k^n个等式。

祝你好运!

import java.util.*;

public class Expressions {
    static String operators = "+-/*";

    static String translate(String postfix) {
        Stack<String> expr = new Stack<String>();
        Scanner sc = new Scanner(postfix);
        while (sc.hasNext()) {
            String t = sc.next();
            if (operators.indexOf(t) == -1) {
                expr.push(t);
            } else {
                expr.push("(" + expr.pop() + t + expr.pop() + ")");
            }
        }
        return expr.pop();
    }

    static void brute(Integer[] numbers, int stackHeight, String eq) {
        if (stackHeight >= 2) {
            for (char op : operators.toCharArray()) {
                brute(numbers, stackHeight - 1, eq + " " + op);
            }
        }
        boolean allUsedUp = true;
        for (int i = 0; i < numbers.length; i++) {
            if (numbers[i] != null) {
                allUsedUp = false;
                Integer n = numbers[i];
                numbers[i] = null;
                brute(numbers, stackHeight + 1, eq + " " + n);
                numbers[i] = n;
            }
        }
        if (allUsedUp && stackHeight == 1) {
            System.out.println(eq + " === " + translate(eq));
        }
    }
    static void expression(Integer... numbers) {
        brute(numbers, 0, "");
    }

    public static void main(String args[]) {
        expression(1, 2, 3, 4);
    }
}

答案 2 :(得分:5)

在考虑如何解决问题之前(比如图表),只关注问题确实很有帮助。如果你发现自己陷入困境并且似乎无法想出任何伪代码,那么很可能会有一些东西阻碍你;尚未解决的其他一些问题或疑虑。在这种情况下,一个示例“粘性”问题可能是“这个问题究竟是什么递归?”

在阅读下一段之前,请先尝试回答这个问题。如果你知道什么是递归的问题,那么编写一个递归方法来解决它可能不是很困难。

您想知道某个使用一组数字的表达式(每个数字只使用一次)是否为您提供了目标值。有四个二进制运算,每个运算都有反向。所以,换句话说,你想知道第一个用其他数字的表达式操作的数字是否能给你目标。好吧,换句话说,你想知道'其他'数字的某些表达是否是[...]。如果没有,那么使用带有第一个数字的第一个操作并不能真正满足您的需求,所以请尝试其他操作。如果它们不起作用,那么也许它本身并不存在。

编辑:我认为这是因为没有括号的四个运算符的中缀表达式,因为对原始问题的评论说为了示例而添加了括号(为了清楚起见?)并且没有明确使用括号说明。

答案 3 :(得分:3)

一般来说,当你需要递归地做某事时,从“底部”开始并思考你的方式是有帮助的。 考虑一下:您有S n 个数字{a,b,c,...},以及一组四个操作{+,-,*,/}。让我们调用你在集合F(S)

上运行的递归函数
  • 如果 n 为1,则F(S)将只是该数字。
  • 如果 n 为2,F(S)可以是八件事:
    • S(2个选项)
    • 中选择您的左手编号
    • 然后选择要应用的操作(4个选项)
    • 您的右手编号将是
    • 中剩下的任何内容
  • 现在,您可以从 n = 2案例中进行推广:
    • x中选择一个S作为左手操作数( n 选项)
    • 选择要应用的操作
    • 您的右手编号为F(S-x)

我会让你从这里拿走它。 :)

编辑:马克提出了有效的批评;上面的方法绝对不会得到一切。要解决这个问题,您需要以稍微不同的方式来考虑它:

  • 在每一步,您首先选择一个操作(4个选项),然后选择
  • 分区 S分为两组,分别为左手和右手操作数,
  • 并递归地将F应用于两个分区

但是,将一组的所有分区分成两部分本身并不是一件容易的事。

答案 4 :(得分:2)

关于如何解决这个问题的最佳线索是,您的老师/教授希望您使用递归。也就是说,这个不是数学问题 - 这是一个搜索问题。

不要放弃太多(毕竟这是家庭作业),但是你必须使用运算符,数字和包含剩余数字的列表来产生对递归函数的调用。递归函数将从列表中提取一个数字,并使用传入的操作将其与传入的数字(即您的运行总计)相结合。获取运行总计并再次使用列表中的其余项目自行调用(您必须在调用中迭代列表,但调用顺序是深度优先)。对四个运营商中的每一个执行此操作一次,除非前一段搜索已实现成功。

我更新了这个以使用列表而不是堆栈

当操作的结果是您的目标编号且列表为空时,您已成功找到操作集(跟踪成功叶子的路径的那些操作) - 设置成功标志并展开。请注意,运算符不在列表中,也不在调用中:函数本身总是迭代所有四个。从成功的叶子“展开”运算符序列以获取序列的机制是返回当前运算符和前缀为递归调用返回的值的数字(只有一个会成功,因为你停止成功 - 显然,是一个使用)。如果没有一个成功,那么你返回的内容无论如何都不重要。

更新当您必须考虑Daniel发布的表达式时,这很多更难。你有关于数字的数据的组合数据(由于/和 - 的数字是顺序敏感的,即使没有分组和分组,因为它改变了优先级)。然后,当然,你也有操作的组合。管理(4 + 3)* 2和4 +(3 * 2)之间的差异更难,因为分组不像运算符或数字那样递归(你可以在制作你的时候以广度优先的方式迭代它们(深度优先)递归调用)。

答案 5 :(得分:2)

这里有一些Python代码可以帮助您入门:它只是打印所有可能的表达式,而不必过多担心冗余。您需要对其进行修改以评估表达式并与目标数进行比较,而不是简单地打印它们。

基本思路是:给定一组数字S,以所有可能的方式将S分为两个子集leftright(我们不关心{中的顺序或元素) {1}}和left),rightleft都是非空的。现在,对于每个分区,找到组合right中的元素的所有方法(递归!),同样地left组合,并将两个结果值与所有可能的运算符组合。当一个集合只有一个元素时,递归最低点,在这种情况下,只有一个值可能。

即使您不了解Python,right函数也应该相当容易理解; expressions函数包含一些Python奇怪的东西,但它所做的就是将列表splittings的所有分区分成左右两部分。

l

答案 6 :(得分:-1)

伪代码:

Works(list, target)
for n in list
tmp=list.remove(n)
return Works(tmp,target+n) or Works(tmp,target-n) or Works(tmp, n-target) or ...

那么你只需要把基础案例放进去。我想我放弃了太多。