简单字符串压缩:删除连续的重复子字符串

时间:2015-07-13 06:02:37

标签: string algorithm

最近我在亚马逊的采访中被问到这个问题。

给定一个字符串,从中删除连续的重复子字符串。如果有多个连续的交叉子串,请删除其中最大的子串。

为清楚起见,以下是一些例子:

  • 输入:aabcccddeaaa
    输出:abcdea(压缩连续重复的字符)
  • 输入:abababcdeee
    输出:abcde(压缩连续重复的子串)
  • 输入:ababcdabcd
    输出:ababcd

(您可以压缩“ab”或“abcd”,但由于“abcd”的长度较大,您更喜欢压缩较大的长度。)

我无法提出有效的实施方案,任何人都知道这方面的好方法吗?

由于这是一个面试问题,请不要使用复杂的图书馆功能。

4 个答案:

答案 0 :(得分:2)

对于字符串X,我们可以使用Z-algorithm Given a string S of length n, the Z Algorithm produces an array Z where Z[i] is the length of the longest substring starting from pat[i] which is also a prefix of patSource

找到O(n ^ 2)中最大的连续重复子字符串

对于X的每个后缀,从i开始,对此子字符串应用Z算法:

int result = 0;
for(int i = 0; i < X.length(); i++)
   int[]z = Z-algo(X.substring(i)); //this take O(n)
   for(int j = i + result + 1; j < X.length(); j++)
       if(z[j] >= j - i + 1)
          result = (j - i + 1);

重复上述过程,直到我们找不到任何重复的子串,我们就可以得到一个O(n ^ 3)算法。

注意:重新阅读问题后,特别是最后一个例子,我发现有效的重复子字符串仅限于原始的子字符串。因此,通过使用最大堆,时间复杂度可以减少到O(n ^ 2 log n)。

答案 1 :(得分:1)

没有正则表达式...这种递归方法有效:

var cases = ['aabcccddeaaa', 'abababcdeee', 'ababcdabcd'];

function compress(str) {
  var len, sub, i, n;

  // if str is shorter than 2, can't be any repeating substrings
  if(str.length < 2)
    return str;

  // max meaningful length is str.length/2 (truncated to integer)
  for(len = (str.length / 2) | 0; len > 0; len--) {
    // search for a repeating substring of "len" length starting at index i
    for(i = 0; i + (len * 2) <= str.length; i++) {
      sub = str.substr(i, len);
      // if such a substring exists...
      if(str.indexOf(sub, i + len) == i + len) {
        // "count" how many occurences (advance index till beyond repeats)
        for(n = i + len * 2; str.indexOf(sub, n) == n; n += len);
        // return a string composed of the compressed part before the match +
        // the substring + the compressed part beyond the match
        return compress(str.substr(0, i)) + sub + compress(str.substr(n));
      }
    }
  }

  // if nothing found, return original string
  return str;
}

alert(JSON.stringify(cases.map(compress)));

在关于算法复杂性的评论中经过长时间的争论后,我决定重构一下并使用自我实现的startsWith函数,并利用它来计算内部操作(复杂性......)。 / p>

我抓住机会通过最小化字符串分配来进一步优化,所以现在递归适用于整个字符串+开始/结束索引。

下面的代码生成一个输出,其中包括输入字符串,结果,n ^ 2(用于O(n ^ 2)比较)和实际内部操作计数。我添加了一些边缘情况来展示它的表现。我找不到导致n ^ 2计数的输入,它们都在下面。

var cases = ['aabcccddeaaa', 'abababcdeee', 'ababcdabcd',
             'aabaaabaab', '1', '111222', '123456789', '1234567899'];

var innerCount;

function startsWith(str, start, subStart, subLen) {
  var subEnd = subStart + subLen - 1;
  while(subStart <= subEnd) {
    innerCount++;
    if(str[start++] != str[subStart++])
      return false;
  }
  return true;
}

function doCompress(str, maxLen, minIndex, maxIndex) {
  var len, sub, i, n;

  // if str is shorter than 2, can't be any repeating substrings
  if(maxIndex - minIndex + 1 < 2)
    return str.substring(minIndex, maxIndex + 1);

  for(len = maxLen; len > 0; len--) {
    // search for a repeating substring of "len" length starting at index i
    for(i = minIndex; i + (len * 2) <= maxIndex + 1; i++) {
      // if such a substring exists...
      if(startsWith(str, i + len, i, len)) {
        // "count" how many occurences (advance index till beyond repeats)
        for(n = i + len * 2; (n + len <= maxIndex + 1) && startsWith(str, n, i, len); n += len);
        // return a string composed of the compressed part before the match +
        // the substring + the compressed part beyond the match
        return (i > minIndex ? doCompress(str, len - 1, minIndex, i - 1) : '') +
          str.substr(i, len) +
          (n < maxIndex ? doCompress(str, len, n, maxIndex) : '');
      }
    }
  }

  // if nothing found, return original string
  return str.substring(minIndex, maxIndex + 1);
}

function compress(str) {
  innerCount = 0;
  // max meaningful length is str.length/2 (truncated to integer)
  return {
    source: str,
    result: doCompress(str, (str.length / 2) | 0, 0, str.length - 1),
    'n^2': str.length*str.length,
    innerCount: innerCount};
}

alert(JSON.stringify(cases.map(compress), null, '\t'));

此解决方案的时间复杂度为O(n ^ 2)。

答案 2 :(得分:1)

pos=[]
dstr={}
final=[]
x="ababcdabcdcde"

for k in re.finditer(r"(?=(.+?)\1+)",x):        #Find start of all overlapping strings
    pos.append(k.start())
i=0
for k in pos: #Find end of overlapping strings
    s=re.findall(r"^((.*)\2+)",x[k:])
    dstr[i]=(k,len(s[0][0]))
    i=i+1
#print dstr.values()
k=0
while k< len(dstr.values())-1:           #remove smaller length overlapping result
    if dstr.values()[k+1][0]<dstr.values()[k][1]<dstr.values()[k+1][1]:
        pass
    else:
        final.append(dstr.values()[k][0])
    k=k+1
if dstr.values()[k-1][0] in final:
    pass
else:
    final.append(dstr.values()[k][0])
#print final
for k in final:             #remove strings
    s=re.sub(r"(.*)\1+",r"\1",x[k:])
    x=x[:k]+s
print x

对于给定的输入,这在python.Works中很好。

答案 3 :(得分:0)

有一个简单的非递归O(n ^ 3)。关键的观察是:假设有一个字符串&#39; aabcbcabbc&#39;,如果我们只是删除连续重复,只要我们首先减少长度= 1的字符串,长度= 2秒,依此类推,我们可以减少它,减少将是最佳的。因此

&#39; aabcabbc&#39; =&GT; &#39; abcbcabc&#39; =&GT; &#39; ABCABC&#39; =&GT; &#39; ABC&#39;

Python代码:

def strcompress(str):
   strlen = len(str)
   for size in range (1, strlen // 2):
      for i in range (0, strlen - 2 * size + 1):
            str1 = str[i:i+size]
            str2 = str[i+size:i+2*size]
            while str1 == str2:
               str = str[:i+size] + str[i+2*size:]
               strlen = len(str)
               if i + 2*size > strlen:
                  break
               str2 = str[i+size:i+2*size]
   print("The compressed string is:" + str)   
   return

示例:

>>> strcompress("ababcdabcd")
The compressed string is:abcd

编辑:修复了代码中的一些错误。这适用于现有样本和我提供的示例。