【问题标题】:Testing for repeated characters in a string测试字符串中的重复字符
【发布时间】:2009-05-06 13:24:53
【问题描述】:

我正在使用字符串做一些工作,并且我需要确定一个字符串(通常是小于 10 个字符的小字符串)是否包含重复字符。

`ABCDE`  // does not contain repeats 
`AABCD`  // does contain repeats, ie A is repeated

我可以遍历 string.ToCharArray() 并针对 char[] 中的每个其他字符测试每个字符,但我觉得我错过了一些明显的东西......也许我只需要咖啡。有人可以帮忙吗?

编辑:

字符串会被排序,所以顺序并不重要,所以 ABCDA => AABCD

重复的频率也很重要,所以我需要知道重复是对还是三重等。

【问题讨论】:

  • “ABCDA”会被视为重复吗? IE。您对任何重复字符或连续字符感兴趣吗?
  • 什么版本的框架?
  • 框架版本为3.5

标签: c# algorithm string


【解决方案1】:

如果字符串已排序,您只需依次记住每个字符并检查以确保下一个字符与最后一个字符不完全相同。

除此之外,对于十个字符以下的字符串,仅针对所有其他字符测试每个字符可能与大多数其他事情一样快或更快。正如另一位评论者所建议的那样,位向量可能会更快(如果您有少量合法字符,则会有所帮助。)

奖励:这里有一个巧妙的 LINQ 解决方案来实现 Jon 的功能:

int longestRun =
    s.Select((c, i) => s.Substring(i).TakeWhile(x => x == c).Count()).Max();

所以,好吧,它不是很快!你有问题吗?!

:-)

【讨论】:

  • 虽然不是很优雅...一个漂亮的小 LINQ 语句会非常简洁。
  • 没错,但如果他甚至问这个问题,我认为性能很重要。
【解决方案2】:

如果字符串很短,那么循环和测试可能是最简单和最有效的方法。我的意思是您可以创建一个哈希集(在您使用的任何平台上)并遍历字符,如果字符已经在集合中则失败,否则将其添加到集合中 - 但这只是当字符串更长时可能会提供任何好处。

编辑:现在我们知道它已排序,mquander's answer 是最好的 IMO。这是一个实现:

public static bool IsSortedNoRepeats(string text)
{
    if (text.Length == 0)
    {
        return true;
    }
    char current = text[0];
    for (int i=1; i < text.Length; i++)
    {
        char next = text[i];
        if (next <= current)
        {
            return false;
        }
        current = next;
    }
    return true;
}

如果您不介意重复使用索引器,可以使用较短的替代方法:

public static bool IsSortedNoRepeats(string text)
{
    for (int i=1; i < text.Length; i++)
    {
        if (text[i] <= text[i-1])
        {
            return false;
        }
    }
    return true;
}

编辑:好的,在“频率”方面,我会稍微扭转一下这个问题。我仍然会假设字符串是排序的,所以我们想知道最长运行的长度。当没有重复时,最长的运行长度将为 0(对于空字符串)或 1(对于非空字符串)。否则,将是 2 个或更多。

首先是特定于字符串的版本:

public static int LongestRun(string text)
{
    if (text.Length == 0)
    {
        return 0;
    }
    char current = text[0];
    int currentRun = 1;
    int bestRun = 0;

    for (int i=1; i < text.Length; i++)
    {
        if (current != text[i])
        {
            bestRun = Math.Max(currentRun, bestRun);
            currentRun = 0;
            current = text[i];
        }
        currentRun++;
    }
    // It's possible that the final run is the best one
    return Math.Max(currentRun, bestRun);
}

现在我们也可以将其作为IEnumerable&lt;T&gt; 上的通用扩展方法:

public static int LongestRun(this IEnumerable<T> source)
{
    bool first = true;
    T current = default(T);
    int currentRun = 0;
    int bestRun = 0;

    foreach (T element in source)
    {
        if (first || !EqualityComparer<T>.Default(element, current))
        {
            first = false;
            bestRun = Math.Max(currentRun, bestRun);
            currentRun = 0;
            current = element;
        }
    }
    // It's possible that the final run is the best one
    return Math.Max(currentRun, bestRun);
}

然后你可以打电话给"AABCD".LongestRun()例如。

【讨论】:

  • 这正是我会做的。 +1
  • 我还以为你是 LINQ 传道者:P
  • 我是合适的 LINQ 的粉丝。在这种情况下,我认为不是。
  • 我还没有深入研究 MSIL 来检查,但我的假设是编译器会将 LINQ 优化为类似于循环。你能详细说明为什么你认为这更合适吗?我认为您对此的意见将非常有用。
  • 没有发现频率位。我有时间会编辑。
【解决方案3】:

这会很快告诉你如果一个字符串包含重复:

bool containsDups = "ABCDEA".Length != s.Distinct().Count();

它只是根据原始长度检查不同字符的数量。如果它们不同,则说明您有重复...

编辑:我想这并没有考虑到你在编辑中提到的重复频率......但是这里的一些其他建议已经解决了这个问题,所以我不会发布代码,因为我注意到其中一些已经为您提供了一个相当优雅的解决方案。我特别喜欢 Joe 使用 LINQ 扩展的实现。

