导致给定后缀数组的最小字符数

时间:2014-09-04 23:25:39

标签: java algorithm dynamic-programming suffix-array

给定suffix array,来自SRM 630的TopCoder任务要求查找字符串中可能形成具有给定后缀数组的字符串的不同字符的最小数量。 full problem statement can be found on the TopCoder website

我找到的最佳解决方案就在这里:https://github.com/ftiasch/acm-icpc/blob/6db1ed02a727611830b974a1d4de38bab8f390f9/topcoder/single-round-match/single-round-match-630/SuffixArrayDiv1.java

这是ftiasch编写的算法:

public int minimalCharacters(int[] array) {
    int n = array.length;
    int[] position = new int[n + 1];
    for (int i = 0; i < n; ++i) {
        position[array[i]] = i;
    }
    position[n] = -1;
    int[] minimum = new int[n + 1];
    for (int i = n - 1; i >= 0; --i) {
        minimum[i] = Integer.MAX_VALUE;
        for (int j = i + 1; j <= n; ++j) {
            boolean valid = true;
            for (int x = i; x < j; ++x) {
                for (int y = x + 1; y < j; ++y) {
                    valid &= position[array[x] + 1] < position[array[y] + 1];
                }
            }
            if (valid && minimum[j] < Integer.MAX_VALUE) {
                minimum[i] = Math.min(minimum[i], minimum[j] + 1);
            }
        }
    }
    return minimum[0];
}

我知道这是一个动态编程算法,但它是如何工作的?我真的需要一只手来理解它。

修改

以下是ftiasch写回来的内容:

  

嗨Ariel,

     

首先,感谢您的赞美。坦率地说,我的解决方案   不是问题的最佳解决方案。最优的一个在O(n)中运行   时间,但我的需要O(n ^ 4)。我刚刚在比赛中选择了这个想法   因为n相对较小。

     

请记住,相同的字符在SA中会变得连续。以来   问题要求的字符数最少,所以我决定   使用动态编程将SA划分为连续的段   这样每个段都以相同的字符开头。

     

S [SA [i]] = = S [SA [j]]需要哪个条件,假设i <1。   J&prime;词典比较表明后缀(SA [i] + 1)应该   小于后缀(SA [j] + 1)。我们可以很容易地找到了   条件也足够了。

     

如果您有任何其他问题,请写信给我。 :)

EDIT1

感谢大卫,我们终于成功了。以下是David的Python版本中的线性时间算法:

public int minimalCharacters(int[] array) {
    int n = array.length, i;
    if (n == 0)
        return 0;
    int[] array1 = new int[n + 1];
    for (i = 0; i < n; i++)
        array1[1 + i] = array[i];
    int[] position = new int[n + 1];
    for (i = 0; i < n + 1; i++)
        position[array1[i]] = i;
    int k = 1;
    for (i = n; i > 1; i--) {
        if (position[array1[i] + 1] <= position[array1[i - 1] + 1])
            k++;
    }
    return k;
}

1 个答案:

答案 0 :(得分:4)

这是一个二次时间算法。后缀数组为每个指定 一对后缀如何按字典顺序(和空的 后缀总是少于所有这些)。设s为未知字符串 并假设我们将后缀s[i...]与后缀s[j...]进行比较。 如果s[i] != s[j],则s[i]s[j]的比较结算。 否则,结果与我们比较s[i+1...]s[j+1...]

假设我们希望确保s[i...] < s[j...]。显然我们需要 s[i] <= s[j]。事实上,除非s[i+1...] < s[j+1...],否则我们需要。{ 严格的不平等s[i] < s[j],否则决胜局就会发生 错误道。否则,无论其余部分如何,s[i] == s[j]都足够了 的字符串。收集所有不等式作为定向中的弧 顶点对应s中位置的图形。这张图是 必须按后缀的总顺序非循环。制作每个弧长 如果不等式是严格的,则为1,否则为长度0。输出长度 最长路径的加一,(如果图形为空,则为零)。

至少需要相应的许多不同的字母 不平等链。可能不太清楚的是,这很多 不同的字母就足够了,但如果我们确定每个字母的标签 s中的顶点/位置,从那里开始的最长路径的长度, 然后每个弧的头部和尾部都有适当的标记。

要进入线性时间,我们可以利用它的结构 图形。它很简单(尽管不是微不足道的;图表指标 毕竟)显示访问图的所有顶点的路径 最长的,所以我们只需要计算它的长度。

以下是示例代码(minChars1)的音译版本 直接从上面的描述(minChars2,现在实现 剥夺了所有理解用法),一种强力解决方案 (minChars3)和线性时间解决方案(minChars4)。     import itertools

def minChars1(array):
    n = len(array)
    position = [-1] * (n + 1)
    for i in range(n):
        position[array[i]] = i
    infinity = n + 1
    minimum = [infinity] * (n + 1)
    minimum[n] = 0
    for i in range(n - 1, -1, -1):
        for j in range(i + 1, n + 1):
            valid = True
            for x in range(i, j):
                for y in range(x + 1, j):
                    valid = valid and position[array[x] + 1] < position[array[y] + 1]
            if valid and minimum[j] < infinity:
                minimum[i] = min(minimum[i], minimum[j] + 1)
    return minimum[0]


def lengthOfLongestPath(graph, memo, i):
    if i not in memo:
        result = 0
        for w, j in graph[i]:
            result = max(result, w + lengthOfLongestPath(graph, memo, j))
        memo[i] = result
    return memo[i]


def minChars2(array):
    n = len(array)
    position = [-1] * (n + 1)
    for i in range(n):
        position[array[i]] = i
    graph = {}
    for i in range(n):
        graph[i] = []
        for j in range(n):
            if position[i] > position[j]:
                w = 0 if position[i + 1] > position[j + 1] else 1
                graph[i].append((w, j))
    memo = {None: -1}
    for i in range(n):
        lengthOfLongestPath(graph, memo, i)
    return max(memo.values()) + 1


def minChars3(array):
    n = len(array)
    position = [None] * n
    for i in range(n):
        position[array[i]] = i
    for k in range(n):
        for s in itertools.product(range(k), repeat=n):
            valid = True
            for i in range(n):
                for j in range(n):
                    valid = valid and (s[i:] < s[j:]) == (position[i] < position[j])
            if valid:
                return k
    return n


def minChars4(array):
    n = len(array)
    if n == 0:
        return 0
    array1 = [n] * (n + 1)
    for i in range(n):
        array1[1 + i] = array[i]
    position = [None] * (n + 1)
    for i in range(n + 1):
        position[array1[i]] = i
    k = 1
    for i in range(n, 1, -1):
        if position[array1[i] + 1] <= position[array1[i - 1] + 1]:
            k += 1
    return k


def test():
    for n in range(7):
        for array in itertools.permutations(range(n)):
            assert minChars1(array) == minChars2(array) == minChars3(array) == minChars4(array)


test()