剑指Offer[38]:字符串的排列

题目

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

题目来源于牛客网,对剑指Offer书中字符串排列问题做了小小修改,也就是需要按字典序打印出所有的排列。也就是说两个字符排列需要根据从左到右逐个比较对应的字符的先后来确定先后顺序。

1. 常规解法

剑指Offer中给出的解法最后得到的所有全排列是乱序的,因此最后再对得到的所有字符串进行排序就行了。核心的思路就是以下两步:

  1. 求所有出现在第一个位置的字符,就是把第一个位置的字符和后面所有位置的字符交换;
  2. 固定第一个位置的字符,求后面所有字符的全排列。

以"abc"为例,求它的全排列可以用下图描述(图来源于百度):

从图和上面的步骤2可以看到这显然是一种递归的思路,最后代码如下:

import java.util.ArrayList;
import java.util.Collections;
public class Solution38 {
    public ArrayList<String> Permutation(String str) {
        if(str.length() <= 0){
            return new ArrayList();
        }
        ArrayList<String> strList = new ArrayList();
        Permutation(str.toCharArray(), strList, 0);  
        Collections.sort(strList);  // 牛客网的题目要求所有排列按字典顺序打印
        return (ArrayList)strList;
    }
    
    private void Permutation(char[] strArr, ArrayList strList, int begin){
        if(begin == strArr.length - 1){   // 已经到了字符串末尾并且列表中没有当前字符串
            if(!strList.contains(new String(strArr))){   // 将其加入列表中
                strList.add(new String(strArr));
            }
            return;
        }
        for(int i = begin; i < strArr.length; i++){  // 依次交换头部字符和后面的每一个字符
                swap(strArr, i, begin);   
                Permutation(strArr, strList, begin + 1);  // 递归地对后面部分进行排列
                swap(strArr, i, begin);    // 复位操作,相当于回溯
            }
    }
    
    private void swap(char[] arr, int i, int j){
        char temp = arr[i];
        arr[i] = arr[j];
        arr[j] = temp;
    }
}

最后在得到所有的全排列之后,用Collections类的sort函数对存储全排列的List进行排序就可以得到字典序的全排列。

2. 非递归的方法

偶然看到的一个大佬的解法,原文在这:https://www.cnblogs.com/pmars/archive/2013/12/04/3458289.html
一个全排列可看做一个字符串,字符串可有前缀、后缀。生成给定全排列的下一个排列.所谓一个的下一个就是这一个与下一个之间没有其他的。这就要求这一个与下一个有尽可能长的共同前缀,也即变化限制在尽可能短的后缀上。
直接看这种方法的思路吧:

设P是[1,n]的一个全排列。P= P 1 P 2 … P j − 1 P j P j + 1 … P k − 1 P k P k + 1 … P n P_1P_2…P_{j-1}P_jP_{j+1}…P_{k-1}P_kP_{k+1}…P_n P1P2Pj1PjPj+1Pk1PkPk+1Pn
Find: j = m a x { i ∣ P i < P i + 1 } j = max\{i|P_i<P_i+1\} j=max{iPi<Pi+1}
    k = m a x { i ∣ P i > P j } k = max\{i|P_i>P_j\} k=max{iPi>Pj}
   swap(Pi, Pj)
   反转 P j + 1 P_{j+1} Pj+1以后的部分(包括 P j + 1 P_{j+1} Pj+1
   得到的P’就是P按字典序的下一个全排列

【例】 如何得到346987521的下一个全排列

1,从尾部往前找第一个P(i-1) < P(i)的位置
     3 4 6 9 8 7 5 2 1
   最终找到6是从后向前第一个变小的数字,记录下6的位置i-1
2,从i位置往后找到最后一个大于6的数
     3 4 6 9 8 7 5 2 1
   最终找到7的位置,记录位置为m
3,交换位置i-1和m的值
     3 4 7 9 8 6 5 2 1
4,倒序i位置后的所有数据
     3 4 7 1 2 5 6 8 9
则347125689为346987521的下一个排列

这种解法的代码如下,最后得到的结果就是按字典序排序的,不需要再排序:

import java.util.ArrayList;
public class Solution {
    public ArrayList<String> Permutation(String str) {
        if(str.length() <= 0){
            return new ArrayList();
        }
        ArrayList<String> strList = new ArrayList();
        char[] chars = str.toCharArray();
        strList.add(String.valueOf(chars));
        int length = chars.length;
        int left, right;
        while(true){
            left = length - 1;
            // 向前查找第一个变小的元素
            while(left > 0 && chars[left] <= chars[left - 1]){
                left--;
            }
            if(left == 0){
                break;
            }
            right = left;
            // 向后查找最后一个大于chars[left - 1]的元素
            while(right + 1 < length && chars[right + 1] > chars[left - 1]){
                right++;
            }
            // 交换两个值
            swap(chars, left - 1, right);
            reverse(chars, left);  // 对left以后的部分进行反转
            strList.add(String.valueOf(chars));
        }
        return strList;
    }
    private void reverse(char[] chars, int start){
        if(start >= chars.length - 1){
            return;
        }
        int length = chars.length;
        for(int i = start; i < (length + start) / 2; i++){
            swap(chars, i, length + start - i - 1);
        }
    }
    
    private void swap(char[] arr, int i, int j){
        char temp = arr[i];
        arr[i] = arr[j];
        arr[j] = temp;
    }
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值