Java实现字符串的排序与组合-详细分析实现过程

剑指Offer_24 字符串的排序


2018/6/19 星期二

题目描述

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

输入描述

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

牛客网代码编写框架:

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

    }
}

这个问题就是可转换为,“a”、”b”、”c“三个字符中找出其中所有的排列问题。这就是典型的「全排列」。学过排列组合的我们知道,总的排列可能次数就是 C33=6 C 3 3 = 6 种可能的情况。

这里我们从迭代的思想中进行考虑。我们将 abc 中分解为两个部分,第一个字符和剩余字符部分。如果第一个字符为 a ,那么就是求解剩余的 bc两个字符的全排列。同理,再依次的求结果首字母为 bc 的情况。这就是一个典型的迭代过程。

Ann=n×(n1)××1 A n n = n × ( n − 1 ) × ⋯ × 1 解题思路如下:

  1. 从n 个字符中选出任意一个字符
  2. 从剩余的 n-1个字符中任选一个字符
  3. 依次的选择,直到最后。。。

在下面的 Java 代码中,我们每次将输入字符串转换成字符串数组(char[] chars),方便我们进行字符的个数统计和「选取」。如何从数组中,区分「排列好的前缀」和后面「待排的元素」?我们可以使用一个数字 offset 来标记数组中,数组的前缀和后面待排元素的分界线。当 offset 移动到整个数组的后面时候(也就是 offset == 数组的长度),就开始输出序列。

import java.util.ArrayList;
import java.util.Collections;

/**
 * 字符串的排列与组合
 * <p>一个很经典的问题,就是给你,a,b,c是三个字符串,输出,它所有可能的排列,比如,abc,acb,bac等。
 * 根据排列组合我们知道,总的排列个数是 C_3^3 = 6,如何用编程来实现打印所有字符的需求呢?21 </p>
 *
 * @author jhZhang
 * @date 2018/6/15
 */
public class Solution {
    /**
     * 把字符串拆分成两部分,第一部分不变,第二部分开始进行交换
     *
     * @param str
     */
    public ArrayList<String> Permutation(String str) {
        ArrayList<String> result = new ArrayList<>();
        if (str == null || str.length() <= 0) {
            return result;
        }
        char[] chars = str.toCharArray();
        Premutation(chars, 0, result);
        Collections.sort(result);
        return result;
    }

    private void Premutation(char[] chars, int offset, ArrayList<String> result) {
        int len = chars.length;
        if (offset < 0 || offset > len) {
            return;
        }
        if (offset == len) {
            String str = String.valueOf(chars);
            if (!result.contains(str)) {
                result.add(String.valueOf(chars));
            }
        }
        for (int i = offset; i < len; i++) {
            swap(chars, offset, i);
            // 保存第offset元素,进行交换
            Premutation(chars, offset + 1, result);
            swap(chars, offset, i);
        }
    }

    public void swap(char[] chars, int i, int j) {
        char tmp = chars[j];
        chars[j] = chars[i];
        chars[i] = tmp;
    }
}

上面的这段代码是可以直接通过练习,但是在这里也遇到了几个问题。

  1. 提示我,当输入情况为null时,返回的情况不对,[] 而不是 null。指针情况不对,我是直接返回null,而题目要求应该是先新建个ArrayList 如果为null,则直接返回该新建的list。
  2. 一开始不清楚,String.valueOf() 中输入 char[] 对象时,会直接的返回char数组组合出来的字符串。
  3. 需要考虑字符串中输入重复的情况。
  4. 需要考虑添加的列表进行排序。

测试用例

  1. 功能测试:输入的字符串中有一个或者多个字符
  2. 特殊输入测试:输入的字符串中内容为空或者为Null指针。

本题扩展-如何求出字符串的组合


如果题目要求的不是求字符串的所有排列,而是求字符的所有「组合」(假设所有的字符都不重复),应该怎么办呢?

同样的从一个具体的例子中进行说明,如果我们输入的候选字符格式是,“a”、”b“、”c“ 那么这三个字符存在多少种组合呢?

如果输出结果有一个字符,那么存在,{a,b,c}, C13=3 C 3 1 = 3 种情况;

