剑指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<String> result = new ArrayList<>();
    public ArrayList<String> 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; i<flag.length; i++){
            if(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


leetcode 题解 (2020-12-06新增,增加剪枝)(非字典序)
https://leetcode-cn.com/problems/zi-fu-chuan-de-pai-lie-lcof/solution/mian-shi-ti-38-zi-fu-chuan-de-pai-lie-hui-su-fa-by/

class Solution {
    List<String> res = new LinkedList<>();
    char[] c;
    public String[] permutation(String s) {
        c = s.toCharArray();
        dfs(0);
        return res.toArray(new String[res.size()]);
    }
    void dfs(int x) {
        if(x == c.length - 1) {
            res.add(String.valueOf(c)); // 添加排列方案
            return;
        }
        
        // 每次进入dfs方法都会新建一个set,因为dfs方法的目的就是为了确认第k位的数字,所以如果for循环中出现的数字已经出现在k位过,则跳过,因为重复了
        HashSet<Character> set = new HashSet<>();
        for(int i = x; i < c.length; i++) {
            if(set.contains(c[i])) continue; // 重复,因此剪枝
            set.add(c[i]);
            swap(i, x); // 交换,将 c[i] 固定在第 x 位 
            dfs(x + 1); // 开启固定第 x + 1 位字符
            swap(i, x); // 恢复交换
        }
    }
    void swap(int a, int b) {
        char tmp = c[a];
        c[a] = c[b];
        c[b] = tmp;
    }
}

作者:jyd
链接:https://leetcode-cn.com/problems/zi-fu-chuan-de-pai-lie-lcof/solution/mian-shi-ti-38-zi-fu-chuan-de-pai-lie-hui-su-fa-by/
来源:力扣(LeetCode)
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。

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

   public ArrayList<String> Permutation(String str) {
        ArrayList<String> re = new ArrayList<>();
        if (str == null || str.length() == 0) {
            return re;
        }
        HashSet<String> set = new HashSet<String>();
        fun(set, str.toCharArray(), 0);
        re.addAll(set);
        Collections.sort(re);
        return re;
    }
    void fun(HashSet<String> 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<String> Permutation(String str) {
       ArrayList<String> 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;
}

扩展延伸题:
这里写图片描述

这里写图片描述
这里写图片描述

  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值