给定一个字符串,找到两个相同的子序列,连续索引C ++

时间:2016-05-19 22:50:35

标签: c++ algorithm

我需要构建一个算法(不一定有效),给定一个字符串找到并打印两个相同的子序列(通过print我的意思是颜色)。此外,这两个子序列的索引集的并集必须是一组连续的自然数(整数整数)。

在数学方面,我所寻找的东西被称为“紧密的双胞胎”,如果有什么帮助的话。 (例如,见paper (PDF) here。)

我举几个例子:

1)考虑字符串231213231

我正在寻找以" 123"形式的两个子序列。要更好地查看此图片:

231213231

第一个子序列标有下划线,第二个子序列标有上划线。如你所见,他们拥有我需要的所有属性。

2)考虑字符串12341234

12341234

3)考虑字符串12132344。

现在它变得更复杂了:

enter image description here

4)考虑字符串:13412342

这也不是那么容易:

enter image description here

我认为这些例子很好地解释了我的意思。

我一直在思考一种可以做到这一点但却没有成功的算法。

为了着色,我想使用这段代码:

 using namespace std;
HANDLE  hConsole;
        hConsole = GetStdHandle(STD_OUTPUT_HANDLE);
    SetConsoleTextAttribute(hConsole, k);

其中k是颜色。

任何帮助,甚至提示,都将受到高度赞赏。

4 个答案:

答案 0 :(得分:2)

警告:Commenterגלעדברקן指出此算法为字符串1213213515提供错误的答案6(高于应尽可能!)。My implementation得到相同的错误答案,所以似乎这个算法是一个严重的问题。我会试着弄清问题是什么,但在此期间不要相信这个算法!

我已经想到了一个解决方案,它将采用 O(n ^ 3)时间和O(n ^ 2)空间,这应该可以在长达1000左右的字符串上使用。它基于对最常见子序列(LCS)的通常概念的调整。为简单起见,我将描述如何找到一个带有“紧双”属性的最小长度子串,该属性从输入字符串中的位置1开始,我假设其长度为2n;只需运行此算法2次,每次从输入字符串中的下一个位置开始。

“自我避免”常见子序列

如果length-2n输入字符串S具有“紧密双胞胎”(TT)属性,则它具有与其自身共同的子序列(或者等效地,S的两个副本具有共同的子序列):

  • 长度为n,
  • 遵守附加约束,即S的第一个副本中的任何字符位置都不会与第二个副本中的相同字符位置匹配。

事实上,我们可以安全地将后一种约束收紧到 S的第一个副本中没有任何字符位置与第二个副本中的相同或更低的字符位置相匹配,因为我们将以增加的长度顺序寻找TT子串,并且(如底部所示)在任何最小长度 TT子串中,总是可以将字符分配给两个子序列A和B,以便对于任何匹配的对(i,j)的子串中的位置,其中i <1。 j,位置i的字符被分配给A.让我们将这样一个共同的子序列称为自我避免公共子序列(SACS)。

使高效计算成为可能的关键是没有长度为2n的字符串的SACS可以有超过n个字符(因为很明显你不能将超过2组n个字符塞入长度为2n的字符串中),因此,如果存在这样的长度-n SACS,那么它必须具有最大可能的长度。 因此,要确定S是否为TT,只需在S与其自身之间寻找最大长度SACS,并检查其实际上是否具有长度n。

通过动态编程计算

让我们将f(i,j)定义为S的length-i前缀的最长自回避公共子序列的长度,其长度为j的前缀为S.要实际计算f(i,j),我们可以使用通常的LCS动态编程公式的一个小修改:

f(0, _) = 0
f(_, 0) = 0
f(i>0, j>0) = max(f(i-1, j), f(i, j-1), m(i, j))
m(i, j) = (if S[i] == S[j] && i < j then 1 else 0) + f(i-1, j-1)

如您所见,唯一的区别是附加条件&& i < j。与通常的LCS DP一样,计算它需要O(n ^ 2)时间,因为2个参数的范围在0和n之间,并且在递归步骤之外所需的计算是O(1)。 (实际上我们只需要计算这个DP矩阵的“上三角”,因为对角线以下的每个单元格(i,j)将由它上面的相应单元格(j,i)支配 - 尽管这不会改变渐近复杂性。)

