如何查找字符串的所有子字

时间:2015-01-21 19:40:15

标签: java string substring

我正在尝试了解如何查找给定字符串的所有可能组合(子字符串)。我想到了一个有效的算法,它基本上就像这样:

示例:"abc"

  1. 删除无 - 将"abc"添加到输出
  2. 删除第一个字符("bc") - 添加到输出,然后第二个("ac") - 添加到输出,然后删除第三个("ab") - 添加到输出。
  3. 删除2个字符("a""b""c")并添加到输出
  4. 现在,我不知道我将如何写这个,所以我要求一点帮助,没有什么先进的,因为这是我的hw,我想自己学习和做。更具体地说,我想知道如何在不改变输入的情况下从中间删除char。

    另外,"cb"对我来说不是一个子词,因为所有子词都是在原始字符串中显示的字符顺序。

5 个答案:

答案 0 :(得分:3)

考虑一下:您必须找到所有以第一个字符开头的子字,然后是第二个字符,然后是第三个字符......依此类推。

这可以写成一个递归算法,有两个参数:

  1. "前缀"
  2. 前缀
  3. 后的子词

    在第一次迭代中,前缀将是一个空字符串,您将逐渐用子字填充它并打印一个字符。

    我可以向您展示其工作原理的最简单方法是代码段:

    public void printAllSubWords(String prefix, String subword) {
        for(int i = 0; i < subword.length(); i++) {
            System.out.println(prefix + subword.charAt(i));
            printAllSubWords(prefix + subword.charAt(i), 
                             subword.substring(i + 1, subword.length()));
        }
    }
    

    这是如何运作的?

    首先,考虑一个长度为2的字符串:

    printAllSubWords("", "ab");
    

    执行顺序如下:

    i = 0时:

    • System.out.println(prefix + subword.charAt(i));将按如下方式进行评估:

      System.out.println("" + "ab".charAt(0));并打印a

    • 然后致电

      printAllSubWords(prefix + subword.charAt(i), subword.substring(i + 1, subword.length()));

      printAllSubWords("" + 'a', "ab".substring(0 + 1, "ab".length()));,即:

      printAllSubWords("a", "b");

    • 现在,在第二遍中,System.out.println(prefix + subword.charAt(i));将被评估为:

      System.out.println("a" + "b".charAt(0));并打印ab

    • 然后,仍然在第二次传递中,printAllSubWords(prefix + subword.charAt(i), subword.substring(i + 1, subword.length()));将是

      printAllSubWords("a" + 'b', "b".substring(0 + 1, "ab".length()));,即:

      printAllSubWords("ab", "");

    • 在第三次传递中,for将不会被执行,因为这个新子词("")的长度为零,所以我们返回到最顶层的呼叫。

    i = 1时:

    • System.out.println(prefix + subword.charAt(i));将按如下方式进行评估:

      System.out.println("" + "ab".charAt(1));并打印b

    • 然后致电

      printAllSubWords(prefix + subword.charAt(i), subword.substring(i + 1, subword.length()));

      printAllSubWords("" + 'b', "b".substring(0 + 1, "ab".length()));,即:

      printAllSubWords("b", "");

    • 在这个新的第二遍中,for将不会被执行,因为这个新子词("")的长度为零,所以我们返回到最顶层的调用,这将结束执行。

    尝试编写三个或四个字符的执行序列,看看会发生什么。

    希望这有帮助。


    在评论中,您说要将子词存储在数组中(并且您非常具体:您不需要列表,但需要一个简单的数组)。这是可能的,但它有一些问题。

    • 您需要事先了解 您需要为阵列提供多少条目。由于数组无法调整大小,因此您需要在事物开始之前进行计算。

    老实说,我会建议您使用List(具体来说,ArrayList),但是,请查看是否可以计算其长度。阵列。

    Word lenght | Number of subwords
    ------------+-------------------
      1         |   1
      2         |   3
      3         |   7
      4         |   15
      5         |   31
    

    This question and its accepted answer给了我一个长度为n的单词中有多少个子字的提示。我留给你弄明白了(提示:答案的最后部分是子序列数量的关键,但它包括子序列)。

    一种可能的解决方案是:

    1. 创建一个整数静态变量(类变量),用于保存您正在执行的迭代。每次打印/存储子词时,该数字从零开始并增加一个单位
    2. 在同一个类中,编写一个创建适当大小数组的方法。
    3. 修改上述方法,除了前缀和子词外,还接收这个新创建的数组。
    4. 使用我在步骤1中提到的静态变量作为索引,将System.out.println()内容与存储生成的子词的句子替换为数组。
    5. 再次调用该函数时,请务必同时传递该数组。
    6. 我会在几个小时后回来编写代码示例,但我希望您先尝试自己解决(另外,上面的链接让我想到了另一种方法解决这个不需要递归的问题,我将在以后的编辑中将其包括在内)


      我之前告诉过你的解决方案是这样的:

      public class SubwordPrinter2
      {
          private static int index;
          private static void generateSubwords(String prefix, String subword, String[] arr) {
              String s;
              for(int i = 0; i < subword.length(); i++) {
                  s = prefix + subword.charAt(i);
                  arr[index] = s;
                  index++;
                  generateSubwords(prefix + subword.charAt(i),
                                      subword.substring(i + 1, subword.length()),
                                      arr);
              }
          }
      
          public static void generateAllSubwords(String word) {
              index = 0;
              String[] subwords = new String[(int)Math.pow(2, word.length()) - 1];
              generateSubwords("", word, subwords);
              for(String s : subwords) {
                  System.out.println(s);
              }
          }
      }
      

      没有递归的另一种解决方案

      由于顺序很重要,您可以创建一系列二进制标志,告诉您字符是否必须包含在子字中。像这样:

      String: abc
      Flags:  001
              010
              011
              100
              101
              110
              111
      

      这些是二进制字符串。所以算法将是:

      • i1之间的(2^n) - 1(其中n是单词的长度)
        1. 创建一个二进制字符串,左边用零填充,字长相同。
        2. 对于二进制字符串中的每个1,打印/存储匹配的字符。

      代码:

      public void createSubwords(String word) {
          // As you can see, your array must have (2^n) - 1 entries
          String[] subwords = new String[(int)Math.pow(2, word.length()) - 1];
          String bin;
          String fmt;
          String subword;
          for(int i = 1; i < Math.pow(2, word.length()); i++) {
              // fmt will be used to format the binary string so it is
              // left padded with zeros
              fmt = "%0" + word.length() + "d";
              // bin is the binary string
              bin = String.format(fmt, Long.parseLong(Integer.toBinaryString(i)));
              // Initialize the subword
              subword = "";
              // For each '1' in the binary string, add the matching character
              // to the subword
              for(int j = 0; j < bin.length(); j++) {
                  if(bin.charAt(j) == '1')
                      subword = subword + word.charAt(j);
              }
              // Store it in the array
              subwords[i - 1] = subword;
          }
          // Print each subword
          for(String s : subwords) {
              System.out.println(s);
          }
      }
      

      希望这有帮助

