如何获取字符串中组的所有排列?

时间:2015-12-02 20:57:18

标签: c# pseudocode

这是作业,虽然看起来像是这样。我一直在浏览英国计算机奥林匹克的网站,发现了这个问题(问题1):here。我对它感到困惑,我想看看你们对如何做到这一点的想法。我无法想到将所有内容组合成小组的任何简洁方法(在此之后检查它是否是一个简单的回文,即originalString == new String(groupedString.Reverse.SelectMany(c => c).ToArray),假设它是一个char数组。)

有什么想法吗?谢谢!

工作人员的文字:

  

回文是一个单词,显示相同的字母序列   逆转。如果一个单词可以将其字母组合成两个或   更多的块(每个包含一个或多个相邻的字母)然后它   如果反转这些块的顺序导致块回文结果   相同的块序列。

     

例如,使用括号表示块,以下是   块回文:

     

•BONBON可以组合为(BON)(BON);

     

•ONION可以组合在一起(ON)(I)(ON);

     

•BBACBB可以组合在一起作为(B)(BACB)(B)或(BB)(AC)(BB)或   (B)(B)(AC)(B)(B)

     

注意(BB)(AC)(B)(B)无效反向(B)(B)(AC)(BB)   以不同的顺序显示块。

问题本质上是如何生成所有这些组,然后检查它们是否是回文!

6 个答案:

答案 0 :(得分:9)

  

问题本质上是如何生成所有这些组,然后检查它们是否是回文!

我注意到这不一定是最好的策略。首先生成所有组然后检查它们是否是palidromes比仅生成那些作为回文的组效率低得多。

但本着回答问题的精神,让我们递归地解决问题。我将生成所有组;检查一组组是否是回文,留作练习。我也将忽略一组包含至少两个元素的要求;这很容易检查。

优雅地解决这个问题的方法是递归推理。与所有递归解决方案一样,我们从一个简单的基本案例开始:

空字符串有多少个分组?只有空的分组;也就是说,没有元素的分组。

现在我们假设我们有一个小问题的解决方案,并问“如果我们有一个解决较小问题的解决方案,我们如何使用该解决方案来解决更大的问题?”

好吧,假设我们遇到了更大的问题。我们有一个包含6个字符的字符串,我们希望生成所有分组。而且,分组是对称的;第一组与最后一组的大小相同。假设我们知道如何解决任何较小字符串的问题。

我们按如下方式解决问题。假设字符串是ABCDEF。我们从两端剥离A和F,我们解决了BCDE的问题,记住我们知道如何通过假设来做,现在我们预先添加A并将F附加到每个解决方案中。

BCDE的解决方案是(B)(C)(D)(E), (B)(CD)(E), (BC)(DE), (BCDE)。同样,我们假设我们有一个归纳假设,我们可以解决较小的问题。然后,我们将这些与A和F结合起来,为ABCDEF生成解决方案:(A)(B)(C)(D)(E)(F), (A)(B)(CD)(E)(F), (A)(BC)(DE)(F)(A)(BCDE)(F)

我们取得了很好的进展。我们完了吗?不。接下来我们剥离AB和EF,并递归地解决CD的问题。我不会这样做。我们完了吗?不。我们剥离ABC和DEF并递归地解决中间空字符串的问题。我们完了吗?不。(ABCDEF)也是一种解决方案。现在我们已经完成了。

我希望草图激发解决方案,现在这很简单。我们从辅助函数开始:

    public static IEnumerable<T> AffixSequence<T>(T first, IEnumerable<T> body, T last)
    {
        yield return first;
        foreach (T item in body)
            yield return item;
        yield return last;
    }

这应该很容易理解。现在我们做了真正的工作:

    public static IEnumerable<IEnumerable<string>> GenerateBlocks(string s)
    {

        // The base case is trivial: the blocks of the empty string 
        // is the empty set of blocks.
        if (s.Length == 0)
        {
            yield return new string[0];
            yield break;
        }
        // Generate all the sequences for the middle;
        // combine them with all possible prefixes and suffixes.
        for (int i = 1; s.Length >= 2 * i; ++i)
        {
            string prefix = s.Substring(0, i);
            string suffix = s.Substring(s.Length - i, i);
            string middle = s.Substring(i, s.Length - 2 * i);
            foreach (var body in GenerateBlocks(middle))
                yield return AffixSequence(prefix, body, suffix);
        }
        // Finally, the set of blocks that contains only this string
        // is a solution.
        yield return new[] { s };
    }

让我们测试一下。

        foreach (var blocks in GenerateBlocks("ABCDEF"))
            Console.WriteLine($"({string.Join(")(", blocks)})");

输出

(A)(B)(C)(D)(E)(F)
(A)(B)(CD)(E)(F)
(A)(BC)(DE)(F)
(A)(BCDE)(F)
(AB)(C)(D)(EF)
(AB)(CD)(EF)
(ABC)(DEF)
(ABCDEF)

所以你去吧。

您现在可以检查每个分组是否是回文,但为什么?如果前缀和后缀不相等,上面提到的算法可以很容易地修改,以便通过简单地不递归来消除所有非回文:

if (prefix != suffix) continue;

该算法现在只列举块回文。我们来测试一下:

        foreach (var blocks in GenerateBlocks("BBACBB"))
            Console.WriteLine($"({string.Join(")(", blocks)})");

输出低于;再次注意,我没有过滤掉“整个字符串”块,但这样做很简单。

