如何找到没有重复字符的最长子字符串?

Posted

技术标签:

【中文标题】如何找到没有重复字符的最长子字符串?【英文标题】:How to find the longest substring with no repeated characters? 【发布时间】:2013-06-09 18:25:30 【问题描述】:

我想要一种算法来查找给定字符串中最长的不包含重复字符的子字符串。我可以想到一个 O(n*n) 算法,它考虑给定字符串的所有子字符串并计算非重复字符的数量。例如,考虑字符串“AABGAKG”,其中唯一字符的最长子字符串为 5 个字符,对应于 BGAKG

任何人都可以提出更好的方法吗?

谢谢

编辑:我认为我无法向其他人正确解释我的问题。您可以在子字符串中包含重复字符(这并不是说我们需要 geeksforgeeks 解决方案所做的子字符串中的所有不同字符)。我必须找到的东西是任何子字符串中的最大不重复字符(可能是某些字符重复的情况)。

例如,说字符串是 AABGAKGIMN 那么 BGAKGIMN 就是解决方案。

【问题讨论】:

当前形式的相当离题的问题,但请考虑如何在此处使用std::set 你可以使用哈希表并维护2个索引和一个计数器,想想吧。你会在O(n)内得到它 这里是你应该看到的链接,但我建议你先尝试自己解决........geeksforgeeks.org/… 如果您正在考虑 all 子字符串,它是 O(n^3) 而不是 O(n^2) 但是,正如 Omkant 所说,有一个 @ 987654325@这个问题的解决方案。 为什么 ABGKMN 不是解决方案? 【参考方案1】:

对于每个 start = 0 ... (n-1),尝试将 end 扩展到最右边的位置。

