分区中唯一子字符串的最大数量


30

我修改了标题,以使其更易于理解。

这是问题的详细版本:

我们有一个字符串s ,想要将其拆分为子字符串。每个子字符串彼此不同。一次切割最多可以拥有的唯一子字符串的数量是多少。换句话说,串联形成form的唯一子字符串的最大数量是多少s

这里有些例子:

Example 1
s = 'aababaa'
output = 4
Explain: we can split `s` into aa|b|aba|a or aab|a|b|aa, 
         and 4 is the max number of substrings we can get from one split.

Example 2
s = 'aba'
output = 2
Explain: a|ba

Example 3
s = 'aaaaaaa'
output = 3
Explain: a|aa|aaaa

注意s仅包含小写字符。我没有被告知需要多长时间s,因此无法猜测最佳时间复杂度。:(

这是一个NP难题吗?如果没有,如何有效解决?

我从我的一个朋友那里听到了这个问题,无法解决。我正在尝试使用Trie +贪婪解决此问题。对于第一个示例,该方法失败。

这是我想出的Trie解决方案:

def triesolution(s):
    trie = {}
    p = trie
    output = 0
    for char in s:
        if char not in p:
            output += 1
            p[char] = {}
            p = trie
        else:
            p = p[char]
    return output

例如1,上面的代码将尝试拆分s为,因此返回3 a|ab|abaa

补充:由于大家的想法,看来这个问题非常接近NP问题。现在,我正在尝试从这个方向进行思考。假设我们有一个函数Guess(n)True如果我们可以n从一个拆分中找到唯一的子字符串,则此函数将返回False。这里的一个观察是,如果Guess(n) == True,那么Guess(i) == True就全部i <= n。因为我们可以将两个相邻的子字符串合并在一起。这种观察可能导致二元解。但是,仍然需要我们能够Guess非常有效地计算函数。可悲的是,我仍然找不到一种多项式计算方法Guess(n)


第一个也可以拆分为aab|a|b|aa仍然是4
smac89 '19

3
出于好奇,您的琴弦能得到多长时间?
templatetypedef

aababaa可以分为|| aa | aab | aaba | aabab | aababa | aba | ...等等。您是怎么得到4分的?
Suraj Motaparthy '19

字符串仅包含ab
范·楚格

@PhamTrung不,但是您可以假定它仅包含小写字符。
wqm1800 '19

Answers:


15

这被称为冲突感知字符串分区问题,并且由Anne Condon,JánMaňuch和Chris Thachuk在一篇论文中通过将3-SAT简化为NP-完全问题,证明了冲突感知字符串分区问题及其复杂性用于基因合成的寡核苷酸设计的相关性(国际计算和组合学会议,265-275,2008)。


我粗略地看了一下那篇论文,似乎结果证明那里只表明在每个子字符串中的字符数有上限的情况下,这个问题是NP-hard的。准确吗?如果是这样,那将使其与此问题略有不同。通过类推推理,即使“在树中的节点上受到程度约束的MST”问题是NP-hard的,也可以在多项式时间内找到MST。
templatetypedef

1
为了证明此问题是NP难题,我们需要能够将已知的NP难题(k分区)减少为该问题(无约束分区),而不是采用其他方法。用于k分区的求解器肯定可以解决此问题,但是并不能证明NP硬度。
templatetypedef

我看不到本文能解决问题:据我了解,本文涉及的是决策问题,如果存在划分为最大长度为k的子字符串的情况。如果k大于字符串总长度的一半,那么决策问题就完全成立了(据我所知)。
汉斯·奥尔森,

不,问题对于大k而言具有微不足道的解决方案,但这并不意味着k必定会很小,并且减少会起作用。
templatetypedef

8

(非常感谢吉拉德·巴尔坎(Gilad Barkan)使我意识到了这一讨论。)

让我从纯理论的角度分享我对这个问题的想法(请注意,我也使用“因数”而不是“子词”)。

我认为这里考虑的一个或多个问题的正式定义如下:

给定单词w,找到单词u_1,u_2,...,u_k使得

  • u_i!= u_j对于每个i,j的1 <= i <j <= k
  • u_1 u_2 ... u_k = w

最大化变体(我们想要很多u_i):最大化k

最小化变体(我们想要短的u_i):最小化max {| u_i | :1 <= i <= k}

这些问题通过另外给定边界B来成为决策问题,根据我们在说“多因子”变量还是“短因子”变量,它是k的下限(我们至少希望B因子),或max {| u_i |的上限 :1 <= i <= k}(我们希望长度的因子至多为B)。为了谈论NP硬度,我们需要谈论决策问题。

让我们将术语SF用于“短因子”变量,将MF用于“许多因子”变量。特别是,这是非常关键的一点,问题的定义方式是使我们在某个字母上得到一个不受任何限制的单词。问题的版本是我们知道先验的,我们只能获得输入单词,例如字母{a,b,c,d}是另一个问题!NP-硬度并没有自动地从“无限制”结转到“固定字母”变体(后者可能是更简单)。

SF和MF都是NP完全问题。这已分别在[1,1b]和[2]中显示(正如Gilad已经指出的那样)。如果我在讨论开始时正确地理解了(也许也是)非正式问题的定义,那么讨论的问题就是MF问题。最初没有提到单词仅限于某些固定的字母,后来又说我们可以假设仅使用小写字母。如果这意味着我们只考虑固定字母{a,b,c,...,z}上的单词,那么就NP硬度而言,这实际上将发生很大变化。

仔细研究发现,SF和MF在复杂性方面存在一些差异:

  1. 论文[1,1b]显示,如果我们将字母固定为二进制的,则SF仍为NP完整的(更准确地说:将单词w覆盖字母a和b以及边界B,我们可以将其分解为不同的长度因子)吗?最多B?)。
  2. 论文[1,1b]表明,如果我们将边界B固定为2,则SF仍然是NP完全的(更准确地说:得到一个单词w,我们能否将其分解为最多2个不同的长度因子?)。
  3. 论文[3]表明,如果字母和边界B都是固定的,则SF可以在多项式时间内求解。
  4. 论文[2]表明MF是NP完全的,但前提是字母不受先验的限制或固定!特别是,它,如果问题是NP完全问题,如果我们只考虑在一些固定的字母输入的话(像通常那样在实际设置的情况)回答问题。
  5. 论文[3]表明,如果输入边界B再次由某个常数作为上界,则MF可以在多项式时间内求解,即问题输入是一个单词和{1,2,...,K}的边界B ,其中K是某个固定常数。

