【问题标题】:largest possible rectangle of letters最大可能的字母矩形
【发布时间】:2012-01-20 16:21:35
【问题描述】:

编写一个程序,找出最大可能的字母矩形,使每一行构成一个单词(从左到右),每列构成一个单词(从上到下)。

我发现了这个有趣的问题。这不是家庭作业,尽管听起来可能是这样。 (我不在学校)。我这样做是为了好玩。

示例

来自 catcarapeapirep、tip 我们得到以下矩形(正方形):

c a r
a p e
t i p

我最初的想法是构建某种前缀树,这样我就可以检索所有以特定字符串开头的单词。当我们已经有 2 个或更多单词(从上到下或从左到右阅读)并且我们需要找到下一个要添加的单词时,这将很有用。

还有其他想法吗?

编辑

这可以用长方体(3D 矩形)来完成吗?

如果它需要在对角线上有有效的单词怎么办(创意来源:user645466);如何优化它的算法?

【问题讨论】:

  • 这听起来像是一个很棒的拼字游戏笑话。
  • 您将此问题标记为 NP-hard;这是一个已知的 NP 难题,还是只是您的猜测?
  • @Adrian:另见香蕉图。
  • 可能是NP难是为了吸引人群。 ;)
  • @templatetypedef A reduction from 3SAT 由评论者“d”提出。我个人认为使用精确覆盖作为源问题会更容易。

标签: algorithm optimization np-hard


【解决方案1】:

为相同长度的单词创建一个 Bag[] = index 然后创建 一个 Trie 数组,每个长度的 wordList 一个 Trie

   Rectangle makeRectangle(length, height, rectangle)
    {
        if ( length == rectangle.length()) check if complete and return;
        checkIfPartialComplete - check columns for valid prefixes
        for ( i from 1 to grouplist[i-1])
        {
            newRectangle = append word[i] to rectangle 
            return makeRectangle(l,h, rectangle with appended word) if not equal to null
        }
    }


boolean checkPartial(int l, Trie trie)
{
    for ( int i =0 ; i < l; i++)
    {
        String col = getColumn(i);
        if (!trie.contains(col))
        {
            return false;
        }
    }
    return true;
}
boolean checkComplete(int l, Bag<String> lengthGroup)
{
    for ( int i=0; i < l ; i++)
    {
        String col = getColumn(i);
        if (!lengthGroup.contains(col))
        {
            return false;
        }
    }
    return true;
}

【讨论】:

  • ^取自 CCI 书籍
【解决方案2】:

给定长度的单词字典,创建两个新字典:第一个包含单词的所有单字母前缀(所有可以是给定长度单词的第一个字母的字母),第二个包含所有单词的双字母前缀(两个字母的所有序列,可以是给定长度的单词的前两个字母)。您也可以使用三重前缀,但您可能不需要超出此范围。

  1. 从字典中选择一个单词,命名为X。这将是矩阵的第一行。

  2. 使用您制作的方便列表检查X[1], X[2], ..., X[N] 是否都是有效的单字母前缀。如果是,请继续第 3 步;否则返回步骤 1。

  3. 从字典中选择一个单词,命名为Y。这将是矩阵的第二行。

  4. 使用您制作的方便列表检查X[1] Y[1]X[2] Y[2]、...、X[N] Y[N] 是否都是有效的双字母前缀。如果是,继续第 5 步;否则返回第 3 步。如果这是字典中的最后一个单词,则一直返回第 1 步。

    ...

    2(N-1)。从字典中选择一个单词,命名为Z。这将是矩阵的第 N 行。

    2N。检查X[1] Y[1] ... Z[1]X[2] Y[2] ... Z[2]、...、X[N] Y[N] ... Z[N] 是否都是字典中的单词。如果是的话,恭喜你,你做到了!否则返回步骤 2(N-1)。如果这是字典中的最后一个单词,则一直回到步骤 2(n-3)。

