【问题标题】:Trimming duplicate characters with single character in string用字符串中的单个字符修剪重复字符
【发布时间】:2018-05-26 08:00:21
【问题描述】:

这是一个面试问题 - 你将如何有效地用单个字符修剪字符串中的重复字符。

示例: 假设这是输入字符串

"reeeturrrrnneedd"

输出应该是:

“返回”

我通过使用拆分字符串和循环遍历char数组来解释它,但面试官不相信答案说这不是有效的方法。

private void test()
{
    string s = "reeeturrrnneeddryyf";
    StringBuilder sb = new StringBuilder();
    char pRvChar = default(char);
    foreach (var item in s.ToCharArray())
    {                
        if (pRvChar == item)
        {
            continue;
        }
        pRvChar = item;
        sb.Append(pRvChar);
    }

    MessageBox.Show(sb.ToString());
}

然后我想到了 Linq 反对并使用 distinct,但它会给出不正确的输出,因为它会删除所有重复的字符并且输出将是“reund”

谁能告诉我更有效的方法?

【问题讨论】:

    标签: c# .net regex string linq


    【解决方案1】:

    这是一个正则表达式的解决方案:

    Regex regex = new Regex( "(.)\\1+" );
    string result = regex.Replace( s,"$1" );
    

    我不确定,这是否比您的“for”循环在执行时间方面更有效,但在开发人员工作方面更有效。 并且易于阅读,至少对于熟悉正则表达式的人来说是这样。

    【讨论】:

    • 感谢@HeinzKessler,我想他早就料到了。如果您能对上面的表达式进行详细说明,这将对我进一步使用非常有帮助。
    • 我很高兴地解释一下:“()”的意思是:将括号中表达式找到的字符存储为所谓的捕获组。因为这是第一个(也是唯一一个),所以它是捕获组 1。
    • \1 表示:捕获组 1 的内容,+ 表示:一个或多个重复。 regex.replace() 调用替换了找到的模式,在本例中为“$1”,这意味着:捕获组 1。要学习和使用正则表达式,我强烈推荐 debuggex.comultrapico.com/expresso.htm
    【解决方案2】:

    如果您这样做,您当前的解决方案可能会更理想

    1. 预分配StringBuilder 的容量,在您的情况下这是可能的,因为结果字符串最多与输入字符串的长度相同:

      StringBuilder sb = new StringBuilder(s.Length);
      
    2. 避免构造一个 char 数组进行迭代。 string 类实现了IEnumerable<char>,因此您可以将其直接提供给foreach

      foreach (var item in s)
      

    【讨论】:

      【解决方案3】:

      赛马!

      这是一个使用 fixedunsafe 的指针版本,并带有预分配的结果

      unsafe string Mine()
      {
         var temp = string.Copy(Input);
         var i = 0;
         fixed (char* pInput = Input, pTemp = temp)
         {
            var plen = pInput + Input.Length;
            for (var pI = pInput + 1; pI < plen; pI++)
               if (*pI != *(pTemp+i))
                  *(pTemp + ++i) = *pI;            
         }   
         return temp.Substring(0,i+1);
      }
      

      结果

      Mode            : Release
      Test Framework  : .NET Framework 4.7.1
      Benchmarks runs : 100 times (averaged)
      

      长度:100

      Name     |  Average |  Fastest | StDv |  Cycles | Pass
      ---------------------------------------------------------
      Mine     | 0.003 ms | 0.002 ms | 0.00 |   3,935 | Yes
      Dmitry   | 0.003 ms | 0.002 ms | 0.00 |   5,455 | Yes
      Billy    | 0.003 ms | 0.002 ms | 0.00 |   5,706 | Yes
      SAkbari3 | 0.007 ms | 0.006 ms | 0.00 |  19,730 | Yes
      SAkbari1 | 0.009 ms | 0.008 ms | 0.00 |  25,029 | Yes
      Original | 0.009 ms | 0.003 ms | 0.05 |   7,349 | Base
      SAkbari2 | 0.014 ms | 0.011 ms | 0.00 |  41,027 | Yes
      Heinz    | 0.040 ms | 0.037 ms | 0.00 | 131,196 | Yes
      

      长度:1,000

      Name     |  Average |  Fastest | StDv |    Cycles | Pass
      -----------------------------------------------------------
      Mine     | 0.005 ms | 0.004 ms | 0.00 |    11,126 | Yes
      Billy    | 0.008 ms | 0.007 ms | 0.00 |    22,402 | Yes
      Dmitry   | 0.009 ms | 0.006 ms | 0.00 |    24,487 | Yes
      Original | 0.010 ms | 0.008 ms | 0.00 |    27,334 | Base
      SAkbari3 | 0.041 ms | 0.040 ms | 0.00 |   136,272 | Yes
      SAkbari1 | 0.075 ms | 0.049 ms | 0.05 |   231,981 | Yes
      SAkbari2 | 0.101 ms | 0.076 ms | 0.03 |   334,375 | Yes
      Heinz    | 0.344 ms | 0.267 ms | 0.07 | 1,154,860 | Yes
      

      长度:10,000

      Name     |  Average |  Fastest | StDv |     Cycles | Pass
      ------------------------------------------------------------
      Mine     | 0.020 ms | 0.017 ms | 0.00 |     62,571 | Yes
      Dmitry   | 0.056 ms | 0.046 ms | 0.01 |    185,538 | Yes
      Billy    | 0.061 ms | 0.058 ms | 0.00 |    202,931 | Yes
      Original | 0.069 ms | 0.058 ms | 0.01 |    230,297 | Base
      SAkbari3 | 0.419 ms | 0.372 ms | 0.09 |  1,418,448 | Yes
      SAkbari1 | 0.535 ms | 0.452 ms | 0.09 |  1,813,644 | Yes
      SAkbari2 | 0.957 ms | 0.726 ms | 0.19 |  3,226,844 | Yes
      Heinz    | 2.951 ms | 2.574 ms | 0.47 | 10,027,205 | Yes
      

      长度:100,000

      Name     |   Average |   Fastest | StDv |     Cycles | Pass
      --------------------------------------------------------------
      Mine     |  0.164 ms |  0.158 ms | 0.01 |    552,166 | Yes
      Dmitry   |  0.498 ms |  0.467 ms | 0.02 |  1,690,471 | Yes
      Original |  0.561 ms |  0.523 ms | 0.06 |  1,894,019 | Base
      Billy    |  0.576 ms |  0.536 ms | 0.04 |  1,955,072 | Yes
      SAkbari3 |  3.684 ms |  3.429 ms | 0.15 | 12,534,942 | Yes
      SAkbari1 |  4.547 ms |  4.084 ms | 0.47 | 15,468,091 | Yes
      SAkbari2 |  7.315 ms |  6.848 ms | 0.30 | 24,888,849 | Yes
      Heinz    | 26.091 ms | 24.898 ms | 1.17 | 88,905,648 | Yes
      

      长度:1,000,000

      Name     |    Average |    Fastest | StDv |      Cycles | Pass
      -----------------------------------------------------------------
      Mine     |   1.841 ms |   1.549 ms | 0.20 |   6,256,290 | Yes
      Dmitry   |   5.237 ms |   4.740 ms | 0.27 |  17,808,335 | Yes
      Original |   5.705 ms |   5.178 ms | 0.31 |  19,411,876 | Base
      Billy    |   6.027 ms |   5.374 ms | 0.31 |  20,477,533 | Yes
      SAkbari3 |  39.369 ms |  36.608 ms | 2.27 | 134,030,971 | Yes
      SAkbari1 |  46.502 ms |  44.410 ms | 1.67 | 158,181,468 | Yes
      SAkbari2 |  74.398 ms |  72.187 ms | 1.41 | 253,311,101 | Yes
      Heinz    | 259.090 ms | 254.766 ms | 2.62 | 881,738,225 | Yes
      

      总结

      正则表达式很烂!

      【讨论】:

      • 我很少使用regx,它慢吗?我接受 regx 答案,因为它看起来非常清晰和独特。我不知道它在内部是如何工作的,以及返回输出需要多少成本。
      • @NeerajKumarGupta 正则表达式比你原来的解决方案慢了大约 50 倍,而且字符串越大越糟糕。在这些情况下使用正则表达式就像试图用锤子抓鱼
      • “在这些情况下使用正则表达式就像试图用锤子钓到鱼一样” - 您是指什么样的“这些情况”?例如。如果字符串是用户输入的并且有几十个字符,那么没有人关心性能。这是另一个故事,在每秒有 1000 个请求的 Web 服务器上,或者在一个巨大的数据库项目上。因此,为了证明您的陈述的合理性,您应该更具体地使用“情况”一词。此外,Regex 可以在运行时使用“RegexOptions.Compiled”选项编译为 IL 代码。
      • 你真的认为面试官期望一个“不安全”的解决方案吗?我经常进行面试,有趣的部分是,候选人是否回问了正确的问题(以及所有问题),例如。关于上下文、字符串的典型长度、面试官对“高效”的含义等。
      • @HeinzKessler 哇,咆哮了吗?我一直使用正则表达式,并且已经使用了很多年。但它经常被年轻的绝地武士滥用。但是对于这个在乎的练习来说,我什至标记了你的回答。然而,在这种情况下,它比其他任何事情都慢了大约 50 倍,而且肯定不会更高效
      【解决方案4】:

      一些建议:

      • 在您知道字符串至少包含一个重复字符之前,请勿创建 StringBuilder
      • 为避免在 StringBuilder 内调整大小,请使用获取初始容量的构造函数并将原始长度减去 1。
      • 不要调用ToCharArray(),因为它会分配一个新数组并将字符串复制到其中。

        public static string RemoveRepeatedChars(string s)
        {
            if ((s == null) || (s.Length < 2))
                return s;
        
            // Return original string if no repeated char
            int i = 1;
            while ((i < s.Length) && (s[i] != s[i - 1]))
                i++;
            if (i == s.Length)
                return s;
        
            // i is index of first repeat
            var sb = new StringBuilder(s.Length - 1);
            sb.Append(s, 0, i); // add everything before the first repeat
            char prevChar = s[i];
            i++; // skip the first repeat
            for (; i < s.Length; i++)
            {
                if (s[i] != prevChar)
                {
                    sb.Append(s[i]);
                    prevChar = s[i];
                }
            }
            return sb.ToString();
        }
        

      【讨论】:

      • 这实际上做得很好,但扩展性不太好,但比大多数都好
      【解决方案5】:

      这是一个 LINQ 解决方案:

      string s = "reeeturrrnneeddryyf";
      var result = string.Join("", s.Where((x, index) => index == s.Length - 1
                                   || x != s[index + 1]).ToArray());
      

      或者其他方式:

      var result = string.Join("", s.Zip(s.Skip(1), (first, second) => new[] { first, second })
                           .Where(z => z[0] != z[1]).Select(c => c[0])
                           .Concat(new[] { s[s.Length - 1] }));
      

      或者其他方式:(使用扩展方法)

      public static class Extensions
      {
          public static IEnumerable<T> TrimmingDuplicateCharacters<T>(this IEnumerable<T> source)
          {
              using (var iterator = source.GetEnumerator())
              {
                  var comparer = EqualityComparer<T>.Default;
      
                  if (!iterator.MoveNext())
                      yield break;
      
                  var current = iterator.Current;
                  yield return current;
      
                  while (iterator.MoveNext())
                  {
                      if (comparer.Equals(iterator.Current, current))
                          continue;
      
                      current = iterator.Current;
                      yield return current;
                  }
              }
          }
      }
      

      然后:

      var result = string.Join("", s.TrimmingDuplicateCharacters());
      

      【讨论】:

        【解决方案6】:
        //Program using C Language
        #include <stdio.h>
        int main()
        {
            char a[] = "rrrrreetttuurrneed";
            int i = 0;
            while(a[i])
            {
                if(a[i] != a[i+1])
                printf("%c", a[i]);
                i++;
            }
            printf("\n");
            return 0;
        }
        

        【讨论】:

        • 您还应该解释您的代码以帮助未来的访问者
        • 很好的答案,错误的语言......实际上并没有产生结果
        • 它按预期生成结果!语言无所谓,你需要的只是一种高效的算法和一种能产生更好结果的高效编程语言。
        猜你喜欢
        • 2014-03-26
        • 2017-10-16
        • 2010-10-13
        • 1970-01-01
        • 1970-01-01
        • 2015-12-12
        • 1970-01-01
        • 1970-01-01
        • 2011-04-04
        相关资源
        最近更新 更多