关于这些结果的一些注释:Wrt(1)和(2),直观上很清楚,如果字母是二进制的,那么,为了使问题SF变得困难,边界B也不能固定。相反,固定B = 2意味着字母大小必须变得相当大才能产生困难的实例。结果,(3)显得微不足道(实际上,[3]说得更多:我们不仅可以在运行时求解多项式,而且可以将| w | ^ 2倍仅取决于字母大小的因数)求解并绑定到B)。(5)也不难:如果我们的单词比B长,那么我们可以通过简单地切成不同长度的因子来获得所需的因子分解。如果不是,那么我们可以强行使用所有可能性,这仅在B中呈指数关系,在这种情况下,B被假定为常数。

因此,我们得到的结果如下:SF似乎更困难,因为即使对于固定的字母或固定的边界B,我们也有硬度。另​​一方面,如果边界固定,则问题MF可以被多项式求解。在这方面比SF容易),而对应的问题是字母大小未定。因此,即使事实证明固定字母的MF也是NP完整的,MF的复杂度也比SF略小。但是,如果可以证明MF可以在固定时间内解决固定字母的问题,那么MF比SF容易得多……因为一种很难解决的情况是人为的(无界字母!) 。

我确实花了点力气试图解决带有有界字母的MF的情况,但此后我无法解决并停止研究。我不相信其他研究人员已经尽力解决了这个问题(因此,这不是这些非常棘手的公开问题之一,许多人已经尝试并失败了;我认为这是可行的)。我的猜测是,对于固定字母来说这也是NP难的,但是减少的过程可能是如此复杂,以至于您会得到类似“ MF对于35或更大的字母来说很难”之类的东西,这也不是太好了。

关于其他文献,我知道论文[4],该论文考虑了将单词w分解为都是回文的不同因子u_1,u_2,...,u_k的问题,这些因子也是NP完全的。

