【问题标题】:how to find all subwords of a string [closed]如何查找字符串的所有子词[关闭]
【发布时间】:2015-03-20 11:02:36
【问题描述】:

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

示例:"abc"

  1. 删除无 - 添加"abc" 到输出
  2. 删除第一个字符 ("bc") - 添加到输出,然后是第二个 ("ac") - 添加到输出,然后删除第三个 ("ab") - 添加到输出。
  3. 删除 2 个字符("a""b""c")并添加到输出中

现在,我不知道该怎么写,所以我寻求一点帮助,没有什么先进的,因为这是我的硬件,我想自己学习和做。更具体地说,我想知道如何在不更改输入的情况下从中间删除一个字符。

另外,"cb" 对我来说不是子词,因为所有子词都需要按照它们在原始字符串中显示的顺序排列。

【问题讨论】:

  • 你有没有尝试过?到目前为止,没有任何努力,人们会认为你只是在要求我们为你做功课。 StackOverflow 适用于特定的、与代码相关的 问题。如果您需要有关如何开始或澄清的一般指导,请咨询您的教授/助教/老师。
  • 所以如果你有“abcd”,你会想得到:a、b、c、d、bcd、acd、abd、abc、cd、ad、cd?
  • 是的,我试过了,我做了一个可以按索引删除字母的函数,我想如何在递归调用中执行代码,但我什么都想不出来
  • 然后显示您的代码并详细说明它的具体问题。
  • 您需要跟踪存在的字符数、要删除的字符数、您在字符串中的位置以及当前位置 + 您需要删除的字符数

标签: java string substring


【解决方案1】:

考虑一下:你必须找到所有以第一个字符开头的子词,然后是第二个字符,然后是第三个字符......等等。

这可以写成递归算法,接受两个参数:

  1. “前缀”
  2. 子词前缀之后

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

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

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 的单词中有多少个子词。我留给你弄清楚(提示:答案的最后一部分是子序列数量的关键,但它包括 empty 子序列)。

一种可能的解决方案是:

  1. 创建一个整数静态变量(一个类变量)来保存您正在执行的迭代。该数字从零开始,每次打印/存储子字时增加一个单位
  2. 在同一个类中,编写一个创建适当大小数组的方法。
  3. 修改上述方法,除了前缀和子词之外,还接收这个新创建的数组。
  4. System.out.println() 的内容替换为将生成的子词存储到数组中的句子,使用我在步骤 1 中提到的静态变量作为索引。
  5. 再次调用该函数时,请务必同时传递数组。

我会在几个小时后回来编写代码示例,但我希望您先尝试自己解决它(另外,上面的链接让我想到了另一种解决此问题的方法不需要递归,我会在以后的编辑中包含它)


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

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

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

  • 对于1(2^n) - 1 之间的i(其中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);
    }
}

希望对你有帮助

【讨论】:

  • 非常感谢!我现在知道了!但我还有一个问题,你怎样才能让它不打印,而是存储在一个字符串[]中?不是某种列表,只是一个数组?
  • @DavidBarishav 很高兴学习。顺便说一句,如果您发现此答案有用,请投票和/或接受它。至于你的问题,让我编辑我的帖子来回答它
  • @DavidBarishav 我已经为您的新问题提供了一些提示。请尝试使用这些提示自行解决此问题。我稍后会回来编辑答案并包含代码......以及我必须解决原始问题的另一个想法(不需要递归)。
  • @DavidBarishav 检查我更新的答案:它有将子词存储在数组中的解决方案(使用递归方法)和一个不使用递归的新解决方案
  • 基本情况不是针对空字符串吗?它会返回 "" 作为子字符串之一,因此它将是 2^n,其中 n 是字母的数量?
【解决方案2】:

我已经在 Iterator&lt;T&gt; 中实现了这一点,这可以实现内容的惰性生成。

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");
    }



}

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

位数组维护一个位列表。当第i位为1时,表示对应的字符应该在要生成的字符串中,所以如果字符串为foobar且状态为011011,则结果将是:

foobar
011011
 oo ar

因此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();

现在唯一缺少的是迭代具有该长度的位数组集。为此,BigInteger 提供的方法很有用。这将执行二进制增量。但是,您可以例如使用Gray counter。在这种情况下,输出的顺序会有点不同,但这不是主要问题。

所以我们设置current 来代表状态。最初状态是00000...000,因此是空字符串。但是我们不需要发出那个状态。

hasNext 方法中,我们检查Iterator&lt;T&gt; 是否已经到达可能性的尽头。此时状态为11111....111。因此,我们将最大值存储在max 中,这是1n 倍,n 是字符串的长度。

最后next方法只需要增加状态并计算结果。

现在您当然可以生成一个包含结果的数组。但总的来说,Iterator&lt;T&gt; 更好。迭代器不会显式存储所有值。因此内存使用量(几乎)是恒定的,而数组会导致内存使用量呈指数增长。

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

查看在线演示here

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

BigInteger cur = BigInteger.ZERO;

BigInteger cur = BigInteger.ONE.negate();

【讨论】:

  • 直接给他解决方案? nothing advanced as this is my hw and I want to learn and do it myself
  • @DoubleDouble:给我一点时间来解释一下解决方案。
  • @DoubleDouble:更好?
  • 这是一个很好的解决方案,你解释得很好,我 +1。我仍然不确定这算不算让他“自己做”,我怀疑他可以将此应用于不同但相似的问题,但至少如果他的老师向他提问,他有他需要解释的信息。
  • 虽然这是一个很好的答案,但我认为它违背了教授Java基础知识的目的(我的印象是OP是初学者,希望初学者的答案)。
【解决方案3】:

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

这不是可编译的 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;
}

这实际上需要一个字符串,比如“ABCD”,删除“A”,然后获取“BCD”的所有子词的列表,并返回这些子词的列表,以及那些具有'A' 贴在前面

【讨论】:

  • 我不知道列表,所以你能解释一下吗?
  • @DavidBarishav 该代码不是可编译的代码。我不知道 List&lt;String&gt; 结构本身是否存在于 java 中。它应该代表String[]StringArrayList 或字符串的链接列表,或您喜欢使用的任何类型的列表结构
  • 这个程序将如何到达“abc”中的“b”?如果它首先取“a”,那么将只留下“bc”,然后将“b”关闭,然后返回“c”和“bc”?
  • @DavidBarishav 要理解递归,能够抽象出递归调用非常重要。只要相信GetSubwords(smallString) 会正常工作,并且它会返回["b", "c", "bc"] 的列表,不要考虑它是如何得到这个列表的。只需专注于使用该列表获取["b", "c", "bc", "ab", "ac", "abc"]
  • @DavidBarishav 您要做的一件事是基本情况。如果字符串只有 1 个字符,那是顶部的部分,您只需 return str
【解决方案4】:

这是一个简单的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')

【讨论】:

  • 在 Haskell 中,甚至还有一个内置函数:subsequences
【解决方案5】:

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

这是您可以翻译成 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("");
}

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2012-11-06
    • 1970-01-01
    • 2012-10-30
    • 2012-06-28
    • 2013-07-01
    • 2013-04-21
    • 2011-01-07
    • 1970-01-01
    相关资源
    最近更新 更多