【问题标题】:String Substrings Generation in JavaJava中的字符串子串生成
【发布时间】:2012-03-13 04:02:54
【问题描述】:

我正在尝试查找给定字符串中的所有子字符串。对于像rymis 这样的随机字符串,子序列将是[i, is, m, mi, mis, r, ry, rym, rymi, rymis, s, y, ym, ymi, ymis]。从Wikipedia 开始,长度为n 的字符串将有n * (n + 1) / 2 的子字符串总数。

可以通过以下sn-p的代码找到:

    final Set<String> substring_set = new TreeSet<String>();
    final String text = "rymis";

    for(int iter = 0; iter < text.length(); iter++)
    {
        for(int ator = 1; ator <= text.length() - iter; ator++)
        {
            substring_set.add(text.substring(iter, iter + ator));
        }
    }

这适用于较小的字符串长度,但由于算法接近O(n^2),因此对于较大的长度显然会减慢。

还阅读了可以在O(n) 中进行插入的后缀树,并注意到可以通过从右侧删除 1 个字符直到字符串为空来重复插入子字符串来获得相同的子序列。应该是关于O(1 + … + (n-1) + n) 这是一个summation of n -> n(n+1)/2 -> (n^2 + n)/ 2,这又是在O(n^2) 附近。尽管似乎有一些后缀树可以在log2(n) 时间进行插入,这将是O(n log2(n)) 更好的一个因素。

在我深入研究 Suffix Trees 之前,这是否是正确的路线,是否有其他算法对此更有效,或者O(n^2) 是否与此一样好?

【问题讨论】:

  • 由于该集合包含 n * (n + 1) / 2 个值,因此您必须对集合执行 n * (n + 1) / 2 次插入,所以我不知道如何该算法可能小于 O(n^2)。
  • @JBNizet - 我同意,没有办法避免触及每个子字符串元素。由于原始集合的大小为n,并且大约有n^2个元素要访问,因此这很可能无法提高效率。
  • 这不是家庭作业。使用发布的其他两种算法它们都比我原来的慢,但我注意到数据结构可能不是最优的。如果算法已经产生了唯一的子字符串,那么不需要 TreeSet(数据结构可以稍后排序),并且动态数组也会因为插入量大而效率低下(需要扩展其内部数组并复制)。
  • 通过一些测试,所有三种算法都会生成正确的答案。我在原始帖子中的算法在经验上是最快的,因为它具有较少的恒定时间成本,但并没有显着差异。当像aba 这样添加带有重复字符的字符串时,问题变得更加复杂,其中子字符串开始变得重复,那么就不能再保证结构只包含唯一元素。如果可以确保它确实如此,那么像LinkedList 这样的数据结构将比*SetArrayList 更快地提高速度。
  • @ntin - 重复应该不是问题,因为您总是可以比当前瓶颈更快地删除它们。对数组进行堆排序,然后遍历它,如果前一项相同,则删除当前项。这两个操作应该分别是O(n log n)和O(n)。

标签: java string substring


【解决方案1】:

这是您示例的倒置方式,但仍然是 o(n^2)。

string s = "rymis";
ArrayList<string> al = new ArrayList<string>();
for(int i = 1; i < s.length(); i++){//collect substrings of length i
 for(int k = 0; k < s.length(); k++){//start index for sbstr len i
  if(i + k > s.length())break;//if the sbstr len i runs over end of s move on
  al.add(s.substring(k, k + i));//add sbstr len i at index k to al
 }
}

让我看看我是否可以发布一个递归示例。我开始进行几次递归尝试,并提出了这种使用双滑动窗口的迭代方法,作为对上述方法的一种改进。我想到了一个递归示例,但在减小树大小时遇到​​了问题。

string s = "rymis";
ArrayList<string> al = new ArrayList<string>();
for(int i = 1; i < s.length() + 1; i ++)
{
 for(int k = 0; k < s.length(); k++)
 {
  int a = k;//left bound window 1
  int b = k + i;//right bound window 1
  int c = s.length() - 1 - k - i;//left bound window 2
  int d = s.length() - 1 - k;//right bound window 2
  al.add(s.substring(a,b));//add window 1
  if(a < c)al.add(s.substring(c,d));//add window 2
 }
}

提到了一个使用 arraylist 影响性能的问题,因此下一个将使用更基本的结构。

string s = "rymis";
StringBuilder sb = new StringBuilder();
for(int i = 1; i < s.length() + 1; i ++)
{
 for(int k = 0; k < s.length(); k++)
 {
  int a = k;//left bound window 1
  int b = k + i;//right bound window 1
  int c = s.length() - 1 - k - i;//left bound window 2
  int d = s.length() - 1 - k;//right bound window 2
  if(i > 1 && k > 0)sb.append(",");
  sb.append(s.substring(a,b));//add window 1
  if(a < c){
   sb.append(",");
   sb.append(s.substring(c,d));//add window 2
  }
 }
}
string s = sb.toString();
String[] sArray = s.split("\\,");

【讨论】:

    【解决方案2】:

    我很确定您无法为此击败 O(n^2),正如问题 cmets 中提到的那样。

    我对不同的编码方式很感兴趣,所以我很快做了一个,我决定在这里发布。

    我不认为我在这里提出的解决方案不是渐近更快,但是当计算内部和外部循环时,会更少。这里的重复插入也更少 - 没有重复插入。

    String str = "rymis";
    ArrayList<String> subs = new ArrayList<String>();
    while (str.length() > 0) {
        subs.add(str);
        for (int i=1;i<str.length();i++) {
            subs.add(str.substring(i));
            subs.add(str.substring(0,i));
        }
        str = str.substring(1, Math.max(str.length()-1, 1));
    }
    

    【讨论】:

      【解决方案3】:

      我不确定确切的算法,但您可以查看 Ropes:

      http://en.wikipedia.org/wiki/Rope_(computer_science)

      总而言之,绳索更适合数据量大且经常修改的情况。

      对于您的问题,我相信 Rope 的性能优于 String。

      【讨论】:

        猜你喜欢
        • 2016-02-22
        • 2021-12-26
        • 1970-01-01
        • 2011-12-02
        • 1970-01-01
        • 2014-01-04
        • 1970-01-01
        • 2015-02-22
        • 2021-02-02
        相关资源
        最近更新 更多