为了确定字符串的长度为2j的前缀是否为TT,我们需要f(i,2j)的最大值超过所有0&lt; = i&lt; = 2n - 即列中的最大值DP矩阵的2j。通过记录到目前为止看到的最大值并且在计算列中的每个DP单元时根据需要更新,可以在每个DP单元的O(1)时间内计算该最大值。按j从j = 1到j = 2n的递增顺序进行,让我们一次填充一列DP矩阵,总是在较长的前一个处理S的较短前缀,这样在处理第2j列时我们可以安全地假设不短前缀是TT(因为如果有的话,我们会早些发现它并且已经终止了。)

答案 1 :(得分:2)

这是一个简单的递归,测试紧身双胞胎。当有重复时,它会拆分决策树,以防副本仍然是第一个双胞胎的一部分。你必须在偶数长度的每个子串上运行它。对较长子串的其他优化可能包括对char计数进行散列测试,以及匹配候选双胞胎的非重复部分(仅在整个子字符串中出现两次的字符)。

功能说明:

首先,创建一个散列,每个字符作为键,索引以值的形式出现。然后我们遍历哈希:如果字符计数是奇数,则该函数返回false;并且计数大于2的字符的索引被添加到重复列表中 - 其中一半属于一个双胞胎,但我们不知道哪个。

递归的基本规则是,只有在字符串后面找到匹配项时才增加i,同时保留所选匹配项{(1}})js的记录必须跳过而不寻找匹配。这是有效的,因为如果我们发现i匹配,按顺序,到时间n/2到达结尾时,这基本上只是另一种说法,即字符串是由双胞胎组成的。

JavaScript代码:

j

答案 2 :(得分:1)

让字符串长度为N。

有两种方法。

方法1.这种方法总是指数时间。

对于长度为1..N / 2的每个可能的子序列,列出此子序列的所有出现。对于每次出现,列出所有字符的位置。

例如,对于123123,它应该是:

(1, ((1), (4)))
(2, ((2), (5)))
(3, ((3), (6)))
(12, ((1,2), (4,5)))
(13, ((1,3), (4,6)))
(23, ((2,3), (5,6)))
(123, ((1,2,3),(4,5,6)))
(231, ((2,3,4)))
(312, ((3,4,5)))

后两者不是必需的,因为它们只出现一次。

  • 一种方法是从长度为1的子序列开始(即字符),然后进入长度为2的子序列,等等。在每一步,删除所有只出现一次的子序列,因为你不要需要它们。
  • 另一种方法是检查长度为N的所有2 ** N个二进制字符串。每当二进制字符串不超过N / 2&#34; 1&#34;数字,将其添加到表中。最后删除所有仅出现一次的子序列。

现在您有一个显示超过1次的子序列列表。对于每个子序列,检查所有对,并检查这样的对是否形成紧密的双胞胎。

方法2.更直接地寻找紧身双胞胎。对于每个N *(N-1)/ 2个子串,检查子串是否是偶数长度,并且每个字符在其中出现偶数次,然后,作为其长度L,检查它是否包含两个长度紧密的双胞胎L / 2。有两种方法可以分割它,最简单的方法就是检查它们。但是,有更有趣的方法来寻找t.t.

答案 3 :(得分:1)

我想将此作为动态编程/模式匹配问题来解决。我们一次一个地处理字符,从左到右,我们维持一群非确定性有限自动机/ NDFA,它们对应于部分匹配。我们从一个空匹配开始,每个字符我们以各种可能的方式扩展每个NDFA,每个NDFA可能会产生很多孩子,然后重复删除结果 - 所以我们需要最小化NDFA限制了牛群的大小。

我认为NDFA需要记住以下内容:

1)它在匹配区域之前跳过了一段k个字符。

2)后缀是一个p字符串,表示尚未匹配的字符,需要通过上线匹配。

我认为你总是可以假设p字符串需要与上线匹配,因为如果你在整个答案中交换,你总是可以在答案中交换上划线和下划线。

当您看到新角色时,您可以通过以下方式扩展NDFA:

a)除了跳过之外没有任何内容的NDFA可以添加跳过。

b)NDFA始终可以将新字符添加到其后缀中,该后缀可能为null

c)具有第一个字符与新字符匹配的p字符串的NDFA可以变成具有p-1字符串的NDFA,该字符串由旧后缀的最后一个p-1字符组成。如果字符串现在为零长度,那么您已找到匹配项,并且如果您将每个NDFA的链接保持为其父级,则可以计算出它是什么。

我以为我可以使用一个整体编码来保证只有一个多项式的群体大小,但我无法做到这一点,我无法在这里证明多项式行为,但我注意到一些退化行为的情况得到了处理合理地,因为它们导致多种方式达到相同的后缀。