【问题标题】:C# - Code optimization to get all substrings from a stringC# - 代码优化以从字符串中获取所有子字符串
【发布时间】:2018-03-20 20:35:13
【问题描述】:

我正在编写一个代码 sn-p 以从给定字符串中获取所有子字符串。

这是我使用的代码

 var stringList = new List<string>();
 for (int length = 1; length < mainString.Length; length++)
 {
    for (int start = 0; start <= mainString.Length - length; start++)
    {
       var substring = mainString.Substring(start, length);
       stringList.Add(substring);
    }
 }

对我来说它看起来不太好,有两个 for 循环。有没有其他方法可以以更好的时间复杂度实现这一目标。

我的观点是,为了获得一个子字符串,我肯定需要两个循环。有没有其他方法可以调查?

【问题讨论】:

  • 使用 substring 方法,即使你写错了 for loop 。缺少代码
  • 这听起来像是一个 XY 问题。您打算如何处理所有这些子字符串?
  • 来自给定输入的每个可能的子字符串?
  • @MichaelRandall 是的。正是
  • @Shyamsundarshah 我添加了 mainString.Substring(start, length)。代码到底缺在哪里?

标签: c# optimization


【解决方案1】:

字符串中子字符串的数量是O(n^2),所以一个循环内另一个循环是你能做的最好的。您的代码结构是正确的。

我会这样表述你的代码:

void Main()
{
    var stringList = new List<string>();
    string s = "1234";
    for (int i=0; i <s.Length; i++)
        for (int j=i; j < s.Length; j++)
            stringList.Add(s.Substring(i,j-i+1));
}

【讨论】:

  • 谢谢菲利普!
  • 那么我们没有办法改进 O(n^2) 吗?
  • 不,你不能改进 O(n^2),因为正好有 (n(n+1))/2 个子串,在大 O 表示法中是 O(n^2)。答案的数量决定了复杂性的下限,因此您无法改进它。
  • 我明白了。谢谢@phillip-ngan
【解决方案2】:

你确实需要 2 个 for 循环

Demo here

var input = "asd sdf dfg";
var stringList = new List<string>();

for (int i = 0; i < input.Length; i++)
{
    for (int j = i; j < input.Length; j++)
    {
        var substring = input.Substring(i, j-i+1);
        stringList.Add(substring);
    }
}

foreach(var item in stringList)
{
    Console.WriteLine(item);
}

更新

您无法改进迭代。

但是,您可以通过使用 fixed 数组和指针来提高性能

【讨论】:

  • 感谢迈克尔的努力!我只是想知道是否有任何方法可以提高时间复杂度。这里就和我写的方法一样
  • @PraneetNadkar 没有
  • 谢谢迈克尔 :)
【解决方案3】:

在某些情况下,您可以通过减少对象分配来显着提高执行速度。在这种情况下,通过使用单个 char[]ArraySegment&lt;of char&gt; 来处理子字符串。这也将导致使用更少的地址空间并减少垃圾收集器的负载。

Microsoft Docs 上Using the StringBuilder Class in .NET 页面的相关摘录:

String 对象是不可变的。每次使用System.String 类中的一种方法时,都会在内存中创建一个新的字符串对象,这需要为该新对象分配新的空间。在需要对字符串执行重复修改的情况下,与创建新的String 对象相关的开销可能会很昂贵。

示例实现:

static List<ArraySegment<char>> SubstringsOf(char[] value)
{
    var substrings = new List<ArraySegment<char>>(capacity: value.Length * (value.Length + 1) / 2 - 1);
    for (int length = 1; length < value.Length; length++)
        for (int start = 0; start <= value.Length - length; start++)
            substrings.Add(new ArraySegment<char>(value, start, length));
    return substrings;
}

有关详细信息,请查看 Microsoft Docs 上的Fundamentals of Garbage Collection 页面、StackOverflow 上的what is the use of ArraySegment class? 讨论、MSDN 上的ArraySegment<T> Structure 页面和 MSDN 上的List<T>.Capacity 页面。

【讨论】:

  • 感谢列昂尼德!我可以知道这个 ArraySegment 在搜索或插入方面是否比 List 更快?
  • 它们用于不同的目的。 ArraySegment&lt;of char&gt; 表示单个子字符串,List&lt;of string&gt; 表示一组子字符串。
【解决方案4】:

好吧,O(n**2)时间 复杂性是不可避免的,但是您可以尝试提高 空间 消耗。在许多情况下,您不希望所有子字符串都物化,例如List&lt;string&gt;:

public static IEnumerable<string> AllSubstrings(string value) {
  if (value == null) 
    yield break; // Or throw ArgumentNullException

  for (int length = 1; length < value.Length; ++length)
    for (int start = 0; start <= value.Length - length; ++start)
      yield return value.Substring(start, length);
}

例如,让我们计算"abracadabra" 中从a 开始且长度超过3 字符的所有子字符串。请注意,我们所要做的就是循环遍历子字符串而不将它们保存到列表中:

int count = AllSubstrings("abracadabra")
  .Count(item => item.StartsWith("a") && item.Length > 3);

如果您出于任何原因想要List&lt;string&gt;,只需添加.ToList()

var stringList = AllSubstrings(mainString).ToList(); 

【讨论】:

  • 可能if (AllSubstrings("abracadabra").Any("abc")) { ... } 不是最好的例子,因为它等价于if ("abracadabra".Contains("abc")) { ... },速度更快,可读性更强。
  • @Leonid Vasilyev:非常同意,谢谢!我提供了一个更复杂的例子(实现起来并不容易,一行)
  • 取决于Substring 方法的实现方式,这可能是 O(N^3)
猜你喜欢
  • 2019-12-08
  • 2011-05-25
  • 1970-01-01
  • 2012-01-26
  • 1970-01-01
  • 2015-02-13
  • 1970-01-01
相关资源
最近更新 更多