2019独角兽企业重金招聘Python工程师标准>>>

题目:输入一个字符串,打印出该字符串中字符的所有排列。

例如输入字符串abc,则打印由字符a,b,c所能排列出来的所有字符串:abc,abc,bac,bca,cab,cba

我们求整个字符串的排列,可以看成两步:首先求出所有可能出现在第一 个位置的字符,即把第一个字符和后面所有的字符交换。下图就是分别把第一个字符a和后面的b,c交换的情景。第二步固定第一个字符,求后面所有字符的排 列。这个时候我们仍把后面的所有字符分成两部分:后面字符的第一个字符,以及这个字符之后的所有字符。然后把第一个字符逐一和它后面的字符交换……

注:(a)把字符串分为两部分,一部分是字符串的第一个字符,另一部分是第一个字符以后的所有字符。接下来我们求阴影部分的字符串的排列。(b)拿第一个字符和它后面的字符逐个交换。

即我们以三个字符abc为例来分析一下求字符串排列的过程。首先我们固定第一个字符a,求后面两个字符bc的排列。当两个字符bc的排列求好之后,我 们把第一个字符a和后面的b交换,得到bac,接着我们固定第一个字符b,求后面两个字符ac的排列。现在是把c放到第一位置的时候了。记住前面我们已经 把原先的第一个字符a和后面的b做了交换,为了保证这次c仍然是和原先处在第一位置的a交换,我们在拿c和第一个字符交换之前,先要把b和a交换回来。在 交换b和a之后,再拿c和处在第一位置的a进行交换,得到cba。我们再次固定第一个字符c,求后面两个字符b、a的排列。

为方便起见,用123来示例下。123的全排列有123、132、213、231、312、321这六种。首先考虑213和321这二个数是如何得出的。 显然这二个都是123中的1与后面两数交换得到的。然后可以将123的第二个数和每三个数交换得到132。同理可以根据213和321来得231和 312。因此可以知道——全排列就是从第一个数字起每个数分别与它后面的数字交换。

分析到这里,我们就可以看出,这其实是很典型的递归思路,于是我们写出下面的Java代码:

package cglib;

public class DeleteNode{

public static void main(String[] args) {

char buf[]={'a','b','c'};

perm(buf,0,buf.length-1);

}

public static void perm(char[] buf,int start,int end){

//这个判断很关键,每次递归到最后的时候,就是START每次都等于END的时候,就是要打印出相应的全排列字符串的时候,
        if(start==end){//这个判断用于递归到最后的时候输出相应的字符串

for(int i=0;i<=end;i++){

System.out.print(buf[i]);

}

System.out.println();

}

else{//这个else块的作用有1:交换第一个位置的字符,比如第一个位置的全排列字符串全部打印后

//就把第一个字符和第二个交换;2:递归打印每次第一个字符串的全排列字符串;3:每次

//递归的时候都会传递一个字符串数组,最后三行代码就是控制这个字符串数组不变,意思就是

//什么样子传递出去,就什么样子传递回来,一点不能变化,因为最后三行代码不是用于改变

//字符串数组的

for(int i=start;i<=end;i++){  
                System.out.println("交换前:buf[start="+start+"]="+buf[start]);
                System.out.println("交换前:buf[i="+i+"]="+buf[i]);
                char temp=buf[start];
                //第一次输出abc,这三行代码用于控制第一个位置的字符,就是作用1

buf[start]=buf[i]; //比如,第一次时的第一个字符的全排列输出完后,

//然后把第一个和第二个字符进行交换,交换后,再排列                 
                buf[i]=temp;  //刚被交换到第一个位置字符的 全排列字符串                 
                System.out.println("交换后:buf[start="+start+"]="+buf[start]);
                System.out.println("交换后:buf[i="+i+"]="+buf[i]);
                System.out.println("进入perm,start+1");
                perm(buf,start+1,end);//通过start控制要被输出的字符串,对应作用2

temp=buf[start];//这三行是把变换后的字符串顺序进行还原,能够变换字符串顺序的,对应作用3

//代码就在递归函数perm()上面三行,用于交换字符串顺序来              
                buf[start]=buf[i];  //交换出所需的全排列字符串

buf[i]=temp;
                System.out.println("变回来:buf[start="+start+"]="+buf[start]);
                System.out.println("变回来:buf[i="+i+"]="+buf[i]);

}

}

}

}

输出:

