如何从数字子序列中恢复数字?

时间:2013-08-13 13:40:55

标签: algorithm math

给定四位数1234,有六个可能的两位数子序列(12,13,14,23,24,34)。鉴于一些子序列,是否有可能恢复原始数字?

这是一些示例数据。每行列出了不同6位数字(待定)的3个数字子序列

528, 508, 028, 502, 058, 528, 028, 528, 552, 050
163, 635, 635, 130, 163, 633, 130, 330, 635, 135
445, 444, 444, 444, 454, 444, 445, 
011, 350, 601, 651, 601, 511, 511, 360, 601, 351
102, 021, 102, 221, 102, 100, 002, 021, 021, 121
332, 111, 313, 311, 132, 113, 132, 111, 112
362, 650, 230, 172, 120, 165, 372, 202, 702
103, 038, 138, 150, 110, 518, 510, 538, 108
343, 231, 431, 341, 203, 203, 401, 303, 031, 233

编辑:有时候解决方案可能不是唯一的(多个数字可以给出子序列)。在这种情况下,最好还是返回其中一个,或者甚至是一个列表。

3 个答案:

答案 0 :(得分:3)

您要做的是找到所有子序列的Shortest common supersequence。显然,如果您有所有子序列,包括原始编号,SCS将是您正在寻找的。否则无法保证,但这是一个很好的机会。

不幸的是,对于这个问题没有一个很好的多项式算法,但是如果你谷歌它你会发现有很多可用的近似算法。例如。 An ACO Algorithm for the Shortest Common Supersequene Problem提到了三种总体方法:

  1. 动态编程或Branch'n'Bound。除了极少数字符串或小字母外,这些通常都会变慢。

  2. 使用动态编程成对地查找字符串的SCS,使用启发式方法选择要合并的字符串。

  3. 多数合并启发式可能是您案件中最好的启发式。

  4. 文章中描述的方法。

  5. 这是另一篇关于这个问题的好文章:http://www.update.uu.se/~shikaree/Westling/

答案 1 :(得分:3)

构建一个有向图,每个数字连接到每个序列后面的数字。

处理周期:

一个循环意味着一个不可能的场景 - 同一个角色不能有2个位置(可能有多个位置具有相同值的角色,但不是完全相同的角色 - 作为一个比喻,你可以让很多人命名为Bob,但是任何给定的鲍勃只能在一个地方)。某些节点必须拆分为多个节点。应该对所选节点进行拆分,使得所有传入边缘都位于其中一个新节点中,所有传出边缘位于另一个节点中,并且两者之间存在连接。

应该有多个节点可以被分割,可能只有一个是正确的,你可能需要探索所有可能性,直到找到一个有效的节点。如果一个不起作用,你将得到一个比在某个地方允许的更长的字符串。

最好在完成拓扑排序之前完全摆脱循环(以相同的方式解决它们)。

处理具有相同值的节点(作为循环解析的结果):

如果有多个节点具有可以选择的相同值,则让传出边缘从第一个节点(具有定向路径的节点到达所有其他节点)和传入边缘到最后一个节点(一个所有其他人都有一个有向的路径)。如果在同一序列中有多个具有相同值的数字,则显然需要略微修改。

查找实际字符串:

要确定字符串,请在图表上执行topological sort

示例:

假设我们正在寻找一个5位数字,输入是:

528, 508, 028, 502, 058, 058

我知道058的重复有些微不足道,但这仅仅是为了说明。

对于528,为528创建节点,并连接52以及28

5 -> 2 -> 8

对于508,请创建0,关联50以及08

5 -> 2 -> 8
  \    /
   > 0

对于028,请加入02

5 ------> 2 -> 8
  \    /    /
   > 0 -----

对于502,所有连接都已存在。

对于058,我们得到一个周期(5->0->5),因此我们有两个选择:

  • 0拆分为2个节点:

      /-----------\----\
     /             v    v
    0 -> 5 ------> 2 -> 8
           \
            > 0
    
  • 5拆分为2个节点:

      /-----------\
     /             v
    5 ------> 2 -> 5 -> 8
     \        ^    ^
      \       /    /
       > 0 --------
    

我们假设我们选择后者。

对于058,我们需要上一个5(本例中为右5)的传出边缘以及来自第一个5的传入边缘(左边)在这种情况下5)。这些边缘(5->05->8)已经存在,因此无需做任何事情。

拓扑排序将为我们提供50258,这是我们的号码。

答案 2 :(得分:2)

让逻辑编程为你完成工作。

这是通过core.logic中的

定义作为子序列的含义

(defne subseqo [s1 s2] 
  ([(h . t1) (h . t2)] (subseqo t1 t2)) 
  ([(h1 . t1) (h2 . t2)] (!= h1 h2) (subseqo s1 t2)) 
  ([() _]))

通过解算器运行约束。

(defn recover6 [input-string] 
  (run* [q] 
    (fresh [a b c d e f] 
      (== q [a b c d e f]) 
      (everyg (fn [s] (subseqo (seq s) q)) 
              (re-seq #"\d+" input-string)))))

示例(结果在REPL中感知瞬间):

(recover6 "528, 508, 028, 502, 058, 528, 028, 528, 552, 050")
;=> ([\5 \0 \5 \2 \8 \0]
     [\5 \0 \5 \2 \0 \8]
     [\5 \0 \5 \0 \2 \8]
     [\0 \5 \0 \5 \2 \8]
     [\0 \5 \5 \0 \2 \8])

(recover6 "163, 635, 635, 130, 163, 633, 130, 330, 635, 135")
;=> ([\1 \6 \3 \5 \3 \0] 
     [\1 \6 \3 \3 \5 \0] 
     [\1 \6 \3 \3 \0 \5])

(recover6 "445, 444, 444, 444, 454, 444, 445")
;=> ([\4 \4 \5 \4 _0 _1] 
     ... and many more

在最后一个示例中,下划线表示_0_1是自由变量。他们没有受到限制。很容易将任何自由变量约束到数字集。