吉拉德指出,我快速浏览了论文[5]。不过,似乎要考虑其他设置。在本文中,作者对一个给定单词中可以包含多少个不同的子序列或子单词的组合问题感兴趣,但是它们可以重叠。例如,aaabaab包含20个不同的子词a,b,aa,ab,ba,bb,aaa,aab,aba,baa,baa,aaab,aaba,abaa,baab,aaaba,aabaa,abaab,aabaab,aaabaa,aaabaab(也许我误算了,但您知道了)。它们中的一些仅发生一次,例如baa,其中一些仅发生一次,例如aa。无论如何,问题不在于我们如何以某种方式拆分单词以获取许多不同的因素,因为这意味着每个单独的符号都恰好构成一个因素。

关于解决此类问题的实际解决方案(请记住,我是一名理论家,因此请慎重考虑):

  • 据我所知,如果我们仅考虑固定字母上的输入单词,那么就没有理论上的下界(例如NP硬度)会排除它在多项式时间内求解MF的可能性。但是,有一个警告:如果您使用的是多重时间算法,则该算法应该以固定字母表中的符号数量成指数形式运行(或者在该函数的某些函数中呈指数形式)!否则,对于无界字母来说,它也是多项式时间算法。因此,作为一名理论家,我将寻找仅在符号数量以及以某种方式有助于设计MF算法的情况下才能按时间指数计算的算法任务。另一方面,很可能不存在这种算法,并且在固定字母的情况下MF也是NP-hard的。

  • 如果您对实际解决方案感兴趣,则近似解决方案可能会有所帮助。因此,在最坏的情况下,保证分解仅为最佳值的一半就不会太糟糕。

  • 我想,没有给出可证明的近似比率但在实际环境中能很好工作的启发式方法也很有趣。

  • 将问题实例转换为SAT或ILP实例应该不太困难,然后您可以运行SAT或ILP-Solver甚至获得最佳解决方案。

  • 我的个人看法是,即使不知道MF的固定字母是否为NP困难的,也有足够的理论见解表明该问题足够困难,因此有理由寻找启发式解决方案等。在实际环境中运作良好。


参考书目:

[1] Anne Condon,JánManuch,Chris Thachuk:字符串分区的复杂性。J.离散算法32:24-43(2015)

[1b] Anne Condon,Jan Manuch,Chris Thachuk:碰撞感知字符串分配问题的复杂性及其与基因合成的寡核苷酸设计的关系。茧2008:265-275

[2] Henning Fernau,Florin Manea,Robert Mercas,Markus L. Schmid:带变量的模式匹配:快速算法和新的硬度结果。STACS 2015:302-315

[3] Markus L. Schmid:计算无相等和重复的字符串分解。理论。计算 科学 618:42-51(2016)

[4] Hideo Bannai,Travis Gagie,Innsaga Shunsuke,JuhaKärkkäinen,Dominik Kempa,Marcin Piatkowski和Shiho Sugimoto:多样的回文因式分解都是NP完全的。诠释 J.发现。计算 科学 29(2):143-164(2018)

[5]亚伯拉罕·弗拉克斯曼(Abraham Flaxman),阿拉姆·威特罗斯·哈罗(Aram Wettroth Harrow),格雷戈里·B·索金(Gregory B. Sorkin):具有最大不同子序列和子字符串的字符串。电器。J.梳 11(1)(2004)


(顺便感谢您的发帖!)为澄清起见,我在上面对参考文献[5]的评论确实是关于一个不同的问题-它是对LukStorms 在主评论部分 “对N的任何字符串的回答”的回答。 P个可能字符的长度,这样的字符串可以包含的唯一子字符串的最大值是多少?”
גלעדברקן

3

这是一个解决方案,但是它很快就爆炸了,而且远不是有效的解决方案。它将首先将字符串分解为唯一的子字符串列表,而无需考虑顺序,然后尝试使用itertools.permutation将这些子字符串重新组合回原始字符串,测试每个置换是否与原始字符串匹配。

import itertools as it

def splitter(seq):                                                             
    temp = [seq]
    for x in range(1, len(seq)):
        print(seq[:x], seq[x:])
        temp.append(seq[:x])
        temp.append(seq[x:])
    return temp

if __name__ == "__main__":
    test = input("Enter a string: ")
    temp = splitter(test)
    copy = temp[::]
    condition = True
    for x in temp:
        if len(x) > 1:
            copy.extend(splitter(x))
    copy = sorted(list(set(copy)))
    print(copy)
    count = []
    for x in range(len(test)):
        item = it.permutations(copy, x)
        try:
            while True:
                temp = next(item)
                if "".join(list(temp)) == test:
                    if len(temp) == len(set(temp)):
                        count.append((len(temp), temp))
        except StopIteration:
            print('next permutation begin iteration')
            continue
    print(f"All unique splits: {count}")
    print(f"Longest unique split : {max(count)[0]}")

对于第一个测试,我们得到以下信息:

All unique splits: [(1, ('aababaa',)), (2, ('a', 'ababaa')), (2, ('aa', 'babaa')), (2, 
('aab', 'abaa')), (2, ('aaba', 'baa')), (2, ('aabab', 'aa')), (2, ('aababa', 'a')), (3, 
('a', 'ab', 'abaa')), (3, ('a', 'aba', 'baa')), (3, ('a', 'abab', 'aa')), (3, ('aa', 'b',
 'abaa')), (3, ('aa', 'ba', 'baa')), (3, ('aa', 'baba', 'a')), (3, ('aab', 'a', 'baa')),
 (3, ('aab', 'ab', 'aa')), (3, ('aab', 'aba', 'a')), (3, ('aaba', 'b', 'aa')), (3,
 ('aaba', 'ba', 'a')), (4, ('a', 'aba', 'b', 'aa')), (4, ('aa', 'b', 'a', 'baa')), (4,
 ('aa', 'b', 'aba', 'a')), (4, ('aab', 'a', 'b', 'aa'))]
Longest unique split : 4

也许可以通过某种方式对其进行优化,但是在这台计算机上花费了相当长的时间。


3

我已经试过考虑这个问题,或者考虑是否在给定的索引上进行分区。因此,此函数是递归的,并在每个索引1上创建2个分支。不要在索引i上进行 分区2.在索引i上进行分区。

根据分区,我填写一个集合,然后返回集合的大小

def max(a,b):
    if a>b: return a
    return b



def keep(last, current, inp, map):
    # print last
    # print current
    # print map

    if len(inp) == 2 :
        if inp[0]==inp[1]: return 1
        return 2

    if current >= len(inp):
        return len(map)
    // This is when we are at the start of the string. 
    // In this case we can only do one thing not partition and thus take the entire string as a possible string.

    if current == last :
        map11 = map.copy()
        map11.add(inp[current:])
        return keep(last, current + 1, inp, map11)

    map1 = map.copy();
    if current != (len(inp)-1):
        map1.add(inp[last:current])

    map2 = map.copy()

    return max(keep(last,current+1,inp, map2), keep(current, current+1, inp, map1))

print keep(0,0,"121", set([]))
print keep(0,0,"aaaaaaa", set([]))
print keep(0,0,"aba", set([]))
print keep(0,0,"aababaa", set([]))
print keep(0,0,"21", set([]))
print keep(0,0,"22", set([]))

https://onlinegdb.com/HJynWw-iH


感谢您的解决方案!这个DFS解决方案非常清楚。我有一个小建议,keep因为该set.copy()功能非常耗时,因此可以加速该功能。在完成此功能堆栈时,从集合中删除当前候选者,如何使用回溯?
wqm1800 '19

@ wqm1800您能否详细说明,对不起,我不太清楚。即使我们使用回溯,我们仍然必须merge分开集,因为我们一直在分支。因此,它要么合并要么复制。你能详细说明吗?
拉维·钱达克

1
这是我的回溯解决方案。之所以可行,是因为函数堆栈以DFS方式执行,因此当函数完成时,这意味着它已经完成了对其所有子树的搜索。
wqm1800 '19

3

您可以使用带有set的递归函数作为第二个参数来跟踪当前路径中的唯一字符串。对于每个递归,遍历所有索引加1,在该索引处将字符串拆分为可能的候选字符串,如果候选字符串尚未在集合中,则使用剩余的字符串和候选对象添加到集合中进行递归调用要从剩余字符串中获取最大数目的唯一子字符串,请向其添加1并从迭代中返回最大值的最大值。如果给定字符串为空或所有候选字符串已在集合中,则返回0:

def max_unique_substrings(s, seen=()):
    maximum = 0
    for i in range(1, len(s) + 1):
        candidate = s[:i]
        if candidate not in seen:
            maximum = max(maximum, 1 + max_unique_substrings(s[i:], {candidate, *seen}))
    return maximum

演示:https : //repl.it/@blhsing/PriceyScalySphere

在Python 3.8中,也可以使用max生成器表达式对函数的调用来编写上述逻辑,该表达式可以过滤已通过赋值表达式“看到”的候选对象:

def max_unique_substrings(s, seen=()):
    return max((1 + max_unique_substrings(s[i:], {candidate, *seen}) for i in range(1, len(s) + 1) if (candidate := s[:i]) not in seen), default=0)

1

这是一个基于图论的答案。

建模
这个问题可以建模为一个最大独立集问题上大小的图表O(n²):如下
咱们w = c_1, ..., c_n是输入字符串。
让我们G = (V,E)成为一个无向图,其构建如下:
V = { (a, b) such that a,b in [1, n], a <= b }。我们可以看到的大小Vn(n-1)/2,其中每个顶点代表的子字符串w
然后,对于每对夫妇的顶点(a1, b1)(a2, b2),我们所建立的边缘((a1, b1), (a2, b2))IFF
(I)[a1, b1]相交[a2, b2]
(II) c_a1...c_b1 = c_a2...c_b2
否则,如果(i)它们表示的子字符串重叠w或(ii)两个子字符串相等,则在两个顶点之间建立一条边。

然后,我们可以看到,为什么一个最大独立集G提供了解决我们的问题。

复杂度
在一般情况下,最大独立集(MIS)问题是NP难的,时间复杂度为O(1.1996^n)且在多项式空间中[Xiao,NamaGoshi(2017)]
起初,我认为生成的图将是一个弦图(没有诱导的长度> 3的循环),因为它可以很好地解决此类问题,因此可以在线性时间内解决MIS问题。
但是我很快意识到事实并非如此,找到包含5个或更多长度的诱导循环的示例非常容易。
实际上,结果图不显示我们通常需要的任何“好”属性,并且可以将MIS问题的复杂性降低为多项式。
这只是问题复杂性的上限,因为多项式时间减少仅在一个方向上进行(我们可以将这个问题简化为MIS问题,而不能将其简化为MIS问题,至少不是平凡的)。因此,最终我们O(1.1996^(n(n-1)/2))在最坏的情况下解决了这个问题。
所以,a,我无法证明它在P中,或者它是NP完全的或NP困难的。可以肯定的是,问题出在NP,但是我想这对任何人来说都不奇怪。

实现
将这个问题简化为MIS问题的优点是MIS是一个经典问题,可以找到几种实现方案,并且MIS问题也很容易写为ILP。
这是MIS问题的ILP公式:

Objective function 
maximize sum(X[i], i in 1..n)
Constraints:
for all i in 1..n, X[i] in {0, 1}
for all edge (i, j), X[i] + X[j] <= 1

我认为,这应该是解决此问题的最有效方法(将此模型用作MIS问题),因为ILP求解器非常有效,特别是在涉及大型实例时。

这是我使用Python3和GLPK求解器完成的实现。要对其进行测试,您需要与Cplex文件格式兼容的LP解算器。

from itertools import combinations

def edges_from_string(w):
    # build vertices
    vertices = set((a, b) for b in range(len(w)) for a in range(b+1))
    # build edges
    edges = {(a, b): set() for (a, b) in vertices}
    for (a1, b1), (a2, b2) in combinations(edges, 2):
        # case: substrings overlap
        if a1 <= a2 <= b1:
            edges[(a1, b1)].add((a2, b2))
        if a2 <= a1 <= b2:
            edges[(a2, b2)].add((a1, b1))
        # case: equal substrings
        if w[a1:b1+1] == w[a2:b2+1]:
            if a1 < a2:
                edges[(a1, b1)].add((a2, b2))
            else:
                edges[(a2, b2)].add((a1, b1))
    return edges

def write_LP_from_edges(edges, filename):
    with open(filename, 'w') as LP_file:
        LP_file.write('Maximize Z: ')
        LP_file.write("\n".join([
            "+X%s_%s" % (a, b)
            for (a, b) in edges
        ]) + '\n')
        LP_file.write('\nsubject to \n')
        for (a1, b1) in edges:
            for (a2, b2) in edges[(a1, b1)]:
                LP_file.write(
                    "+X%s_%s + X%s_%s <= 1\n" %
                    (a1, b1, a2, b2)
                )
        LP_file.write('\nbinary\n')
        LP_file.write("\n".join([
            "X%s_%s" % (a, b)
            for (a, b) in edges.keys()
        ]))
        LP_file.write('\nend\n')
write_LP_from_edges(edges_from_string('aababaa'), 'LP_file_1')
write_LP_from_edges(edges_from_string('kzshidfiouzh'), 'LP_file_2')

然后,您可以用解决这些问题glpsol命令:
glpsol --lp LP_file_1
aababaa得到迅速解决(0.02秒我的笔记本电脑),但正如所料,事情就(多)作为强硬的字符串大小增长....
这个方案只给出了数值(而不是最佳分区),不过可以使用LP求解器/ python接口(例如pyomo)以类似的实现找到最佳分区和相应的子字符串

时间和内存
aababaa:0.02秒,0.4 MB,值:4
kzshidfiouzh:1.4秒,3.8 MB,值:10
aababababbababab:60.2秒,31.5 MB,值:8
kzshidfiouzhsdjfyu:207.5秒,55.7 MB,值:14
请注意,LP解算器还提供当前解决方案的上下限,因此对于最后一个示例,我可以在一分钟后得到实际的解决方案作为下限。


建模并不是减少或证明复杂性,尽管它可以对实施解决方案有所帮助。我最初将此模型(MIS)作为主要回答下的注释编写,后来删除了它。Markus Schmid是撰写此主题论文的少数理论家之一,已经在该网页上做出了详尽的回答。决策问题的复杂性类别在文献中仍然是开放的。
גלעדברקן

在这种情况下,MIS是一种无关紧要的关联,因为我们当然在寻找一大堆“无连接(边缘)连接”的东西。例如,对于一个字符的字母,答案是一个数字分区,为此将有一个简单的多项式时间解。问题的某些方面可能会提供优化,在进行更多研究后可能会绕过基于O(n ^ 2)的LP,而在MIS视图中暂停可能会忽略该优化。但是对于一般的工作解决方案来说确实不错。
גלעדברקן

你看了我的答案吗?我提供了从MIS到此问题的简单的单项多项式时间缩减方法,无可替代。至于单字符字母,问题显然出在P中,贪婪的琐碎分辨率。
m.raynal,

似乎您已经基于MIS对它的复杂性类做出了假设。
19לעדברקן19年

因此,请阅读答案:-),您将发现情况并非如此,我仅使用MIS复杂度来给出问题复杂度的上限。没有下界。
m.raynal,

0

我的其他答案是密切相关的,但并不完全与此问题相对应,这使得是否找到最大的无相等字符串因式分解可能具有不同的复杂度级别与是否存在具有约束因子长度的无相等因式分解(后者后者)存在歧义。被引用的论文解决)。

在本文中,变量的模式匹配:快速算法和新的硬度结果(Henning Fernau,Florin Manea,RobertMercaş和Markus L.Schmid,在第32届计算机科学理论方面专题讨论会上,STACS,2015年,莱布尼兹(Leibniz)第30卷作者在国际信息学进展(LIPIcs),第302-315页,2015年指出,对于给定的数目k和单词w,决定是否w可以分解为k不同的因子是NP完全的。

如果我们考虑templatetypedef的评论,暗示有可能是一个多项式时间解决不受限制的,最大的自由平等,因式分解那么肯定我们可以使用这样的算法来回答,如果我们能分割字符串成k通过简单的观察,如果不同的因素(子)k是小于我们已经知道的最大值。

然而,施密德(Schmid)(2016)写道:“如果字母固定,MaxEFF-s是否保持NP完整仍是一个悬而未决的问题。” (计算无相等和重复的字符串因式分解,理论计算机科学第618卷,2016年3月7日,第42-51页)

但是,最大无平等分解大小(MaxEFF-s)仍然是参数化的,并定义为:

实例:一个单词w和一个数字m1 ≤ m ≤ |w|

问题:是否存在wwith 的无相等分解因数p s(p) ≥ m?(s(p)是分解的大小。)

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.