繁体   English   中英

包含集合中出现次数最多的字符串的最短字符串

[英]Shortest string containing the most occurrence of strings in a set

定义一个字符串 M 的度数是它在另一个字符串 S 中出现的次数。例如 M = "aba" 和 S="ababa",M 的度数是 2。给定一组字符串和一个整数 N,找到最小长度的字符串,使得集合中所有字符串的度数之和至少为 N。

例如一个集合 {"ab", "bd", "abd" "babd", "abc"}, N = 4, 答案将是 "babd"。 它一次包含“ab”、“abd”、“babd”和“bd”。

N <= 100,M <= 100,集合中每个字符串的长度<= 100。集合中的字符串仅由大写和小写字母组成。

如何解决这个问题呢? 这看起来类似于最短超弦问题,它具有具有指数复杂度的动态规划解决方案。 然而,这个问题的约束要大得多,同样的想法在这里也行不通。 是否有一些字符串数据结构可以应用在这里?

我有一个多项式时间算法,我懒得编写代码。 但我会为你描述。

首先,使集合中的每个字符串加上空字符串成为图的节点。 空字符串相互连接,反之亦然。 如果一个字符串的结尾与另一个字符串的开头重叠,它们也会连接。 如果两个可以以不同的量重叠,则它们会得到多个边缘。 (所以它不完全是一个图表......)

每条边都有一个cost和一个value 成本是您必须扩展正在构建的字符串以从旧端移动到新端的字符数。 (换句话说,第二个字符串的长度减去重叠的长度。)有这个。 是您完成的跨越前一个和后一个字符串之间障碍的新字符串的数量。

你的例子是 {"ab", "bd", "abd" "babd", "abc"}。 这是每个转换的(cost, value)对。

 from  ->   to  : (value, cost)
 ""    ->   "ab": (    1,    2)
 ""    ->   "bd": (    1,    2)
 ""    ->  "abd": (    3,    3) # we added "ab", "bd" and "abd"
 ""    -> "babd": (    4,    4) # we get "ab", "bd", "abd" and "babd"
 ""    ->  "abc": (    2,    3) # we get "ab" and "abc"
 "ab"  ->     "": (    0,    0)
 "ab"  ->   "bd": (    2,    1) # we added "abd" and "bd" for 1 character
 "ab"  ->  "abd": (    2,    1) # ditto
 "ab"  ->  "abc": (    1,    1) # we only added "abc"
 "bd"  ->     "": (    0,    0) # only empty, nothing else starts "bd"
"abd"  ->     "": (    0,    0) 

"babd" -> "": ( 0, 0) "babd" -> "abd": ( 0, 0) # 重叠,但没有添加任何内容。 "abc" -> "": ( 0, 0)

好的,所有这些都设置好了。 我们为什么要这个图?

请注意,如果我们从成本为 0 且值为 0 的 "" 开始,则在图中选择一条路径,即构造一个字符串。 它正确说明了成本,并提供了价值的下限。 该值可以更高。 例如,如果你的集合是 {"ab", "bc", "cd", "abcd"} 那么路径 "" -> "ab" -> "bc" -> "cd" 将导致字符串 "abcd" " 成本为 4,预测值为 3。但该值估计忽略了我们匹配 "abcd" 的事实。

然而,对于仅由集合中的子串组成的任何给定字符串,有一条通过图的路径具有正确的成本和正确的值。 (在每次选择时,您都希望选择尚未计数的最早的起始匹配字符串,并从中选择最长的字符串。这样您就不会错过任何匹配项。)

所以我们已经将我们的问题从构造字符串转变为构造通过图形的路径。 我们要做的是建立以下数据结构:

for each (value, node) combination:
    (best cost, previous node, previous value)

填充该数据结构是一个动态规划问题。 填写后,我们可以回溯它以找到图中的哪条路径以该成本使我们达到该值。 给定那个路径,我们可以找出执行它的字符串。

它有多快? 如果我们的集合有K字符串,那么我们只需要填写K * N值,每个值我们最多可以为新值提供K候选值。 这使得路径寻找成为O(K^2 * N)问题。

所以这是我的方法。 在第一次迭代时,我们从初始字符串中构造一个池。 在那之后:

  1. 我们从池中选择一个具有最小长度和度数总和 = N 的字符串。 如果我们找到这样的字符串,我们就返回它。
  2. 我们从池中过滤出度数小于最大值的所有字符串。 我们只使用最好的字符串组合。
  3. 我们从当前池和初始字符串中构建所有变体。 这里我们需要考虑到字符串可以重叠。 假设字符串“aba”和“ab”(来自初始字符串)可以产生:ababa、abab、abaab(我们不包括“aba”,因为我们的池中已经有了它,我们需要进一步移动)。
  4. 我们过滤掉重复项,这是我们的下一个池。
  5. 从第 1 点开始重复所有内容。

FindTarget() 方法接受目标总和作为参数。 FindTarget(4) 将解决示例任务。

public class Solution
{
    /// <summary>
    /// The initial strings.
    /// </summary>
    string[] stringsSet;
    Tuple<string, string>[][] splits;
    public Solution(string[] strings)
    {
        stringsSet = strings;
        splits = stringsSet.Select(s => ProduceItemSplits(s)).ToArray();
    }

    /// <summary>
    /// Find the optimal string.
    /// </summary>
    /// <param name="N">Target degree.</param>
    /// <returns></returns>
    public string FindTarget(int N)
    {
        var pool = stringsSet;
        while (true)
        {
            var poolWithDegree = pool.Select(s => new { str = s, degree = GetN(s) })
                .ToArray();
            var maxDegree = poolWithDegree.Max(m => m.degree);
            var optimalString = poolWithDegree
                .Where(w => w.degree >= N)
                .OrderBy(od => od.str.Length)
                .FirstOrDefault();
            if (optimalString != null) return optimalString.str; // We found it
            var nextPool = poolWithDegree.Where(w => w.degree == maxDegree)
                .SelectMany(sm => ExpandString(sm.str))
                .Distinct()
                .ToArray();
            pool = nextPool;
        }
    }
    /// <summary>
    /// Get degree.
    /// </summary>
    /// <param name="candidate"></param>
    /// <returns></returns>
    public int GetN(string candidate)
    {

        var N = stringsSet.Select(s =>
        {
            var c = Regex.Matches(candidate, s).Count();
            return c;
        }).Sum();
        return N;
    }

    public Tuple<string, string>[] ProduceItemSplits(string item)
    {
        var substings = Enumerable.Range(0, item.Length + 1)
            .Select((i) => new Tuple<string, string>(item.Substring(0, i), item.Substring(i, item.Length - i))).ToArray();
        return substings;
    }

    private IEnumerable<string> ExpandStringWithOneItem(string str, int index)
    {
        var item = stringsSet[index];
        var itemSplits = splits[index];
        var startAttachments = itemSplits.Where(w => str.StartsWith(w.Item2) && w.Item1.Length > 0)
            .Select(s => s.Item1 + str);
        var endAttachments = itemSplits.Where(w => str.EndsWith(w.Item1) && w.Item2.Length > 0)
            .Select(s => str + s.Item2);
        return startAttachments.Union(endAttachments);
    }

    public IEnumerable<string> ExpandString(string str)
    {
        var r = Enumerable.Range(0, splits.Length - 1)
                .Select(s => ExpandStringWithOneItem(str, s))
                .SelectMany(s => s);
        return r;
    }
}

static void Main(string[] args)
{
    var solution = new Solution(new string[] { "ab", "bd", "abd", "babd", "abc" });
    var s = solution.FindTarget(150);
    Console.WriteLine(s);
}

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM