最长子串,无Y分度和征服

时间:2018-11-07 19:48:38

标签: algorithm divide-and-conquer longest-substring

在继续讨论该问题之前,我应该指出,我知道有很多更简单的方法可以解决此问题,而无需使用分而治之。但是,在这种限制下解决这个问题的重点是我实际上想学习如何解决分而治之的问题。我擅长识别正确的解决方案,但是实施自己的D&C策略并不是我目前的技能。

问题是这样的:给定一个字符串,找到不包含字母“ y”的最长子字符串。例如,longestNoY(“ abydefyhi”)应该返回“ def”。

我解决这个问题的第一个方法是确定基本案例。如果我们有一个长度为2的字符串,我们将要返回非y分量(如果两个字符均为y,则返回空字符串)。如果我们有一个长度为1的字符串,如果它不是'y',我们将返回它。

所以第一部分应该是这样的:

def longestNoY(string, start, end):
     #Conquer
     if start == end:
          if string == 'y': return ''
          return string
     if start + 1 == end:
          if string == "yy": return ''
          if string[0] == 'y': return string[1]
          return string[0]
     ....

接下来,我知道我需要递归地为父字符串的每一半调用该函数。我还知道我想让函数返回两个孩子中较长的那个,除非两个孩子的长度之和等于父对象的长度,那么该函数应该返回父对象,因为没有'y's在孩子们中。

    #Divide and Partial Implementation of Rejoin
    ....
    middle = (start + end) // 2
    leftString = longestNoY(string, start, middle)
    rightString = longestNoY(string, middle, end)
    if len(leftString) + len(rightString) == len(string): return string
    ....

我现在遇到的问题最好用一个例子来解释:

0 1 2     3 4   5 6   7 8 
a b   y   d e | f y   h i
a b   y | d e | f y | h i
a b | y | d e | f y | h i 

左侧最长的子字符串是“ ab”或“ de”,但是我们知道“ de”与'f'相邻,它将使“ def”最长。我不知道该如何继续进行下去。请不要给我提供解决此问题的程序。

4 个答案:

答案 0 :(得分:2)

这是可能。但是,每次您都需要返回四个值:从“切片”的左端开始的最长子序列(可以为零),“中间”的最长子序列,最长的子序列,该子序列以“ slice”的右端结尾(也可以为零),并且该字符串只是一个非Y字符序列(布尔值)。实际上,可以通过检查前三个元素中的一个是否等于长度来得出第四个元素,但这可能更易于实现。

为什么这很重要?因为一系列非y可以“通过”除法。例如:

abcdeYfghi   jklYmnopqr

在这里,如果我们将其拆分为中间(或其他任何不是“恒定”和“静止”的方式)。

所以这里递归地有几种情况:

  1. 空字符串返回(0, 0, 0, True)
  2. Y以外的非空字符串,我们返回(1, 1, 1, True);
  3. 对于单例字符串Y,我们返回(0, 0, 0, False);
  4. 将字符串一分为二并随后对结果应用“合并”逻辑的递归情况。

“合并逻辑”相当复杂,特别是因为两个“子切片”可能只包含非Y字符串。切片后,我们获得了两个三元组(a0, a1, a2, a3)(b0, b1, b2, b3),并生成了一个三元组(c0, c1, c2, c3)

如果a3 = Trueb3 = True,那么这当然意味着当前切片也不包含Y。因此我们可以得出:

c3 = a3 and b3

假设a3成立,那么它就成立了c0 = a0 + b0,因为a0之后没有Y,因此左侧的“序列”与子序列的整个长度相同加上右侧部分的左侧子序列。如果a3不成立 ,则c0就是a0

鉴于b3成立,那么出于与上述理由相同的理由持有c2 = a2 + b2,如果没有,则认为a2 = b2

现在中间的元素是三个元素的最大值:

  1. 左切片a1中间的元素;
  2. 右切片b1中间的元素;和
  3. a2b0的和,因为它们可以重叠,所以这是两者的和。

因此,我们返回树的最大值。

因此在Python中,这看起来像:

def longestNoY(string, start, end):
    if start == end:
        return (0, 0, 0, True)
    elif start+1 == end:
        if string[start] == 'y':
            return (0, 0, 0, False)
        else:
            return (1, 1, 1, True)
    else:
        mid = (start + end)//2
        a0, a1, a2, a3 = longestNoY(string, start, mid)
        b0, b1, b2, b3 = longestNoY(string, mid, end)
        c3 = a3 and b3
        c0 = a0 + a3 * b0
        c2 = b2 + b3 * a2
        c1 = max(a1, b1, a2 + b0)
        return (c0, c1, c2, c3)

最终结果是元组中前三个项目的最大值。

对于给定的示例字符串,我们获得:

             (1, 1, 1, True) a
             (1, 1, 1, True) b
         (2, 2, 2, True) ab
             (0, 0, 0, False) y
             (1, 1, 1, True) d
         (0, 1, 1, False) yd
     (2, 2, 1, False) abyd
             (1, 1, 1, True) e
             (1, 1, 1, True) f
         (2, 2, 2, True) ef
             (0, 0, 0, False) y
                 (1, 1, 1, True) h
                 (1, 1, 1, True) i
             (2, 2, 2, True) hi
         (0, 2, 2, False) yhi
     (2, 2, 2, False) efyhi
 (2, 3, 2, False) abydefyhi
(2, 3, 2, False)

但是,话虽如此,但在我看来,构建一些遍历时间却与遍历相同但通常更昂贵的函数(函数调用,构造新对象等)是不必要的复杂过程。尤其是因为线性遍历只是:

def longestNoY(string):
    mx = 0
    cur = 0
    for c in string:
        if c == 'y':
            mx = max(mx, cur)
            cur = 0
        else:
            cur += 1
    return mx

然而,这里的优点是上述算法可以用于并行化。例如,如果字符串很大,则可以使用上面的代码,以便每个内核都可以计数。但是,在那种情况下,在“核心”级别上使用迭代级别,并且仅使用上述级别来“分发”工作和“收集”结果可能会有所益处。

答案 1 :(得分:2)

只需遍历字符串即可轻松解决。但我知道您想学习Divide Conquer。

对我来说,这不是使用Divide Conquer解决的好问题。

递归建议的@WillemVanOnsem具有与线性遍历基本相同的效果。

但是,如果您确实想以分而治之的方式进行操作,则需要考虑穿过中点的子字符串,即start <= i <= mid

答案 2 :(得分:1)

我认为解决问题的最佳方法是在y之前和之后找到位置,而不是y。这样,您将找到间隔的左端和右端。我没有提供代码,因为您明确要求不要为您解决问题,所以请指向正确的方向,所以:

  • 在琐碎的情况下(间隔的长度为0),确定您所拥有的项目是有效的左端还是右端,且间隔为有效
  • 在非平凡的情况下,总是将集合左右减半(如果项目的数量是奇数,那没问题,只需将中间的位置放到某个地方),并同时对它们进行分而治之
  • 在平凡的情况下,请始终考虑左右子问题为您提供的最佳间隔
  • 在非平凡的情况下,请确保如果间隔从左开始并在右结束,请考虑到这一点
  • 从这样的间隔开始,长度越大越好

这些是您需要采用的思想,以实现您期望的分裂和征服。编码愉快!

答案 3 :(得分:0)

现在我确实有时间学习,我决定回到这个问题,并提出了一个易于理解的解决方案。在这里:

def across(string, start, end, middle):
    startL = middle
    bestL = ''
    while(startL >= start and string[startL] != 'y'):
        bestL = string[startL] + bestL
        startL -= 1
    startR = middle + 1
    bestR = ''
    while(startR <= end and string[startR] != 'y'):
        bestR = bestR + string[startR]
        startR += 1
    return bestL + bestR

def longestNoY(string, start, end):
    if(start > end):
        return ''
    if(start == end):
        if(string[start] == 'y'):
            return ''
        return string[start]
    middle = (start + end) // 2
    leftString = longestNoY(string, start, middle)
    rightString = longestNoY(string, middle + 1, end)
    acrossString = across(string, start, end, middle)
    return max(leftString, rightString, acrossString, key=len)
相关问题