找到给定字符串的每个可能的子集[重复]

Posted

技术标签:

【中文标题】找到给定字符串的每个可能的子集[重复]【英文标题】:Find every possible subset given a string [duplicate] 【发布时间】:2015-04-20 03:38:11 【问题描述】:

我正在尝试在 Java 中查找字符串的每个可能的字谜 - 我的意思是,如果我有一个 4 个字符长的单词,我想要从它派生的所有可能的 3 个字符长的单词,所有 2 个字符长的单词和所有 1 个字符长。我认为最直接的方法是使用两个嵌套的 for 循环并在字符串上进行迭代。这是我现在的代码:

private ArrayList<String> subsets(String word)
        ArrayList<String> s = new ArrayList<String>();
        int length = word.length();
        for (int c=0; c<length; c++)
            for (int i=0; i<length-c; i++)
                String sub = word.substring(c, c+i+1);
                System.out.println(sub);
                //if (!s.contains(sub) && sub!=null) 
                    s.add(sub);
            
        
        //java.util.Collections.sort(s, new MyComparator());
        //System.out.println(s.toString());
        return s;
    

我的问题是它适用于 3 个字母的单词,fun 会产生这个结果(不要介意排序,这个单词已经过处理,所以我有一个按字母顺序排列的字符串):

f
fn
fnu
n
nu
u

但是当我尝试 4 个字母的单词时,它会遗漏一些东西,如 catq 给我的:

a
ac
acq
acqt
c
cq
cqt
q
qt
t

即,我没有看到 3 个字符的长词 act - 这是我在测试此方法时正在寻找的那个。我不明白问题出在哪里,这很可能是我在创建子字符串时犯的一个逻辑错误。如果有人可以帮助我,请不要给我它的代码,而是给我解决方案背后的原因。这是一门课程,我需要自己编写代码。

编辑:清除某些东西,对我来说 acq、qca、caq、aqc、cqa、qac 等是同一件事 - 为了更清楚,发生的是字符串按字母顺序排序,所以所有这些排列都应该作为一个唯一的结果出现,acq。所以,我不需要字符串的所有排列,而是给定一个 4 个字符长的字符串,我可以从中派生出所有 3 个字符长的字符串 - 这意味着一次取出一个字符并返回该字符串因此,对原始字符串中的每个字符都这样做。 我希望我已经让我的问题更清楚了

【问题讨论】:

这与查找power set 非常相似。有很多算法可以找到幂集,你应该研究一下。 也不适用于 3s。你有“fn”但没有“fu” 只是一个一般建议:逐步调试对于此类逻辑错误非常有帮助。 并非总是如此——逐步调试非常适合找出它为什么会做不应该做的事情。在找出为什么它没有做它应该做的事情时不太好。 Anagram 会比 substring 更好 【参考方案1】:

一切正常,您只是在测试/输入中将“caqt”拼错为“acqt”。

(问题可能是您正在对输入进行排序。如果您想要substrings,则必须让输入保持未排序。)

编辑后:查看Generating all permutations of a given string 然后只需对单个字母进行排序,然后将它们放在一组中。

【讨论】:

按照算法定义,这些将是等效的起始字符串。 不,他按字母顺序输出。 现在想想,我不太确定。没有人说过要查找所有字谜,它只是查找子字符串,并对结果中的字母进行排序。虽然我认为它应该是 'actq' 或其他东西,但要让 'act' 作为子字符串......好吧,最后有 Q 的任何排列。 对,但是acqt -> acq -> acq。如果他想要正确的子字符串,他需要停止对 输入 进行排序 @mk.: 哈哈哈,你说得对。这就是错误。 (您可以编辑添加吗?然后我可以撤回反对票:P)【参考方案2】:

好的,既然您已经设计了自己的解决方案,我会告诉您我的看法。首先,考虑你的结果列表会有多大。您实际上是在依次接收每个字母,并且要么包含它,要么不包含它。每个字母有 2 种可能性,总结果为 2^n,其中 n 是字母的数量。这当然包括您不使用任何字母并以空字符串结尾的情况。

接下来,如果您用 0 表示“包含这封信”,用 1 表示不包含它,那么以您的“fnu”为例,您最终会得到:

000 - ''
001 - 'u'
010 - 'n'
011 - 'nu'
100 - 'f'
101 - 'fu' (no offense intended)
110 - 'fn'
111 - 'fnu'.

显然,这些只是二进制数,您可以推导出一个函数,给定 0-7 中的任何数字,输入三个字母,将计算相应的子集。

在java中很容易做到。手头没有java编译器,但这应该是大致正确的:

public string getSubSet(string input, int index) 
  // Should check that index >=0 and < 2^input.length here.
  // Should also check that input.length <= 31.
  string returnValue = "";
  for (int i = 0; i < input.length; i++) 
    if (i & (1 << i) != 0) // 1 << i is the equivalent of 2^i
      returnValue += input[i];
  
  return returnValue;

然后,如果您需要,您可以执行一个调用此函数的循环,如下所示:

for (i = 1; i < (1 << input.length); i++)
  getSubSet(input, i); // this doesn't do anything, but you can add it to a list, or output it as desired.

注意我从 1 而不是 0 开始 - 这是因为索引 0 处的结果将是空字符串。顺便说一句,这实际上首先执行最低有效位,因此您的输出列表将是“f”、“n”、“fn”、“u”、“fu”、“nu”、“fnu”,但顺序没有t 似乎很重要。

【讨论】:

【参考方案3】:

这是我想出的方法,好像行得通

private void subsets(String word, ArrayList<String> subset)
        if(word.length() == 1)
            subset.add(word);
            return;
         
        else 
            String firstChar = word.substring(0,1);
            word = word.substring(1);
            subsets(word, subset);
            int size = subset.size();
            for (int i = 0; i < size; i++)
                String temp = firstChar + subset.get(i);
                subset.add(temp);
            
            subset.add(firstChar);
            return;
        
    

我要做的是检查单词是否大于一个字符,否则我会将字符单独添加到 ArrayList 并开始递归过程。如果它更大,我保存第一个字符并使用字符串的其余部分进行递归调用。发生的情况是,整个字符串被分割成保存在递归堆栈中的字符,直到我的单词长度变为 1,只剩下一个字符。

当发生这种情况时,正如我在开始时所说,字符被添加到列表中,现在递归开始并查看数组的大小,在第一次迭代中为 1,然后使用 for 循环添加为上一次调用而保存在堆栈中的字符与 ArrayList 中的每个元素连接。然后它自己添加角色并再次展开递归。 即,fun这个词会发生:

f saved
List empty
recursive call(un)
-
u saved
List empty
recursive call(n)
-
n.length == 1
List = [n]
return
-
list.size=1
temp = u + list[0]
List = [n, un]
add the character saved in the stack on its own
List = [n, un, u]
return
-
list.size=3
temp = f + list[0]
List = [n, un, u, fn]
temp = f + list[1]
List = [n, un, u, fn, fun]
temp = f + list[2]
List = [n, un, u, fn, fun, fu]
add the character saved in the stack on its own
List = [n, un, u, fn, fun, fu, f]
return

我已经尽可能清楚了,我希望这能澄清我最初的问题以及如何解决它。

【讨论】:

【参考方案4】:

这是工作代码:

public static void main(String[] args) 
    String input = "abcde";
    Set<String> returnList = permutations(input);
    System.out.println(returnList);


private static Set<String> permutations(String input) 
    if (input.length() == 1) 
        Set<String> a = new TreeSet<>();
        a.add(input);
        return a;
    
    Set<String> returnSet = new TreeSet<>();

    for (int i = 0; i < input.length(); i++) 
        String prefix = input.substring(i, i + 1);
        Set<String> permutations = permutations(input.substring(i + 1));
        returnSet.add(prefix);
        returnSet.addAll(permutations);
        Iterator<String> it = permutations.iterator();
        while (it.hasNext()) 
            returnSet.add(prefix + it.next());
        
    
    return returnSet;

【讨论】:

我很想投反对票只是因为您没有阅读问题。他不想要代码,他需要了解如何去做。没有解释的代码几乎正是他不想要的。回答问题和为问题提供解决方案是有区别的。并非所有答案都是解决方案。

以上是关于找到给定字符串的每个可能的子集[重复]的主要内容,如果未能解决你的问题,请参考以下文章

LeetCode 90. 子集 II(Subsets II)

获取字符串或组合的所有可能排列,包括 Java 中的重复字符

从非前缀邻居的子集中找到最大收益值?

改进子集解决方案

给定一个字符串,找到最长子串的长度,而不重复字符。

回溯 - 给定一组数字,找到总和等于 M 的所有子集(给定 M)