【讨论】:

  • 您可以删除 .ToCharArray(),它只使用 s.Distinct().Count() 就可以正常工作...
【解决方案4】:

由于您使用的是 3.5,因此您可以在一个 LINQ 查询中执行此操作:

var results = stringInput
  .ToCharArray() // not actually needed, I've left it here to show what's actually happening
  .GroupBy(c=>c)
  .Where(g=>g.Count()>1)
  .Select(g=>new {Letter=g.First(),Count=g.Count()})
;

对于在输入中多次出现的每个字符,这将为您提供字符和出现次数。

【讨论】:

  • 您可以通过检查 distincts 来进一步压缩这一点……如果不同数量的差异与实际不同,那么您就有了重复项。
  • OP 想知道哪些字母被重复,以及出现的次数,因此我在上面的解决方案。
  • @Bob 如 OPs 编辑中所述,这会处理更精简的解决方案可能不会出现的频率。
  • +1 不错的解决方案,正如我在我的文章中指出的那样,只要您不需要频率,它就会变得更加简单。
  • 虽然它适用于这个解决方案,但您应该选择 g.Key,而不是 g.First()
【解决方案5】:

我认为最简单的方法是使用这个简单的正则表达式

bool foundMatch = false;
foundMatch = Regex.IsMatch(yourString, @"(\w)\1");

如果您需要有关比赛的更多信息(开始、长度等)

        Match match = null;
    string testString = "ABCDE AABCD";
    match = Regex.Match(testString, @"(\w)\1+?");
    if (match.Success)
    {
        string matchText = match.Value; // AA
        int matchIndnex = match.Index;  // 6
        int matchLength = match.Length; // 2
    }

【讨论】:

    【解决方案6】:

    更新现在,您需要一组计数器来维护计数。

    保留一个位数组,其中一位代表一个唯一字符。遇到字符时打开该位,并在字符串上运行一次。位数组索引和字符集的映射由您决定。如果您看到某个特定位已经打开,请中断。

    【讨论】:

    • +1。 HashSet 也是有效的,但由于这个问题仅限于 26 个项目,因此 bit / bool 数组会更快。
    • 如果问的不是太多,有人可以提供一个实现吗?
    • 问题现已被编辑,此答案不再有效,因为无法通过这种方式获得重复的频率。
    【解决方案7】:

    怎么样:

    string strString = "AA BRA KA DABRA";
    
    var grp = from c in strString.ToCharArray() 
            group c by c into m
            select new { Key = m.Key, Count = m.Count() };
    
    foreach (var item in grp)
    {
        Console.WriteLine(
            string.Format("Character:{0} Appears {1} times", 
            item.Key.ToString(), item.Count));
    }
    

    【讨论】:

    • 与乔的相同,但 +1 显示不同的语法。 btw String 实现 IEnumerable,不需要 ToCharArray()
    【解决方案8】:
    /(.).*\1/
    

    (或您的正则表达式库语法中的任何等效项)

    不是最有效的,因为它可能会回溯到字符串中的每个字符,然后再次向前扫描。而且我通常不提倡正则表达式。但是如果你想要简洁......

    【讨论】:

      【解决方案9】:

      我开始在网上寻找一些信息,我得到了以下解决方案。

      string input = "aaaaabbcbbbcccddefgg";
              char[] chars = input.ToCharArray();
              Dictionary<char, int> dictionary = new Dictionary<char,int>();
      
              foreach (char c in chars)
              {
                  if (!dictionary.ContainsKey(c))
                  {
                      dictionary[c] = 1; //
                  }
                  else
                  {
                      dictionary[c]++;
                  }
              }
      
              foreach (KeyValuePair<char, int> combo in dictionary)
              {
                  if (combo.Value > 1) //If the vale of the key is greater than 1 it means the letter is repeated
                  {
                      Console.WriteLine("Letter " + combo.Key + " " + "is repeated " + combo.Value.ToString() + " times");
                  }
      
              }
      

      我希望它有所帮助,我有一次面试,面试官要求我解决这个问题,我知道这是一个常见问题。

      【讨论】:

        【解决方案10】:

        当没有订单时,您可以使用字典来记录计数:

        String input = "AABCD";
        var result = new Dictionary<Char, int>(26);
        var chars = input.ToCharArray();
        foreach (var c in chars)
        {
            if (!result.ContainsKey(c))
            {
                result[c] = 0; // initialize the counter in the result
            }
            result[c]++;
        }
        
        foreach (var charCombo in result)
        {
            Console.WriteLine("{0}: {1}",charCombo.Key, charCombo.Value);   
        }
        

        【讨论】:

          【解决方案11】:

          Jon 描述的哈希解决方案可能是最好的。您可以使用 HybridDictionary,因为它适用于小型和大型数据集。其中字母是键,值是频率。 (每次添加失败或 .Contains(key) 的 HybridDictionary 返回 true 时更新频率)

          【讨论】:

            猜你喜欢
            • 2011-07-25
            • 2015-11-12
            • 1970-01-01
            • 1970-01-01
            • 2015-10-11
            • 2016-03-27
            • 2013-06-01
            • 2011-10-14
            • 1970-01-01
            相关资源
            最近更新 更多