【问题标题】:Searching a minimal string meeting some conditions搜索满足某些条件的最小字符串
【发布时间】:2016-08-22 06:48:53
【问题描述】:

最近,我在一次采访中被问到以下问题。

给定一个字符串 S,我需要找到另一个字符串 S2,使得 S2 是 S 的子序列,并且 S 也是 S2+reverse(S2) 的子序列。这里的“+”表示串联。对于给定的 S,我需要输出 S2 的最小可能长度。

有人告诉我这是一个动态编程问题,但是我无法解决它。有人可以帮我解决这个问题吗?

编辑-

有没有办法在 O(N2) 或更短的时间内做到这一点。

【问题讨论】:

  • O(n^3) 解决方案是否可以接受?
  • 不,我需要一个比 O(n^3) 更好的解决方案。

标签: string dynamic-programming subsequence


【解决方案1】:

这个问题有两个重要方面。

  1. 因为我们需要 S 作为 S2+reverse(S2) 的子串,所以 S2 应该有 至少 n/2 长度。
  2. 连接 S2 和反向 (S2) 后,有一个模式,其中 字母重复,例如

所以解决方案是从 S 的中心到 S 的末尾检查任何连续的元素。如果你找到了,请检查任一侧的元素,如图所示。

现在,如果您能够到达字符串的末尾,那么最小元素数(结果)就是从开始到找到连续元素的点的距离。在这个例子中,它的 C 即 3。

我们知道这可能不会总是发生。即您可能无法在中心找到连续的元素。假设连续元素在中心之后,那么我们可以做同样的测试。

主字符串

子字符串

拼接字符串

现在主要的疑问出现了。为什么我们只考虑从中心开始的左侧?答案很简单,串联的字符串由 S+reverse(S) 组成。所以我们确信子字符串中的最后一个元素在连接的字符串中是连续的。主字符串前半部分的任何重复都无法产生更好的结果,因为至少我们应该在最终连接的字符串中包含 n 个字母

现在是复杂性的问题: 搜索连续字母最多给出 O(n) 现在迭代地检查任一侧的元素可以给出 O(n) 的最坏情况复杂度。即最大 n/2 比较。 我们可能会在第二次检查中失败很多次,因此我们在复杂性之间存在乘法关系,即 O(n*n)。

我相信这是一个正确的解决方案,还没有发现任何漏洞。

【解决方案2】:

假设 S2 是“苹果”。那么我们可以做出这样的假设:

S2 + reverseS2 >= S >= S2
“appleelppa” >= S >= “苹果”

所以给定的 S 将包含“apple”到不超过“appleelppe”的内容。它可以是“appleel”或“appleelpp”。

String S ="locomotiffitomoc";
// as you see S2 string is "locomotif" but 
// we don't know S2 yet, so it's blank
String S2 = "";
for (int a=0; a<S.length(); a++) {
    try {
        int b = 0;
        while (S.charAt(a - b) == S.charAt(a + b + 1))
            b++;
        // if this for loop breaks that means that there is a character that doesn't match the rule
        // if for loop doesn't break but throws an exception we found it.
    } catch (Exception e) {
        // if StringOutOfBoundsException is thrown this means end of the string.
        // you can check this manually of course.
        S2 = S.substring(0,a+1);
        break;
    }
}
System.out.println(S2); // will print out "locomotif"

恭喜,您找到了最小 S2。

【讨论】:

  • 子串≠子序列
【解决方案3】:

S 中的每个字符都可以包含在 S2 中,也可以不包含在 S2 中。有了它,我们可以构建尝试两种情况的递归:

  • S 的第一个字符用作封面,
  • S 的第一个字符不是 用于掩护,

并计算这两个封面的最小值。为了实现这一点,跟踪已经选择的 S2+reverse(S2) 覆盖了多少 S 就足够了。

有一些优化,我们知道结果是什么(找到掩体,不能有掩体),如果它不会覆盖某些东西,则不需要将第一个字符作为掩体。

简单的python实现:

cache = {}

def S2(S, to_cover):
    if not to_cover:  # Covered
        return ''
    if not S:         # Not covered
        return None
    if len(to_cover) > 2*len(S):  # Can't cover
        return None
    key = (S, to_cover)
    if key not in cache:
        without_char = S2(S[1:], to_cover)     # Calculate with first character skipped
        cache[key] = without_char
        _f = to_cover[0] == S[0]
        _l = to_cover[-1] == S[0]
        if _f or _l:
            # Calculate with first character used
            with_char = S2(S[1:], to_cover[int(_f):len(to_cover)-int(_l)])
            if with_char is not None:
                with_char = S[0] + with_char  # Append char to result
                if without_char is None or len(with_char) <= len(without_char):
                    cache[key] = with_char
    return cache[key]

s = '21211233123123213213131212122111312113221122132121221212321212112121321212121132'
c = S2(s, s)
print len(s), s
print len(c), c

【讨论】:

  • 安特 - 我无法理解您的解决方案。您是否将字符串作为 DP 的参数传递?你的DP状态是什么?抱歉,我从来没有使用过 python,所以这段代码让我很困惑。
  • @LTim 两个方法参数都是字符串。第一个参数是初始字符串 S 的“尾部”,从中选择 S2 的其余部分。第二个参数是 S2+reverse(S2) 覆盖的 S 的剩余部分。如果找到,方法返回字符串 S2。如果找不到 S2,则方法返回 None。
  • 您的解决方案似乎效率低下,有没有办法在 O(N^2) 或更短的时间内做到这一点。我不需要字符串,只需要最小长度。
  • @LTim 要获得封面长度,请将行 'with_char = S[0] + with_char' 更改为 'with_char = 1 + with_char'。缓存中元素的最大数量是 N^3 阶,因为参数 S 可以有 N 个不同的值和 to_cover N*(N-1)/2 个值。我认为实际数字要小得多,因为调用顺序和 to_cover 的结构,大约 N^2。缓存可以被索引以获得持续访问。有了这个,我认为运行时间是 O(N^2),其中 N 是 S 长度。上例在我的笔记本电脑上运行约 23 毫秒。如果字符串的不同字符比重复次数少,它运行得更快。
  • 嗨@Ante:在abacabca 的情况下,您的解决方案似乎产生了错误的结果。它返回abcabca,但最小的解决方案是abacab
猜你喜欢
  • 2017-02-28
  • 1970-01-01
  • 1970-01-01
  • 2020-01-05
  • 2022-10-24
  • 1970-01-01
  • 2020-12-06
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多