逻辑是一次建立一行单词的矩形,为行选择单词,然后检查列可以完成单词。这将比一次添加一个字母快得多。

【讨论】:

  • 您是否使此算法特定于我的 3 by 3 示例?对不起,如果我误导了,但我正在寻找一个 n x m 矩形。我问的原因是因为我看到一个单字母前缀列表和一个双字母前缀列表,这对 3 x 3 矩形有意义。不过,我确实喜欢这种推理;可以概括。
  • @Adrian 它概括地简单地找到了一个 nxn 解决方案。
  • 你能解释一下单字母和双字母前缀是什么吗?我可能理解不正确。
  • @Adrian 我针对一般情况对其进行了修改并添加了前缀列表的解释(例如,您示例的单字母前缀是A,C,R,T,双字母前缀是AP,CA,RE,TI)。
【解决方案3】:

令 D 为字典。固定 m 和 n。我们可以将寻找一个 m × n 矩形的问题表述为constraint satisfaction problem (CSP)。

xi,1…xi,n ∈ D    ∀i ∈ {1, …, m}
x1,j…xm,j ∈ D    ∀j ∈ {1, …, n}
xi,j ∈ {A, …, Z}    ∀i ∈ {1, …, m}, ∀j ∈ {1, …, n}

解决 CSP 的一种非常常见的方法是将回溯与约束传播相结合。粗略地说,回溯意味着我们选择一个变量,猜测它的值,然后用更少的变量递归地解决子问题,而约束传播意味着试图减少每个变量的可能性数量(可能为零,这意味着没有解决方案)。

例如,我们可以通过选择 x1,1 = Q 来开始一个 3 × 3 的网格。

Q??
???
???

使用英语词典,x1,2 和 x2,1 的唯一可能是U(在拼字游戏“单词”之前)。

解决 CSP 的艺术是在回溯和约束传播之间取得平衡。如果我们根本不传播约束,那么我们只是在使用蛮力。如果我们完美地传播约束,那么我们就不需要回溯,但是一个能够自行解决 NP-hard 问题的传播算法运行起来可能相当昂贵。

在这个问题中,除非我们有良好的数据结构支持,否则在每个回溯节点处理一个大字典会变得很昂贵。我将概述一种使用trieDAWG 快速计算前缀延伸到完整单词的字母集的方法。

在每个回溯节点,我们分配的变量集是一个 Young 表。换句话说,在它上面和左边的变量被赋值之前,没有变量被赋值。在下图中,. 表示已分配变量,*? 表示未分配变量。

.....*
...*??
...*??
..*???
*?????

标记为* 的变量是下一个被赋值的候选变量。有多个选择而不是每次都选择一个固定变量的好处是,一些变量的排序可能比其他的要好得多。

对于每个*,在 trie/DAWG 中进行两次查找,一次查找水平方向,一次查找垂直方向,并计算接下来可能出现的字母集的交集。一种经典的策略是选择可能性最小的变量,希望我们更快地达到矛盾。我喜欢这种策略,因为当变量的可能性为零时,它会自然地修剪,而当变量的可能性为零时,它会自然地传播。通过缓存结果,我们可以非常快速地评估每个节点。

【讨论】:

  • 如果我理解正确,通过“缓存结果”,您的意思是矩形中每个位置的可能字母列表,以及可能对应于 2 个位置的前缀树中的节点:立即到左上方。每当您回溯并更改先前的决定时,该缓存都应该失效;所以它不如你永远不会为相同的参数重新计算的记忆功能强大。无论如何,通过这个实现,我仍然在运行时获得了极快的增长,只有 16,000 个单词需要将近一个小时,似乎对数百万个单词没有希望。
猜你喜欢
  • 1970-01-01
  • 2013-03-05
  • 2018-08-26
  • 2021-08-26
  • 2010-09-05
  • 2011-10-18
  • 1970-01-01
  • 2010-11-25
  • 1970-01-01
相关资源
最近更新 更多