答案 1 :(得分:2)

我已经在Iterator<T>中实现了此功能,这可以实现内容的延迟生成。

import java.math.BigInteger;
import java.util.Iterator;

public class SubstringIterator implements Iterator<String> {

    String s;
    BigInteger cur = BigInteger.ZERO;
    BigInteger max;

    public SubstringIterator(String s) {
        this.s = s;
        max = BigInteger.ONE.shiftLeft(s.length()).subtract(BigInteger.ONE);
    }

    @Override
    public boolean hasNext() {
        return cur.compareTo(max) < 0;
    }

    @Override
    public String next() {
        cur = cur.add(BigInteger.ONE);
        StringBuilder sb = new StringBuilder();
        for(int i = 0x00; i < s.length(); i++) {
            if(cur.testBit(i)) {
                sb.append(s.charAt(i));
            }
        }
        return sb.toString();
    }

    @Override
    public void remove() {
        throw new UnsupportedOperationException("This is not a collection iterator");
    }



}

代码的工作原理如下:您需要声明一个bitarray:一个具有任意位数的数组。现在我们使用BigInteger,因为它非常方便,但您可以使用任何等效的数据结构。

bitarray维护一个位列表。当 i -th位为1时,这意味着相应的字符应该在要生成的字符串中,因此如果字符串是foobar且状态为{{ 1}},结果将是:

011011

因此foobar 011011 oo ar 。基于bitarray生成String的过程由:

给出
ooar

现在唯一缺少的是迭代具有该长度的一组比特阵列。为此,StringBuilder sb = new StringBuilder(); for(int i = 0x00; i < s.length(); i++) { if(cur.testBit(i)) { sb.append(s.charAt(i)); } } return sb.toString(); 提供的方法很有用。这将进行二进制增量。但是,您可以使用Gray counter。在这种情况下,输出的顺序会有所不同,但这不是主要问题。

所以我们就是这样设置BigInteger来表示状态。最初状态为current,因此为空字符串。但我们不需要发出这种状态。

00000...000方法中,我们检查hasNext是否已达到可能性的终点。这是状态为Iterator<T>的时候。因此,我们将最大值存储在11111....111中,其中 n max,其中1为字符串的长度。

最后,n方法只需递增状态并计算结果。

现在你可以生成一个包含结果的数组。但总的来说,next更好。迭代器不会显式存储所有值。因此,内存使用率(几乎)不变,而数组会导致指数内存使用。

此外,它可以节省CPU使用率,因为人们并不总是需要计算所有值。假设您正在查看Iterator<T>是否是成员,您可以从生成foo的那一刻起切断搜索,而首先构建整个数组可能会更加昂贵。

参见在线演示here

如果空字符串也被视为子字符串替换:

"foo"

通过

BigInteger cur = BigInteger.ZERO;

答案 2 :(得分:0)

我做了一个递归函数。它看起来像这样

这不是可编译的java代码。它只概述了算法

List<String> GetSubwords(String str)
{
    if(str.length == 1)
        return str; 

    List<String> result = new List<String>();
    FirstChar = str[0];

    // the portion of the string after the first character
    var smallString = str.Substring(1, str.length-1);
    List<String> smallerSubWords = GetSubwords(smallString);

    result.add(FirstChar.ToString())
    foreach(subword in smallerSubwords)
    {
        result.add(subword);
        result.add(firstChar + subword);
    }
    return result;
}

这基本上需要一个字符串,比如&#34; ABCD&#34;,删除&#34; A&#34;,然后获取&#34; BCD&#34;的所有子字的列表,并返回那些列表,以及前面加'A'的列表

答案 3 :(得分:0)

这是一个简单的python版本的递归,java中的翻译可能会很冗长,但很简单:

def subs(s):
    if len(s) == 0:
        return ['']
    return [pref + sb for sb in subs(s[1:]) for pref in ('', s[0])]

print subs('ABC')

答案 4 :(得分:0)

这是一个简单的算法。 Say string的长度为n。生成从02^n-1的所有数字。对于每个这样的数字,如果第i位设置为1,则从左到右扫描其二进制表示并写入ith字符输出。

以下是可以转换为java的C ++示例:

char s[] = "abc";
for(int i = 0; i < 1 << 3; i++)
{   for(int j = 0; j < 32; j++)
    {   if((1 << j) & i)
            printf("%c", s[j]);
    }
    puts("");
}