字符串的排列

题目描述

输入一个字符串,按字典序打印出该字符串中字符的所有排列。例如输入字符串abc,则打印出由字符a,b,c所能排列出来的所有字符串abc,acb,bac,bca,cab和cba。

输入描述:

<pre>输入一个字符串,长度不超过9(可能有字符重复),字符只包括大小写字母。</pre>

这个问题可以进行分解,例如输入abc,求整个字符串的排列可以分解成两步:

1、求出第一个字符的所有可能性,即将第一个字符与其他全部字符交换逐一交换位置。例如可以得到abc、bac、cba。

2、固定第一个字符,对之后的字符重复第一步。abc,bac,cba可以得到abc、acb、bac、bca、cba、cab。

可以通过循环或者递归来实现

解法一:
public class Solution {
    public ArrayList<String> Permutation(String str) {
        ArrayList<String> res = new ArrayList<String>();
        if(str == null || str.length() == 0)
            return res;
        Queue<String> q = new LinkedList<String>();
        q.add(str);
        //遍历字符串str,从头到尾依次来确定所有可能情况
        for(int index = 0; index < str.length() - 1; index++) {
            //使用count来记录已知情况的个数,而不是直接使用q.size(),避免了死循环
            int count = q.size();
            //遍历res中的所有已知的组合情况
            for(int i = 0; i < count; i++) {
                char[] s = q.poll().toCharArray();
            //将index之后的每个字符与index交换位置,求得所有可能情况
                for(int j = index; j < s.length; j++) {
                    char tmp = s[index];
                    s[index] = s[j];
                    s[j] = tmp;
                    if(!q.contains(String.valueOf(s))){
                        q.add(String.valueOf(s));
                    }
                }
            }
        }
        for(String s : q) {
            res.add(s);
        }
        Collections.sort(res);
        return res;
    }
}

上述算法通过循环,依次固定了从0到str.length-1位,从而求得了所有可能。

以str=abc为例:

1、先在res中输入abc

2、确定第一位,在res中删除abc,添加abc、bac、cba。

3、固定第一位,确定第二位,从res中删除abc,添加abc、acb;从res中删除bac,添加bac、bca;从res中删除cba,添加cba、cab。

4、固定第二位,由于第三位只有一种可能,无需确定。对res进行排序,得到字典序结果。

解法二:
public class Solution {
    public ArrayList<String> Permutation(String str) {
        ArrayList<String> res = new ArrayList<>();
        if(str != null && str.length() > 0) {
            char[] s = str.toCharArray();
            transfer(s, res, 0);
            Collections.sort(res);
        }
        return res;
    }
    //固定第start位,获得所有可能情况
    public void transfer (char[] s, ArrayList<String> res, int start) {
        if(start == s.length - 1) {
            String val = String.valueOf(s);
            if(!res.contains(val)) {
                res.add(val);
            }
        }
        else {
            for(int i = start; i < s.length; i++) {
                //交换
                swap(s, start, i);
                transfer(s, res, start + 1);
                //复位
                swap(s, start, i);

            }
        }
    }
    //交换函数
    public void swap(char[] s, int i, int j) {
        char tmp = s[i];
        s[i] = s[j];
        s[j] = tmp;
    }
}

上述代码用递归实现了整个过程,但需要注意的是用于复位的代码

swap(s, start, i);

这行代码容易产生误解,误以为递归出栈后的数组仍为原数组,所以可以直接使用交换函数复位。我们知道,java中虽然都为值传递,但非基本类型作为参数传入时,例如传入数组s时,传入的实际上是引用s的引用,即引用的引用,对形参的修改会影响原数据,所以实际上数组s的值是会改变的。详见文章“Java中对象引用”

//交换
swap(s, start, i);
transfer(s, res, start + 1);
//复位
swap(s, start, i);

那么问题来了,既然数组s会改变,即使递归出栈得到的也是改变后的数组,紧跟在transfer后的swap得到的数组难道是除了start位以外之后的字符顺序都被打乱的数组?那么为何swap函数又确实起到了复位的作用呢?

答案是因为当递归到最后一层时,swap只能对数组的最后两位进行操作,即i=str.length,假设现在数组为……ab,所以在这一层第二个swap函数确实实现了复位功能,进而递归回退到倒数第二层,由于在上一层将ab位置复位了,即这一层start位之后的ba是原顺序,所以swap函数对……cba也能实现复位。(因为start位c是固定的,只要ba是原顺序,则cba也必为原顺序)。所以这个复位操作是通过递推一步步回退来实现的。

解法三:

使用字典序全排列生成算法来计算。

算法介绍:

假设存在一个全排列P1P2P3P4……Pj-1PjPj+1……Pk-1PkPk+1……Pn-1Pn

我们可以求得其下一个排列。(所谓下一个排列,大于该全排列的最小排列,比如123456的下一个排列是123465)。

步骤为:

1、求得j,其中j=max{i|Pi < Pi-1},即从左往右第一个开始变小的值得下标。

2、求得k,期中k=max{i|Pi>Pj},即j的右边大于Pj的值中最大的下标。

3、交换Pj和Pk。

4、将下标从j+1到n的序列翻转。

举例说明:

123465

求得j=3,进而得到k=5,进行交换得到123564,进行翻转,得到123546,所以123465的下一个排列为123546。

这种方法被写在了C++ STL库中。

于是通过循环我们可以很轻松得到全部的字典序排列,只要输入的是最小的字典序排列即可。

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 215,923评论 6 498
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 92,154评论 3 392
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 161,775评论 0 351
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 57,960评论 1 290
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 66,976评论 6 388
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 50,972评论 1 295
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,893评论 3 416
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,709评论 0 271
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 45,159评论 1 308
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,400评论 2 331
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,552评论 1 346
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,265评论 5 341
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,876评论 3 325
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,528评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,701评论 1 268
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 47,552评论 2 368
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,451评论 2 352

推荐阅读更多精彩内容