剑指offer——字符串的排列(好题,扩展题也很好,全排列的算法)

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

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

思路:
看题目的意思,应该就是求一个全排列?
想到一个递归的方法,先把原字符串分割,split(),放入一个char数组。每次递归传入当前的StringBuilder对象和已使用的下标flag数组,可以防止重复使用,当对象的length等于字符串长度后,转换成String加入到结果数组中。


在本地跑是正常的,在OJ系统上提示
测试用例:
a
对应输出应该为:
[“a”]
你的输出为:
java.lang.StringIndexOutOfBoundsException: String index out of range: -1

但这个用例本地也是正常的。

查阅相关资料后发现是split的问题。

网上说split的使用尽量都用转义字符表示。

在java.lang包中有String.split()方法,返回是一个数组:   1、如果用“.”作为分隔的话,必须是如下写法:String.split("\\."),这样才能正确的分隔开,不能用String.split("."); 
2、如果用“|”作为分隔的话,必须是如下写法:String.split("\\|"),这样才能正确的分隔开,不能用String.split("|"); “.”和“|”都是转义字符,必须得加"\\";    
3、如果在一个字符串中有多个分隔符,可以用“|”作为连字符,比如:“acount=? and uu =? or n=?”,把三个都分隔出来,可以用String.split("and|or");

这里只是把String变成字符数组,还是用自带的toCharArray()吧。

因为题目中有提到字典序,所以最后还要Collections.sort(result);
这样即使一开始输入的字符串并不是字典序,最后也会变成字典序。
我的代码如下(这个递归的思路更好理解一点)

import java.util.ArrayList;
public class Solution {
    ArrayList result = new ArrayList<>();
    public ArrayList Permutation(String str) {
       if(str==null||str.length()==0)
           return result;
       String[] a = str.trim().split(""); // toCharArray()
       int length = a.length;
       int[] flag = new int[length];
       StringBuilder sb = new StringBuilder();
        helper(sb,flag,a);
        return result;
    }
    public void helper(StringBuilder sb, int[] flag, String[] a){
        if(sb.length()==flag.length&&!result.contains(sb.toString())){
            result.add(sb.toString());
            return;
        }
        for(int i = 0; iif(flag[i]==1)
                continue;
            sb.append(a[i]);
            flag[i] = 1;
            helper(sb,flag,a);
            flag[i] = 0;
            sb.deleteCharAt(sb.length()-1);
        }
    }
}

解决问题 提交时间 状态 运行时间 占用内存 使用语言
字符串的排列 2017-06-20 答案正确 208 ms 12284K Java


另一种递归思路,将元素交换求全排列。
这个方法必须要加Collection.sort(),即使在输入str是有序的时候也必须进行这一步。
因为一开始是存在了HashSet里,HashSet里的元素是无序的。(不保证输出顺序和输入顺序相同)

   public ArrayList Permutation(String str) {
        ArrayList re = new ArrayList<>();
        if (str == null || str.length() == 0) {
            return re;
        }
        HashSet set = new HashSet();
        fun(set, str.toCharArray(), 0);
        re.addAll(set);
        Collections.sort(re);
        return re;
    }
    void fun(HashSet re, char[] str, int k) {
        if (k == str.length) {
            re.add(new String(str));
            return;
        }
        for (int i = k; i < str.length; i++) {
            swap(str, i, k);
            fun(re, str, k + 1);
            swap(str, i, k);
        }
    }
    void swap(char[] str, int i, int j) {
        if (i != j) {
            char t = str[i];
            str[i] = str[j];
            str[j] = t;
        }
    }

https://jingyan.baidu.com/article/86112f1329e97927379787da.html(用这个图看递归很好,学习!)


非递归的生成字典序方法,目前执行时间最少

import java.util.*;
public class Solution{
public ArrayList Permutation(String str) {
       ArrayList res = new ArrayList<>();

        if (str != null && str.length() > 0) {
            char[] seq = str.toCharArray();
            Arrays.sort(seq); //排列
            res.add(String.valueOf(seq)); //先输出一个解

            int len = seq.length;
            while (true) {
                int p = len - 1, q;
                //从后向前找一个seq[p - 1] < seq[p]
                while (p >= 1 && seq[p - 1] >= seq[p]) --p;
                if (p == 0) break; //已经是“最小”的排列,退出
                //从p向后找最后一个比seq[p]大的数
                q = p; --p;
                while (q < len && seq[q] > seq[p]) q++;
                --q;
                //交换这两个位置上的值
                swap(seq, q, p);
                //将p之后的序列倒序排列
                reverse(seq, p + 1);
                res.add(String.valueOf(seq));
            }
        }

        return res;
    }

    public static void reverse(char[] seq, int start) {
        int len;
        if(seq == null || (len = seq.length) <= start)
            return;
        for (int i = 0; i < ((len - start) >> 1); i++) {
            int p = start + i, q = len - 1 - i;
            if (p != q)
                swap(seq, p, q);
        }
    }

    public static void swap(char[] cs, int i, int j) {
        char temp = cs[i];
        cs[i] = cs[j];
        cs[j] = temp;
    }
}

如果给的原字符串中有重复的字符,虽然用了set结构可以防止重复放入结果集,但是也可以通过代码去重,在for循环中加入一个判断即可,如果测试用例中重复字符很多,可以减小时间复杂度


    static boolean is_swap(char[] s, int i, int k)
    {
        if (i == k) // 自己和自己交换是允许的
            return true;
        for (int j = i; j < k; j++) //保证在i到k的中间,不存在和k相同的字符,如果有的话,说明k重复了,这次交换没有必要
        {
            if (s[j] == s[k])
                return false;
        }
        return true;
    }
return true;
}

扩展延伸题:
剑指offer——字符串的排列(好题,扩展题也很好,全排列的算法)_第1张图片

剑指offer——字符串的排列(好题,扩展题也很好,全排列的算法)_第2张图片
剑指offer——字符串的排列(好题,扩展题也很好,全排列的算法)_第3张图片

你可能感兴趣的:(面试算法题)