如何确定字符串是否是字符串列表的串联

时间:2013-07-18 19:08:55

标签: string algorithm

假设给出了一个字符串S,以及一些其他字符串L的列表。

我们如何知道S是否是L的所有可能连接之一?

例如:

S =“abcdabce”

L = [“abcd”,“a”,“bc”,“e”]

S是“abcd”+“a”+“bc”+“e”,则S是L的串联,而“ababcecd”则不是。

为了解决这个问题,我尝试使用DFS /回溯。伪代码如下:

boolean isConcatenation(S, L) {
    if (L.length == 1 && S == L[0]) return true;
    for (String s: L) {
        if (S.startwith(s)) {
            markAsVisited(s);
            if (isConcatnation(S.exclude(s), L.exclude(s))) 
                return true;
            markAsUnvisited(s);
        }
    }
    return false;
}

但是,DFS /回溯不是一种有效的解决方案。我很好奇什么是解决这个问题的最快算法,或者是否有任何其他算法以更快的方式解决它。我希望有像KMP这样的算法可以在O(n)时间内解决它。

7 个答案:

答案 0 :(得分:3)

在python中:

>>> yes = 'abcdabce'
>>> no = 'ababcecd'
>>> L = ['abcd','a','bc','e']
>>> yes in [''.join(p) for p in itertools.permutations(L)]
True
>>> no in [''.join(p) for p in itertools.permutations(L)]
False

编辑:正如所指出的,这是n!复杂,所以不适合大L.但嘿,开发时间不到10秒。

您可以从基本置换器开始构建自己的置换生成器:

def all_perms(elements):
    if len(elements) <=1:
        yield elements
    else:
        for perm in all_perms(elements[1:]):
            for i in range(len(elements)):
                yield perm[:i] + elements[0:1] + perm[i:]

然后通过跟踪元素串联的内容来丢弃您不关心的分支,只有在它累加到目标字符串时才进行迭代。

    def all_perms(elements, conc=''):
    ...
            for perm in all_perms(elements[1:], conc + elements[0]):
    ...
                if target.startswith(''.join(conc)):
    ...

答案 1 :(得分:2)

动态编程方法是从左到右工作,构建一个数组A [x],如果字符串的前x个字符形成L的可能连接之一,则A [x]为真。你可以工作通过检查列表中的每个可能的字符串,给出前面的A [n] A [n] - 如果S到第n个字符的字符匹配长度为k的候选字符串,如果A [nk]为真,则可以设置A [n]是真的。

我注意到你可以使用https://en.wikipedia.org/wiki/Aho%E2%80%93Corasick_string_matching_algorithm找到你需要的匹配作为动态程序的输入 - 匹配成本将是输入字符串大小的线性,所有候选字符串的总大小,以及输入字符串和候选字符串之间的匹配数。

答案 2 :(得分:0)

我会尝试以下方法:

  • S
  • 中查找 L_i 模式的所有位置
  • 设n =长度(S)+1
  • 使用 n 节点
  • 创建图表
  • 适用于所有 L_i 位置 i :有向边: node_i - &gt; L_i匹配节点 - &gt;的 node_ {I +长度(L_I)}
  • 启用排列约束,您必须添加更多节点/边缘以排除多次使用相同模式
  • 现在我可以问一个新问题:是否存在从 0 n 的有向路径?

注释:

  • 如果存在度数<2的节点( 0&lt; i&lt; n ),则无法匹配
  • 所有具有d- = 1,d + = 1的节点都是置换的一部分
  • 面包首先或diskstra寻找解决方案

答案 3 :(得分:0)

您可以使用Trie数据结构。首先,从L中的字符串构造一个trie。

然后,对于输入字符串S,在trie中搜索S.

在搜索期间,对于每个被访问的节点,它是L中一个单词的结尾,在trie(从根)上调用一个新的搜索,其中剩余(但不匹配)的后缀为S.所以,我们使用递归。如果您在该过程中使用了S的所有字符,那么您知道,S是来自L的某些字符串的连接。

答案 4 :(得分:0)

我会建议这个解决方案:

  1. 取一个大小为256的数组,它将存储L的所有字符串中每个字符的出现次数。现在尝试将其与S的每个字符的计数相匹配。如果两者都不相等,那么我们可以自信地说它们不能形成给定的角色。
  2. 如果计数相同,请执行以下操作,使用KMP算法尝试同时查找S中L中的每个字符串。如果在任何时候匹配,我们从L中删除该字符串并继续搜索L中的其他字符串。在任何时候我们都没有找到匹配我们只是打印它无法表示。如果在最后L是空的,我们得出结论S确实是L的串联。
  3. 假设L是一组唯一的字符串。

答案 5 :(得分:0)

两个Haskell命题:

可能有一些反例......只是为了好玩......按自定义排序L:

import Data.List (sortBy,isInfixOf)

h s l = (concat . sortBy wierd $ l) == s where
  wierd a b | isInfixOf (a ++ b) s = LT
            | isInfixOf (b ++ a) s = GT
            | otherwise            = EQ

更无聊......试图从L建立S:

import Data.List (delete,isPrefixOf)

f s l = g s l [] where
  g str subs result
    | concat result == s = [result]
    | otherwise   = 
        if null str || null subs' 
           then []
           else do sub <- subs'
                   g (drop (length sub) str) (delete sub subs) (result ++ [sub])
   where subs' = filter (flip isPrefixOf str) subs 

输出:

*Main> f "abcdabce" ["abcd", "a", "bc", "e", "abc"]
[["abcd","a","bc","e"],["abcd","abc","e"]]

*Main> h "abcdabce" ["abcd", "a", "bc", "e", "abc"]
False

*Main> h "abcdabce" ["abcd", "a", "bc", "e"]
True 

答案 6 :(得分:0)

您的算法具有复杂度N ^ 2(N是列表的长度)。让我们看看实际的C ++

#include <string>
#include <vector>
#include <algorithm>
#include <iostream>
using namespace std;

typedef pair<string::const_iterator, string::const_iterator> stringp;
typedef vector<string> strings;

bool isConcatenation(stringp S, const strings L) {
    for (strings::const_iterator p = L.begin(); p != L.end(); ++p) {
        auto M = mismatch(p->begin(), p->end(), S.first);
        if (M.first == p->end()) {
            if (L.size() == 1)
                return true;
            strings T;
            T.insert(T.end(), L.begin(), p);
            strings::const_iterator v = p;
            T.insert(T.end(), ++v, L.end());
            if (isConcatenation(make_pair(M.second, S.second), T))
                return true;
        }
    }
    return false;
}

我们可以对整个向量进行循环,然后在最佳情况下将搜索减少到O(LOG(N))步骤,而不是循环整个向量,其中所有字符串都以不同的字符开头。最坏的情况将是O(N ^ 2)。