【问题标题】:Shortest string containing the most occurrence of strings in a set包含集合中出现次数最多的字符串的最短字符串
【发布时间】:2020-12-23 20:25:10
【问题描述】:

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

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

N

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

【问题讨论】:

    标签: string algorithm data-structures dynamic-programming


    【解决方案1】:

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

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

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

    您的示例是 {"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) 问题。

    【讨论】:

    • “你还没有统计的最早的起始匹配字符串”是什么意思?
    • @LearningMathematics 我们有一组字符串,如“abc”、“bcd”和“cde”。我们正在查看像“abcdef”这样的组合字符串。在“abc”,我们还没有计算“bcd”或“cde”,因为它们超出了我们所研究的范围。但其中,“bcd”在“abc”中更早开始,所以这是我们想要的下一个。因为我们现在可以得到它并在以后计算“cde”,而如果我们跳转到“cde”,我们会错过“bcd”上的匹配。
    • 你为什么要选择最长的?这是否意味着在“”处,你总是去最长的字符串?还是有别的意思?
    • @LearningMathematics 因为如果你在那个时候选择一个较短的,那么你永远不会计算较长的。例如,如果我们的集合是 {"abc", "bcd", "bcde" and "cde"}。如果我们通过图表的路径是“abc”->“bcd”->“cde”,那么我们不计算“bcde”。
    • 对不起,迟到的评论,但我有几个问题:您提到“(在每个选择中,您想选择您尚未计算的最早的起始匹配字符串,并且选择最长的他们。那么你永远不会错过任何比赛。)“。这不是我必须实施的事情,而只是对先前声明的解释吗?
    【解决方案2】:

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

    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);
    }
    

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2011-04-03
      • 2021-03-19
      • 1970-01-01
      • 2019-07-29
      • 2021-10-08
      • 1970-01-01
      • 2020-02-21
      相关资源
      最近更新 更多