如果输出结果有两个字符,那么存在,{ab,ac,bc}, C23=3 C 3 2 = 3 种情况;

如果输出结果有三个字符,那么存在,{abc}, C33=1 C 3 3 = 1 种情况。

所以,我们知道,解决上诉组合问题的关键就在于如何计算实现 Cmn C n m 的求法。 Cmn C n m 表示的意义就是从n个字符中取出其中的m个字符。

如何更好的进行编程呢?如何求三个字符的「组合」,我们将整个过程在分解一下。

n 个字符的「组合」,可以分解成求 C1n+C2n+Cmn++Cnn0mn C n 1 + C n 2 + … C n m + ⋯ + C n n 0 ≤ m ≤ n ,所以重点难点在于如何求解出 Cmn C n m 的组合(从 n 个字符中 取出 m 个字符的组合)。

同样的,我们知道 Cmn=Amnm! C n m = A n m m ! 。这个公式所表达的实际意义就可以描述为, Amn A n m 的排列中,剔除字符串的顺序影响。这是我个人的理解,实际上就是将,abcbac 中只保留任意一个。所以,计算组合的时候,我们仍然可以直接的以按照计算「排列」的情况来计算,但在最后的时候,剔除掉这些元素相同的「组合」。

如何求解 Cmn C n m 的元素组合? 这就是一个典型的从 n 个数字中取出其中的 m个数字。

  1. 我们分解一下, Cmn C n m 。我们首先从 n 中任取出一个字符,存入「容器」(该容器表示选中需要输出的m个元素集合);
  2. 然后,从取出字符后的剩余数组中,再任取一个字符;
  3. 依次上述的过程……
  4. 当我们取出字符个数达到 m 时候,就是输出我们当中的一个组合。
  5. 最后,对存入的「容器」去重。

我们这里,选择「容器」为HashSet,因为本身set 集合存储字符串与元素添加顺序无关,且自带去重属性。

package offer.jianzhi.chapter4;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashSet;

/**
 * @author jhZhang
 * @date 2018/6/17
 */
public class Main24Combine {
    public ArrayList<String> combine(String str) {
        ArrayList<String> result = new ArrayList<String>();
        if (str == null && str.isEmpty()) {
            return result;
        }
        // 组合  C_n^1+C_n^2 + …… + C_n^n 
        for (int i = 1; i <= str.length(); i++) {
            combine(str.toCharArray(), new HashSet(i), i, result);
        }
        Collections.sort(result);
        return result;
    }

    /**
     * 计算 C_n^m 的组合次数
     *
     * @param pres   选中的元素
     * @param m      选中的个数
     * @param result 返回的结果
     */
    private void combine(char[] chars, HashSet pres, int m, ArrayList<String> result) {
        if (pres.size() == m) {
            // 将容器转换为字符串
            String stack = String.valueOf(pres);
            // 去除重复元素
            if (!result.contains(stack)) {
                System.out.println(stack);
                result.add(stack);
            }
            return;
        }
        for (int i = 0, len = chars.length; i < len; i++) {
            swap(chars, 0, i);
            char[] newChars = new char[len - 1];
            // 复制未选中的剩余元素
            System.arraycopy(chars, 1, newChars, 0, len - 1);
            pres.add(chars[0]);
            combine(newChars, pres, m, result);
            // 回退一格选中的元素
            pres.remove(chars[0]);
            swap(chars, 0, i);
        }
    }
    public void swap(char[] chars, int i, int j) {
        char tmp = chars[j];
        chars[j] = chars[i];
        chars[i] = tmp;
    }

    public static void main(String[] args) {
        String testStr = "1234";
        new Main24Combine().combine(testStr);
    }
}

最终输出的结果如下:

[1]
[2]
[3]
[4]
[1, 2]
[1, 3]
[4, 1]
[2, 3]
[4, 2]
[4, 3]
[1, 2, 3]
[4, 1, 2]
[4, 1, 3]
[4, 2, 3]
[1, 2, 3, 4]

如果觉得文章存在问题,欢迎留言批评指正,而且我觉得实现的内容应该还是可以再优化的。如果觉得不错,加一波关注共同学习交流。
这里写图片描述

  • 2
    点赞
  • 7
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值