18 获取给定的序列的所有排列, 组合

前言

本博文部分图片, 思路来自于剑指offer 或者编程珠玑

问题描述

18 获取给定的序列的所有排列, 组合_第1张图片

18 获取给定的序列的所有排列, 组合_第2张图片

思路

这里有两个问题, 一个是求所有的字符的全排列, 一个是求所有字符的组合

对于问题一, 书上给了一种解法
思路 : 对于一个原始序列, 第一次交换第一个字符 和第一个字符以及后面的字符, 然后递归进入交换第二个字符阶段[同样是交换第二个字符 以及之后的字符], 这样直到交换完所有的字符之后, 打印出序列, 接着返回上一层递归, 交换回刚才的交换的两个字符, 进入下一个交换对象的交换
对于[a, b, c, d], 这样一个序列来说, 第一个字符存在4种交换可能, 第二个字符存在3种, … 最后共有 “4 * 3 * 2 * 1” 种交换序列

接下来一种思路是我实现的比较传统的思路 : 将备选字符放在一个容器, 从中抽取一个数据[不放回], 然后进行递归, 直到抽出的元素个数达到了我们的需求
同样 对于第一次抽取可能存在4种可能, 第二次抽取可能存在3种可能, … 最后共有 “4 * 3 * 2 * 1” 种序列


对于问题二

思路 : 我们建立一个抽取规约 : 在抽取了一个元素之后, 我们只能抽取这个元素之后的其他元素[相比于上面传统的思路 选择的余地主要是少了抽取的元素之前的元素]

示范一下 : 比如 “a, b, c”, 假设我们第一次抽取了”b”, 对于问题一的思路, 剩下的备选元素为”a, c”, 然而 对于问题二, 剩下的备选元素为”c”, 因为如果在抽取”b”前面的数据”a”的时候, 可能会造成与第一次抽取”a”的某个组合重叠, so 建立这个规约的目的就是为了防止各个组成元素相同的排列的重复

参考代码

/**
 * file name : Test11CharCompletePermutation.java
 * created at : 9:23:24 AM Jun 8, 2015
 * created by 970655147
 */

package com.hx.test05;

public class Test11CharCompletePermutation {

    // 1. 给定一个char[] 要求打印出全排列
    // 2. 打印出所有的组合
    public static void main(String[] args) {

        char[] chars = new char[] {'a', 'b', 'c', 'd' };
        Deque deque = new LinkedList();
        charCompletePermutation(chars, deque, chars.length);
//      charCompletePermutation02(chars, 0);
        Log.horizon();

        deque.clear();
        charCombination(chars, deque);

    }

    // 思路 : 使用一个栈 [或者一个数组 [如果使用数组的话, 直接在下面push的地方改成响应的索引赋值, 然后去掉pop即可] ]来保存排列
        // 每次从chars中拿走一个字符, 然后把剩下的字符传入charCompletePermutation 进行递归
    public static void charCompletePermutation(char[] chars, Deque deque, int getN) {
        if(chars == null) {
            return ;
        }
        if(getN == 0 ) {
            Log.log(deque);
        }

        for(int i=0; i1);
            deque.pop();
        }

    }
    // 交换start 和start之后的数据
        // 递归交换start+1 和start+1之后的数据
    // 第一位有chars.length中可能, 第二位有chars.length-1种可能, ...
    public static void charCompletePermutation02(char[] chars, int start) {
        if(start == chars.length) {
            Log.log(chars);
            return ;
        }

        for(int i=start; i1);
            swap(chars, i, start);
        }
    }

    // 计算字符序列的全组合
        // 从getN属于[1, chars.length]  获取chars中getN个字符的组合
        // 获取小于(chars.length/2)个数的组合的时候  使用charCombinationForN方法
        // 获取大于(chars.length/2)个数的组合的时候  使用charCombinationForHalfLater方法
    public static void charCombination(char[] chars, Deque deque) {

        for(int i=1; i<=chars.length/ 2; i++ ) {
            charCombinationForN(chars, deque, i);
        }
        for(int i=chars.length/2+1; i<=chars.length; i++) {
            charCombinationForHalfLater(chars, chars, deque, chars.length-i);
        }

    }

    // 适用于计算sourceChars中的长度小于(sourceChars.length/2)的组合
    // 计算从chars中取出getN个字符的序列
        // 从chars中取出一个字符, 然后递归在其之后的数据中取出getN-1个字符
    private static void charCombinationForN(char[] chars, Deque deque, int getN) {
        if(getN > chars.length ) {
            return ;
        }
        // 注意 : 这里应该是退出条件之一, 使用完序列之后   应该return
        if(getN == 0) {
            Log.log(deque);
            return ;
        }

        // .. 最多到(chars.length-getN)开始
        int end = chars.length - getN;
        for(int i=0; i<=end; i++) {
            deque.push(chars[i]);
            charCombinationForN(copyAfter(chars, i), deque, getN-1);
            deque.pop();
        }

    }

    // 适用于计算sourceChars中的长度大于(sourceChars.length/2)的组合
    // 计算从chars中取出(chars.length-compeltementyN)个字符的序列
    // 从chars中取出一个字符, 然后递归在其之后的数据中取出(compeltementyN-1)个字符
    private static void charCombinationForHalfLater(char[] sourceChars, char[] chars, Deque deque, int compeltementyN) {
        if(compeltementyN > chars.length ) {
            return ;
        }
        // 注意 : 这里应该是退出条件之一, 使用完序列之后   应该return
        if(compeltementyN == 0) {
//          Log.log(deque);
            Log.logWithoutLn("[");
            for(char ch : sourceChars) {
                if(! deque.contains(ch)) {
                    Log.logWithoutLn(ch + ", ");
                }
            }
            Log.logWithoutLn("]");
            Log.enter();

            return ;
        }

        // .. 最多到(chars.length-getN)开始
        int end = chars.length - compeltementyN;
        for(int i=0; i<=end; i++) {
            deque.push(chars[i]);
            charCombinationForHalfLater(sourceChars, copyAfter(chars, i), deque, compeltementyN-1);
            deque.pop();
        }

    }

    // 复制除了exceptIdx 之外的其他元素
    private static char[] copyExcept(char[] chars, int excpetIdx) {
        if((excpetIdx < 0) || (excpetIdx >= chars.length) ) {
            return chars;
        }

        char[] newChars = new char[chars.length-1];
        System.arraycopy(chars, 0, newChars, 0, excpetIdx);
        System.arraycopy(chars, excpetIdx+1, newChars, excpetIdx, chars.length-excpetIdx-1);
        return newChars;
    }

    // 注意 : 不包括from
    // 复制chars中from之后的元素
    private static char[] copyAfter(char[] chars, int from) {
        if((from < 0) || (from >= chars.length) ) {
            return chars;
        }

        char[] newChars = new char[chars.length-from-1];
        System.arraycopy(chars, from+1, newChars, 0, newChars.length);
        return newChars;
    }

    // 交换数组中给定的两个索引的数据
    private static void swap(char[] arr, int idx01, int idx02) {
        char tmp = arr[idx01];
        arr[idx01] = arr[idx02];
        arr[idx02] = tmp;
    }


}

效果截图

18 获取给定的序列的所有排列, 组合_第3张图片

总结

对于后两者的思路, 更好是使用一个boolean[] 来标记各个索引对应的数据是否可抽取, 但是 这里为了易读, 就这样copyExcept/ copyAfter了

注 : 因为作者的水平有限,必然可能出现一些bug, 所以请大家指出!

你可能感兴趣的:(08,[剑指Offer,&,编程珠玑]_笔记)