交换前:buf[start=0]=a
交换前:buf[i=0]=a
交换后:buf[start=0]=a
交换后:buf[i=0]=a
进入perm,start+1
交换前:buf[start=1]=b
交换前:buf[i=1]=b
交换后:buf[start=1]=b
交换后:buf[i=1]=b
进入perm,start+1
abc
变回来:buf[start=1]=b
变回来:buf[i=1]=b
交换前:buf[start=1]=b
交换前:buf[i=2]=c
交换后:buf[start=1]=c
交换后:buf[i=2]=b
进入perm,start+1
acb
变回来:buf[start=1]=b
变回来:buf[i=2]=c
变回来:buf[start=0]=a
变回来:buf[i=0]=a
交换前:buf[start=0]=a
交换前:buf[i=1]=b
交换后:buf[start=0]=b
交换后:buf[i=1]=a
进入perm,start+1
交换前:buf[start=1]=a
交换前:buf[i=1]=a
交换后:buf[start=1]=a
交换后:buf[i=1]=a
进入perm,start+1
bac
变回来:buf[start=1]=a
变回来:buf[i=1]=a
交换前:buf[start=1]=a
交换前:buf[i=2]=c
交换后:buf[start=1]=c
交换后:buf[i=2]=a
进入perm,start+1
bca
变回来:buf[start=1]=a
变回来:buf[i=2]=c
变回来:buf[start=0]=a
变回来:buf[i=1]=b
交换前:buf[start=0]=a
交换前:buf[i=2]=c
交换后:buf[start=0]=c
交换后:buf[i=2]=a
进入perm,start+1
交换前:buf[start=1]=b
交换前:buf[i=1]=b
交换后:buf[start=1]=b
交换后:buf[i=1]=b
进入perm,start+1
cba
变回来:buf[start=1]=b
变回来:buf[i=1]=b
交换前:buf[start=1]=b
交换前:buf[i=2]=a
交换后:buf[start=1]=a
交换后:buf[i=2]=b
进入perm,start+1
cab
变回来:buf[start=1]=b
变回来:buf[i=2]=a
变回来:buf[start=0]=a
变回来:buf[i=2]=c

拓展1:

如果不是求字符的所有排列,而是求字符的所有组合,应该怎么办?还是输入三个字符a、b、c,则它们的组合有a、b、c、ab、ac、bc、abc。当交换字符串中两个字符时,虽然能得到两个不同的排列,但却是同一个组合。比如ab和ba是不同的排列,但只算一个组合。

解题思路:在求一个字符串中所有字符的组合的时候,针对一个字符,有两种情况,假设在长度为n的字符串中选择长度为m的组合字符串,

第一是选择长度为n的字符串中的第一个字符,那么要在其余的长度n-1的字符串中选择m-1个字符

第二是不选择长度为n的字符串中的第一个字符,那么要在其余的长度n-1的字符串中选择m个字符

递归结束的条件就是,当m为0,即从字符串中不再选出字符的时候,这个时候已经找到了m个字符的组合,输出即可。还有一个条件是,当输入的字符串是串,自然是不能从中选出任何字符的。

package cglib;

import java.util.ArrayList;
import java.util.List;

public class DeleteNode{

public static void main(String ss[]) {  
        perm("123");  
        System.out.println();  
    }  
 
    // 求字符串中所有字符的组合abc>a,b,c,ab,ac,bc,abc  
    public static void perm(String s) {  
        List<String> result = new ArrayList<String>();  
        for (int i = 1; i <= s.length(); i++) {  
            perm(s, i, result);  
        }  
    }  
 
    // 从字符串s中选择m个字符  
    public static void perm(String s, int m, List<String> result) {  
 
        // 如果m==0,则递归结束。输出当前结果  
        if (m == 0) {  
            for (int i = 0; i < result.size(); i++) {  
                System.out.print(result.get(i));  
            }  
            System.out.println();  
            return;  
        }  
 
        if (s.length() != 0) {  
            // 选择当前元素  
            result.add(s.charAt(0) + "");  
            perm(s.substring(1, s.length()), m - 1, result);  
            result.remove(result.size() - 1);  
            // 不选当前元素  
            perm(s.substring(1, s.length()), m, result);  
        }  
    }

}

输出:

1
2
3
12
13
23
123

或者用二进制表示:

n个字符的所有组合个数就是2^n  -1 个。既然我们可以知道总数,那么不妨把这些数字换成二进制码,就有2^n  -1组二进制码,细心观察这些0101的二进制码,我们就会发现里面隐藏着一个规律,那就假如我们把那组二进制跟我们的字符串联想在一起,然后把出现1的 位置的字符连起来,不就是对应其中一种组合情况吗?换种说法,那2^n  -1个数所对应的二进制码,就是我们要的组合啊。那么接下来我们只需要把里面1的索引位置找出来,把字符串里面对应索引的字符取出来拼在一起就行了~~ 当然我觉得这里还有优化的余地,例如如何更快地找出1所在的位置之类~~
     还有,假如目标字符串是26个字母,这种算法列出组合,比网上的那些递归方法快差不多一倍(前提条件是不记录结果,或者直接输出结果,因为记录所有结果需要大量内存,不加内存会爆的........

package cglib;

public class DeleteNode{

public static void main(String[] args) {
            String temp = "abc";
            String[] results = getResult(temp);
            for (int i = 0; i < results.length; i++) {  
                System.out.println(results[i]);  
            }  
        }
        private static String[] getResult(String str){
            int start = 1;
            int strSize = str.length();
          
            double max = Math.pow(2, strSize-1);
            double total = Math.pow(2, strSize)-1;
            String[] result = new String[(int)total];
            int step;
            int index;
            StringBuilder sb;
            while (start <= total) {
                sb = new StringBuilder();
                step = 1;
                index = 0;
                while (step <= max) {
                    int temp = step & start;
                    if (temp != 0) {
                        sb.append(str.charAt(index));
                    }
                    step <<= 1;
                    index++;
                }
                result[start -1] = sb.toString();
                start++;
            }
            return result;
        }
     }

输出:

a
b
ab
c
ac
bc
abc

拓展2:

当输入一个含有8个数字的数组,判断有没有可能把这8个数字分别放到正方体的8个顶点上,使得正方体上三组相对的面上的4个顶点的和都相等。

分析

可以先求出a1-a8这8个数字的所有排列,然后判断有没有某一个的排列符合题目设定的条件,即a1+a2+a3+a4 = a5+a6+a7+a8,且a1+a3+a5+a7 = a2+a4+a6+a8,且a1+a2+a5+a6=a3=a4+a7+a8。求8个数字的排列和“面试题28:字符串的排列”中求字符串的排列类似,可 以将求8个数字的排列的问题分解下,将8个数字中的1个轮流固定放在数组的第一个位置,然后求剩下7个数字的排列,再依次递归下去。

相当于求出8个数字的全排列,判断有没有一个排列符合题目给定的条件,即三组对面上顶点的和相等。

package cglib;

public class DeleteNode{

public static void main(String[] args) {

int A[] = {1,2,3,1,2,3,2,2};  
         int B[] = {1,2,3,1,8,3,2,2};  
        
         
         if(perm(A,0,A.length-1))  
             System.out.println("Yes\n");  
            else  
                 System.out.println("No\n");  
            if(perm(B,0,B.length-1))  
                 System.out.println("Yes\n");  
            else  
                 System.out.println("No\n");

}

public static boolean perm(int[] buf,int start,int end){  
        if(buf==null || end!=buf.length-1)  
            return false;
        
        boolean result = false;

if(start==end){
             if(buf[0]+buf[1]+buf[2]+buf[3]==buf[4]+buf[5]+buf[6]+buf[7] &&  
                     buf[0]+buf[2]+buf[4]+buf[6]==buf[1]+buf[5]+buf[3]+buf[7] &&  
                             buf[0]+buf[1]+buf[4]+buf[5]==buf[2]+buf[3]+buf[6]+buf[7])  
                        {  
                 int i;  
                 for(i=0;i<=end;i++)  
                     {System.out.print(buf[i]); }
                 System.out.println();  
                 result =  true;  
                        }  
            
        }

else{
              
            for(int i=start;i<=end;i++){  
                //System.out.println("交换前:buf[start="+start+"]="+buf[start]);
                //System.out.println("交换前:buf[i="+i+"]="+buf[i]);
                int temp=buf[start];
               
                buf[start]=buf[i];                
                buf[i]=temp;                 
                
                result= perm(buf,start+1,end);
                if(result)  
                    break;  
                temp=buf[start];

buf[start]=buf[i];

buf[i]=temp;
                //System.out.println("变回来:buf[start="+start+"]="+buf[start]);
                //System.out.println("变回来:buf[i="+i+"]="+buf[i]);

}

}
        return result;

}

}

输出:

12323212
Yes

No

转载于:https://my.oschina.net/u/2822116/blog/719085

剑指Offer(java版):字符串的排列相关推荐

  1. 剑指offer java版 test3—从尾到头打印链表

    标题:剑指offer java版 test3-从尾到头打印链表 题目:输入一个链表,按链表从尾到头的顺序返回一个ArrayList. 解答:知识不够全面,用ArrayList做的 但是看到大佬们还可以 ...

  2. 剑指offer(二十七)-字符串的排列(Java版)

    描述 输入一个字符串,打印出该字符串中字符的所有排列,你可以以任意顺序返回这个字符串数组.例如输入字符串abc,则按字典序打印出由字符a,b,c所能排列出来的所有字符串abc,acb,bac,bca, ...

  3. 剑指offer java版(三)

    二叉搜索树的后序遍历 问题描述 输入一个整数数组,判断该数组是不是某二叉搜索树的后序遍历的结果.如果是则输出Yes,否则输出No.假设输入的数组的任意两个数字都互不相同. 解题思路 对于后序遍历来说, ...

  4. 剑指offer 28:字符串的排列

    题目描述 输入一个字符串,按字典序打印出该字符串中字符的所有排列.例如输入字符串abc,则打印出由字符a,b,c所能排列出来的所有字符串abc,acb,bac,bca,cab和cba. 解题思路 参考 ...

  5. 剑指offer java版 test42—找出和为S的两个数,左右夹逼法(双指针法)

    前言:受上一题的启发,这题自己也编出来了.在碰到这种有序数列的时候,经常会用到双指针法,一个指左边,一个指右边,然后依照规则移动,增加或缩小范围.很实用. 题目: 输入一个递增排序的数组和一个数字S, ...

  6. 【剑指offer】 登峰造极--字符串的排列

    时间限制:C/C++ 1秒,其他语言2秒 空间限制:C/C++ 32M,其他语言64M 热度指数:623760 本题知识点: 字符串 动态规划 递归 算法知识视频讲解 题目描述 输入一个字符串,按字典 ...

  7. 剑指offer java版(一)

    二维数组中的查找 问题描述 在一个二维数组中(每个一维数组的长度相同),每一行都按照从左到右递增的顺序排序,每一列都按照从上到下递增的顺序排序.请完成一个函数,输入这样的一个二维数组和一个整数,判断数 ...

  8. 剑指offer没有java版吗_剑指Offer(Java版) 持续更新中

    面试题2 单例(之前有整理,略) 面试题3 二维数组中的查找 public boolean find(int target, int [][] array) { boolean found = fal ...

  9. 剑指Offer第二版Java代码实现

    剑指Offer第二版Java代码实现 A.单例模式 面试题 2:实现Singleton模式 B.面试需要的基础知识 面试题 3:数组中重复的数字 面试题 4:二维数组的查找 面试题 5:替换空格 面试 ...

  10. 剑指offer第二版(leetcode)Java题解(不断更新)

    1 数组中的重复数字 题目 在一个长度为 n 的数组 nums 里的所有数字都在 0-n-1 的范围内.数组中某些数字是重复的,但不知道有几个数字重复了,也不知道每个数字重复了几次.请找出数组中任意一 ...

最新文章

  1. python导出xlsx_使用python库xlsxwriter库来输出各种xlsx文件
  2. 关于优酷开放SDk之setOnAdCountListener和setOnNetworkSppedListener
  3. Druid配置文件demo
  4. 注解源代码学习 - Annotation @InjectMocks and @Mock debug
  5. 本周Java技巧#7 – Maven慢吗?
  6. 前端学习(1310):http协议
  7. 关于Keras在测试不能使用正则dropout的解决
  8. java web swing 教程_好程序员Java教程解读什么是swing
  9. shell自动化巡检
  10. python 文件和目录基本操作_Python常用的文件及文件路径、目录操作方法汇总介绍...
  11. Nginx配置SSL报错 nginx: [emerg] unknown directive ssl
  12. 分分钟学会系列:mac地址泛洪攻击实验
  13. linux管理员基础知识
  14. 新系统软件著作权申请流程及注意事项
  15. Firebird学习(02):数据库的中文参考资料
  16. Spanning Tree协议安全攻防
  17. OBS Classic经典版已经不再支持
  18. Winfrom窗体应用程序图标的改变
  19. 阿里云IoT平台APP配网入口
  20. DVWA sql注入(high)

热门文章

  1. opencv python安装 centos_在Ubuntu中安装OpenCV-Python
  2. 【转载】Docker镜像打包示例
  3. Spring Boot学习
  4. 常见的几种 RuntimeException
  5. JavaScript 中常见设计模式整理
  6. 勿以善小而不为--PPP认证之CHAP与PAP的实现与区别
  7. 第7章 处理串行线路和帧中继连接故障
  8. 牛比的表格处理模块tablib
  9. DispatcherServlet与初始化主线
  10. 4-5 求自定类型元素的最大值 (10分)