(B)(B)(AC)(B)(B)
(B)(BACB)(B)
(BB)(AC)(BB)
(BBACBB)

如果您对此主题感兴趣,请考虑阅读我使用相同技术生成每个可能的树形拓扑和语言中每个可能的字符串的系列文章。它从这里开始:

http://blogs.msdn.com/b/ericlippert/archive/2010/04/19/every-binary-tree-there-is.aspx

答案 1 :(得分:3)

这应该有效:

   public List<string> BlockPalin(string s) {
        var list = new List<string>();
        for (int i = 1; i <= s.Length / 2; i++) {
            int backInx = s.Length - i;
            if (s.Substring(0, i) == s.Substring(backInx, i)) {
                var result = string.Format("({0})", s.Substring(0, i));
                result += "|" + result;

                var rest = s.Substring(i, backInx - i);

                if (rest == string.Empty) {
                    list.Add(result.Replace("|", rest));
                    return list;
                }
                else if (rest.Length == 1) {
                    list.Add(result.Replace("|", string.Format("({0})", rest)));
                    return list;
                }
                else {
                    list.Add(result.Replace("|", string.Format("({0})", rest)));

                    var recursiveList = BlockPalin(rest);
                    if (recursiveList.Count > 0) {
                        foreach (var recursiveResult in recursiveList) {
                            list.Add(result.Replace("|", recursiveResult));
                        }
                    }
                    else {
                    //EDIT: Thx to @juharr this list.Add is not needed...
                    //    list.Add(result.Replace("|",string.Format("({0})",rest)));
                        return list;
                    }
                }
            }
        }
        return list;
    }

并且这样称呼(编辑:再次向@juharr发送,不需要使用不同版本):

        var x = BlockPalin("BONBON");//.Distinct().ToList();
        var y = BlockPalin("ONION");//.Distinct().ToList();
        var z = BlockPalin("BBACBB");//.Distinct().ToList();

结果:

  • x包含1个元素:(BON)(BON)
  • y包含1个元素:(ON)(I)(ON)
  • z包含3个元素:(B)(BACB)(B),(B)(B)(AC)(B)(B)和(BB)(AC)(BB)

答案 2 :(得分:2)

虽然没有@Eric Lippert提供的那么优雅,但人们可能会发现以下迭代字符串分配免费解决方案:

foreach (var input in new [] { "BONBON", "ONION", "BBACBB" })
{
    Console.WriteLine(input);
    var blocks = GetPalindromeBlocks(input);
    foreach (var blockList in blocks)
        Console.WriteLine(string.Concat(blockList.Select(range => "(" + input.Substring(range.Start, range.Length) + ")")));
}

测试:

if (!IsPalindromeBlock(input, range)) continue;

删除行{{1}}将产生OP问题的答案。

答案 3 :(得分:1)

目前尚不清楚是否要所有可能的分组,或只是 可能的分组。这是一种偏离我头脑的方式,你可能会得到 分组:

public static IEnumerable<string> GetBlocks(string testString)
{
    if (testString.Length == 0)
    {
        yield break;
    }

    int mid = testString.Length / 2;
    int i = 0;
    while (i < mid)
    {
        if (testString.Take(i + 1).SequenceEqual(testString.Skip(testString.Length - (i + 1))))
        {
            yield return new String(testString.Take(i+1).ToArray());
            break;
        }
        i++;
    }
    if (i == mid)
    {
        yield return testString;
    }
    else
    {
        foreach (var block in GetBlocks(new String(testString.Skip(i + 1).Take(testString.Length - (i + 1) * 2).ToArray())))
        {
            yield return block;
        }
    }
}

如果您提供bonbon,则会返回bon。如果你给它onion,它会给你回复oni。如果你给它bbacbb,它会给你bbac

答案 4 :(得分:1)

这是我的解决方案(没有VS所以我用java做过):

int matches = 0;
public void findMatch(String pal) {     
        String st1 = "", st2 = "";
        int l = pal.length() - 1;   
        for (int i = 0; i < (pal.length())/2 ; i ++ ) {
            st1 = st1 + pal.charAt(i);
            st2 = pal.charAt(l) + st2;
            if (st1.equals(st2)) {
                matches++;
                // DO THE SAME THING FOR THE MATCH
                findMatch(st1);
            }
            l--;
        }   
    }

逻辑非常简单。我制作了两个字符数组并比较它们以在每一步中找到匹配项。关键是你需要为每场比赛检查同样的事情。

findMatch("bonbon"); // 1
findMatch("bbacbb"); // 3

答案 5 :(得分:0)

BONBON ......这样的事情怎么样?

string bonBon = "BONBON";

首先检查偶数或奇数的字符数。

bool isEven = bonBon.Length % 2 == 0;

现在,如果是偶数,将字符串分成两半。

if (isEven)
{
   int halfInd = bonBon.Length / 2;
   string firstHalf = bonBon.Substring(0, halfInd );
   string secondHalf = bonBon.Substring(halfInd);
}

现在,如果是奇数,则将字符串拆分为3个字符串。

else
{
   int halfInd = (bonBon.Length - 1) / 2;
   string firstHalf = bonBon.Substring(0, halfInd);
   string middle = bonBon.Substring(halfInd, bonBon.Length - halfInd);
   string secondHalf = bonBon.Substring(firstHalf.Length + middle.length);
}

可能不完全正确,但它是一个开始.... 还是要加上检查它是否真的是一个回文... 祝你好运!!