保留一个 bool 数组 used[26] 来记住是否已经使用了任何字符。 假设目前我们完成了(开始,结束

对于开始+1

先按集合清除:used[str[start]] = false; while ((end+1

现在我们检查了新的(开始,结束)。总复杂度为 O(N)。

【讨论】:

我最喜欢这个!它简短、快速、简单!我希望它有效,否则我必须删除我的评论。当我看到正则表达式解决方案时,我认为他们在 SW 经验或教育中遗漏了一些东西。【参考方案2】:

这是 C# 中的解决方案。我在 Visual Studio 2012 中进行了测试,它可以工作

public static int LongestSubstNonrepChar(string str) 

        int curSize = 0;
        int maxSize = 0;
        int end = 0;
        bool[] present = new bool[256];


        for (int start = 0; start < str.Length; start++) 
            end = start;
            while (end < str.Length) 
                if (!present[str[end]] && end < str.Length)
                
                    curSize++;
                    present[str[end]] = true;
                    end++;
                
                else
                    break;
            
            if (curSize > maxSize) 
                maxSize = curSize;
            
            //reset current size and the set all letter to false
            curSize = 0;
            for (int i = 0; i < present.Length; i++)
                present[i] = false;
        

            return maxSize;
    

【讨论】:

这是一个干净的解决方案!我不敢相信没有人支持,在 leetcode 上通过了所有 ~1000 个案例 您可以通过在 for 循环中添加 if 语句来优化此解决方案,例如 ::::: if (maxSize 【参考方案3】:

这个怎么样:

public static String getLongestSubstringNoRepeats( String string )
    int iLongestSoFar = 0;
    int posLongestSoFar = 0;
    char charPrevious = 0;
    int xCharacter = 0;
    int iCurrentLength = 0;
    while( xCharacter < string.length() )
        char charCurrent = string.charAt( xCharacter );
        iCurrentLength++;
        if( charCurrent == charPrevious )
            if( iCurrentLength > iLongestSoFar )
                iLongestSoFar = iCurrentLength;
                posLongestSoFar = xCharacter;
            
            iCurrentLength = 1;
        
        charPrevious = charCurrent;
        xCharacter++;
    
    if( iCurrentLength > iLongestSoFar )
        return string.substring( posLongestSoFar );
     else 
        return string.substring( posLongestSoFar, posLongestSoFar + iLongestSoFar );
    

【讨论】:

这只防止重复的相邻字符。给定字符串“nobody”,它将报告整个字符串没有重复字符,但 OP 的期望输出将是“body”。 根据帖子“说字符串是 AABGAKGIMN 然后 BGAKGIMN 是解决方案”。在这个“解决方案”中,G 是相同的。 getLongestSubstringNoRepeats("AABGAKGIMN") doesn't appear to terminate。除此之外,我认为您返回的长度存在错误(可能相差 1 或 2)。然后你返回长度,而不是实际的字符串。 @Dukeling 我想我不明白为什么“BGAKGIMN”是正确的而“ABGAKGIMN”不是。 推测BGAKGIMN 是正确的,因为A 不能出现,因为它是原始字符串中的重复字符。我只是按照示例进行操作,解释导致我遇到一个完全不同的问题。【参考方案4】:

相当棘手的问题,我给你一个基于 C# 的 O(n) 解决方案。

公共字符串 MaxSubStringKUniqueChars(string source, int k)

if (string.IsNullOrEmpty(source) || k > source.Length) return string.Empty;

        var start = 0;
        var ret = string.Empty;
        IDictionary<char, int> dict = new Dictionary<char, int>();

        for (var i = 0; i < source.Length; i++)
        
            if (dict.ContainsKey(source[i]))
            
                dict[source[i]] = 1 + dict[source[i]];
            
            else
            
                dict[source[i]] = 1;
            

            if (dict.Count == k + 1)
            
                if (i - start > ret.Length)
                
                    ret = source.Substring(start, i - start);
                

                while (dict.Count > k)
                
                    int count = dict[source[start]];
                    if (count == 1)
                    
                        dict.Remove(source[start]);
                    
                    else
                    
                        dict[source[start]] = dict[source[start]] - 1;
                    

                    start++;
                
            

        
        //just for edge case like "aabbcceee", should return "cceee"
        if (dict.Count == k && source.Length - start > ret.Length)
        
            return source.Substring(start, source.Length - start);
        

        return ret;
    

`

//这是测试用例。

    public void TestMethod1()
    
        var ret = Item001.MaxSubStringKUniqueChars("aabcd", 2);
        Assert.AreEqual("aab", ret);

        ret = Item001.MaxSubStringKUniqueChars("aabbccddeee", 2);
        Assert.AreEqual("ddeee", ret);

        ret = Item001.MaxSubStringKUniqueChars("abccccccccaaddddeeee", 3);
        Assert.AreEqual("ccccccccaadddd", ret);

        ret = Item001.MaxSubStringKUniqueChars("ababcdcdedddde", 2);
        Assert.AreEqual("dedddde", ret);
    

【讨论】:

非常简洁的解决方案。【参考方案5】:

设 s 为给定的字符串,n 为它的长度。

将 f(i) 定义为 s 的最长 [连续] 子串,以 s[i] 结尾且字母不同。这是独一无二且定义明确的。

计算每个 i 的 f(i)。由 f(i-1) 和 s[i] 很容易推导出来:

如果字母 s[i] 在 f(i-1) 中,令 j 为最大位置 j 否则,f(i) 就是 f(i-1) 加上 s[i]。

您的问题的解决方案是任何最大长度的 f(i)(不一定是唯一的)。

您可以实现此算法以在 O(n * 26) 时间内运行,其中 26 是字母表中的字母数。

【讨论】:

【参考方案6】:

public static int longNonDupSubstring(char[] str)

    int maxCount = 0;
    int count = 0;
    int maxEnd = 0;

    for(int i=1;i < str.length;i++) 

        if(str[i] != str[i-1]) 
            count++;
        

        if (str[i] == str[i-1]) 
            if(maxCount<count) 
                maxCount = count;
                maxEnd = i;
            
            count = 0;
        

        if ( i!=str.length-1 && str[i] == str[i+1]) 
            if(maxCount<count) 
                maxCount = count - 1;
                maxEnd = i-1;
            
            count = 0;
        
    

    int startPos = maxEnd - maxCount + 1;
    for(int i = 0; i < maxCount; i++) 

        System.out.print(str[startPos+i]);
    

    return maxCount;

【讨论】:

【参考方案7】:
  //Given a string ,find the longest sub-string with all distinct characters in it.If there are multiple such strings,print them all.
  #include<iostream>
  #include<cstring>
  #include<array>

  using namespace std;

 //for a string with all small letters
 //for capital letters use 65 instead of 97

 int main()
 

  array<int ,26> count ;

  array<string,26>largest;

  for(int i = 0 ;i <26;i++)
  count[i]=0;

  string s = "abcdefghijrrstqrstuvwxyzprr";
  string out = "";

  int k = 0,max=0;

  for(int i = 0 ; i < s.size() ; i++)
      
         if(count[s[i] - 97]==1)
             
              int loc = out.find(s[i]);

              for(int j=0;j<=loc;j++)   count[out[j] - 97]=0;

              if(out.size() > max) 
                   
                   max = out.size();
                   k=1;
                   largest[0] = out;
                 
             else if(out.size()==max) largest[k++]=out;

             out.assign(out,loc+1,out.size()-loc-1);
           
         out = out + s[i];
         count[s[i] - 97]++;
      

   for(int i=0;i<k;i++)  cout<<largest[i] << endl;
   //output will be
   // abcdefghijr
   // qrstuvwxyzp

  

【讨论】:

请描述您想要实现的什么以及如何(避免像97这样的幻数,尤其是当有'a'时)。您可以为您的解决方案提供的时间复杂度的最严格限制是多少?这个怎么回答Can anyone suggest a better way to do it?【参考方案8】:

让我也贡献一点。我有这个复杂的解决方案将是 O(N)。该算法的空间复杂度为 O(K),其中 K 是输入字符串中不同字符的数量。

public static int NoRepeatSubstring(string str)
    
        int start = 0;
        int maxLen = 0;
        Dictionary<char, int> dic = new Dictionary<char, int>();
        for (int i = 0; i < str.Length; i++)
        
            char rightChar = str[i];
            // if the map already contains the 'rightChar', shrink the window from the beginning so that
            // we have only one occurrence of 'rightChar'
            if (dic.ContainsKey(rightChar))
            
                // this is tricky; in the current window, we will not have any 'rightChar' after its previous index
                // and if 'start' is already ahead of the last index of 'rightChar', we'll keep 'windowStart'
                start = Math.Max(start, dic[rightChar] + 1);
            

            if (dic.ContainsKey(str[i]))
                dic[str[i]] = i;
            else
                dic.Add(str[i], i);

            maxLen = Math.Max(maxLen, i - start + 1);

        
        return maxLen;
    

还有一些单元测试:

Assert.Equal(3, SlideWindow.NoRepeatSubstring("aabccbb"));
Assert.Equal(2, SlideWindow.NoRepeatSubstring("abbbb"));
Assert.Equal(3, SlideWindow.NoRepeatSubstring("abccde"));

【讨论】:

【参考方案9】:
string MaximumSubstringNonRepeating(string text)

    string max = null;
    bool isCapture = false;
    foreach (string s in Regex.Split(text, @"(.)\1+"))
    
        if (!isCapture && (max == null || s.Length > max.Length))
        
            max = s;
        
        isCapture = !isCapture;
    
    return max;

. 匹配任何字符。 ( ) 捕获该字符。 \1 再次匹配捕获的字符。 + 重复该字符。整个模式匹配任何一个字符的两个或多个重复。 "AA"",,,,"

Regex.Split() 在模式的每次匹配时拆分字符串,并返回介于两者之间的片段数组。 (一个警告:它还包括捕获的子字符串。在这种情况下,重复的一个字符。捕获将显示在片段之间。这是我刚刚添加 isCapture 标志的方式。)

该函数将所有重复字符截去,返回每组重复字符之间最长的一段。

>>> MaximumSubstringNonRepeating("AABGAKG") // "AA" is repeated
"BGAKG"

>>> MaximumSubstringNonRepeating("AABGAKGIMNZZZD") // "AA" and "ZZZ" are repeated.
"BGAKGIMN"

【讨论】:

我的正则表达式很弱,你能解释一下它是如何工作的吗? --thnx

以上是关于如何找到没有重复字符的最长子字符串?的主要内容,如果未能解决你的问题,请参考以下文章

最长不重复子串

给定一个字符串,找到最长子串的长度,而不重复字符。

c_cpp 在一个长字符串中,找到出现不止一次的最长子字符串,也称为最长的重复子字符串。

代码题(56)— 最长重复子串无重复字符的最长子串

算法--最长无重复字符子串

java 没有重复字符的最长子串 - 最长无复复字符子串