【问题标题】:Algorithm for permutations of operators and operands运算符和操作数的排列算法
【发布时间】:2010-10-16 06:37:36
【问题描述】:

我在一个面试网站上遇到了这个问题 - 我们有 4 个数字,例如 n1、n2、n3、n4。我们可以将它们放在任何 顺序,我们可以在它们之间使用数学运算符 +、-、*、/ 最终结果为 24。为此编写一个算法 - 它需要 4 个数字,无论最终结果 24 是否可能,都返回 false 或 true 与任何组合。同一个运算符可以多次使用。

其中一种方法是 -

  1. 置换运算符
  2. 置换操作数
  3. 将 2. 中的每个排列应用于 1 中的每个排列。

此解决方案将是蛮力的,不会是最佳解决方案。 我认为使用二叉搜索树可能会有更好的解决方案。

【问题讨论】:

    标签: algorithm language-agnostic permutation


    【解决方案1】:

    使用 RPN(反向波兰表示法)

    有关 RPN 简介,请参阅 here

    问题规模

    我们必须建立一个由四个数字组成的列表,这意味着 3 个运算符。
    这些数字和运算符将被推送或执行到堆栈上。

    让我们调用执行列表 {a1 a2 a3 a4 a5 a6 a7}。

    {a1 a2} 应该是数字,因为堆栈上没有一元操作。

    {a7} 应该是一个操作符,用来完成操作。

    对于{a3, a4, a5, a6},我们有几个选项,但总是至少两个数字必须在堆栈中才能进行操作。所以可能的组合是:(N= number, O=Operator)

    {N N O O}、{N O N O}、{O N O N}、{O N N O} 和 {N O O N}。

    {O O N N} 的组合被禁止,因为第二个 O 的堆栈是空的。

    所以我们有:

          | {N N O O} |  
          | {N O N O} |  
    {N N} | {O N O N} | {O}  
          | {O N N O} |  
          | {N O O N} |  
    

    现在我们将计算可能的安排。当然,我们多算了,因为交换算子(Plus 和 Times)可能会将置换树切成两半,但问题足够小,不必为此烦恼。 (在序列为 {O O} 的情况下,我们也多算了。但我们只是继续......)

    我们必须为第一段选择四个数字中的两个数字,这是 12 可能的安排。

    对于中间段,剩下的两个数可能只能置换,即因子2

    但我们还有另一个因素 5 来计算中间部分的五个备选方案。

    对于三个运算符,因为它们可能会重复,我们有一个因子 4^3=64

    所以问题的大小是粗体数字的乘积:12 2 5 64 = 7680。不需要优化,我们可以蛮力进行。

    剩下的问题是构建 7680 安排和 RPN 评估器。两者都相对容易的任务。

    我会发布它......它仍然是一个草稿,但现在为时已晚!明天继续!

    编辑:RPN 评估器

    这里是递归 RPN 求值​​器的代码。我选择用函数式语言(Mathematica)来简化运算符解析

    rpn[listipt_, stackipt_: {}] := 
      Module[{list=listipt,stack=stackipt}, (*recursive rpn evaluator*)
    
        If[list == {}, Return[stack[[1]]]];        (*end*)
        If[NumberQ[list[[1]]],                     (*if numeric*)
         Return@rpn[Rest[list], PrependTo[stack,list[[1]]]];  (*push nbr and recurse*)
        ,
         (stack[[2]]=list[[1]][stack[[2]], stack[[1]]];       (*if not, operate*)
          Return@rpn[Rest[list], Rest[stack]];);              (*and recurse*)
       ];
    ];
    

    使用示例

    rpn[{1, 1, 1, Plus, Plus}]
    3
    
    rpn[{2, 2, 2, Plus, Plus}]
    6
    
    rpn[{2, 3, 4, Plus, Times}]  (* (4+3)*7 *)
    14
    
    rpn[{2, 3, 4, Plus, Divide}]  (* (2+3)/4 *)
    2/7  
    

    稍后我将发布元组生成器,显示它们是 7680 以及一些关于操作可能结果分布的有趣结果(实际上对于 {1,2,3,4} 集,您可以只能得到 230 种不同的结果!)。

    编辑:元组构造

    首先我们明确地构造中间部分的可能性

    t1 = {{n3, n4, o1, o2}, 
          {n3, o1, n4, o2}, 
          {o1, n3, o2, n4}, 
          {o1, n3, n4, o2}, 
          {n3, o1, o2, n4}};
    

    现在我们为 {n1,n2} 和最后一个运算符添加两个变体

    t2 = Join[Map[Join[{n1, n2}, #, {o3}] &, t1], 
              Map[Join[{n2, n1}, #, {o3}] &, t1]] ( bahh ... don't mind the code*)
    

    导致我们有 10 种不同的配置

    现在我们必须使用数字和运算符的所有可能排列来填充所有这些配置。

    我们首先将所有数字排列构造为我们的元组的分配规则

     repListNumbers = (*construct all number permutations*)
        Table[{n1 -> #[[1]], n2 -> #[[2]], n3 -> #[[3]], n4 -> #[[4]]} &[i], 
             {i, Permutations[{1, 2, 3, 4}]}];
    

    这些小野兽有形态

      {n1 -> 1, n2 -> 2, n3 -> 3, n4 -> 4}
    

    我们可以使用它们来替换元组中的值。例如:

      {n1,n2,n3,o1,o2,n4,o3} /. {n1 -> 1, n2 -> 2, n3 -> 3, n4 -> 4}
    

    结果

      {1,2,3,o1,o2,4,o3}
    

    当然,我们可能已经将替换规则构建为一个能够随意更改数字集的函数。 我们现在对操作符做类似的事情

    repListOps =      (*Construct all possible 3 element tuples*)
      Table[{o1 -> #[[1]], o2 -> #[[2]], o3 -> #[[3]]} &[i], 
          {i, Tuples[{Plus, Times, Divide, Subtract}, 3]}];    
    

    所以我们得到了一个像

    这样的东西的集合
     {o1->Plus, o2->Plus, o3->Divide}
    

    现在我们将元组和所有替换规则合并到一个大列表中:

    t3 = Flatten[t2 /. repListNumbers /. repListOps, 2];
    

    这会导致 15360 种不同的计算。但是我们知道有两倍的因子被高估了,所以现在我们删除重复的元素:

    t3 =Union[t3]
    

    这给了我们预期的 7680 个元素。

    还有一些多算,因为 {2,3,Times} = {3,2,Times} = 6,但这对于我们目前的目的来说是可以的。

    评估结果

    现在我们有了 RPN 评估器和所有这些元组,我们想知道某个最终结果是否可能。

    我们只需要询问该数字是否包含在结果集中:

    In[252]:= MemberQ[rpn /@ t3, 24]
    Out[252]= True
    
    In[253]:= MemberQ[rpn /@ t3, 38]
    Out[253]= False
    

    事实上,结果集的界限是:

    In[254]:= Max[rpn /@ t3]
    Out[254]= Max[36, ComplexInfinity]
    
    In[255]:= Min[rpn /@ t3]
    Out[255]= Min[-23, ComplexInfinity]
    

    无穷大的结果是由于我不关心除以零的事实,所以它们就在那里,就在集合内。数值区间为 [-23,36]。

    如果你想知道有多少个结果等于 24,就数一下

          In[259]:= Length@Select[t3, rpn[#] == 24 &]
          Out[259]= 484
    

    当然,由于“Plus”和“Times”的交换性质,它们中的许多是微不足道的排列,但不是全部:

       {1, 2, Plus, 3, Plus, 4, Times}      -> ((1+2)+3)*4  = 24
       {2, 1, 4, 3, Times, Divide, Divide}  ->  2/(1/(4*3)) = 24
    

    没有使用“减法”得到 24 的序列!

        In[260]:= MemberQ[Flatten@Select[t3, rpn[#] == 24 &], Subtract]
        Out[260]= False
    

    结果谱样本

    【讨论】:

    • @Manoj 希望大小不是它唯一的优点:)
    • 这是一种实现 RPN 评估的方法,它是 Mathematica 独有的,在我的机器上至少快 7 倍:rules = { a___, x_, y_, f_Symbol, b___ } :> {a, f[ x, y], b}; myrpn = 第一个 [# //.规则] &;
    • 或将 RPN 列表标准化为正常的 Mathematica 表达式:hold // ClearAll;保持 = # //. { a___, x_, y_, f_Symbol, b___ } :> HoldForm@{a, f[x, y], b} &;扁平化 = ko @@ # /。 {ko -> 推迟,列表 | HoldForm -> 序列} &; normalize = 合成[展平,保持]
    • @Cetin 这不是Mathematica 标签中的问题。我使用 Mma 作为工具,但尽量保持代码基本,以便没有 Mma 背景的人可以理解它。不管怎样,优化的便利性在答案中分析。
    猜你喜欢
    • 1970-01-01
    • 2017-10-14
    • 1970-01-01
    • 1970-01-01
    • 2017-06-28
    • 2020-05-19
    • 1970-01-01
    • 2015-08-09
    • 2017-12-12
    相关资源
    最近更新 更多