字典序算法与全排列问题(时间复杂度O(N))

给定一个不重复数组组成的数组,比如{1,2,3},按照从小到大的顺序组成的全排列整数有6个:123、132、213、231、312、321,这6个数字都是换位数,即组成的数字一样,只是位置不一样而已。

一、最近最大换位数

首先解决第一个问题,如何找到给定整数,离它最近且比它大的换位数。比如:12534的最近最大换位数是12543,13254的最近最大换位数是13425。

为了和原数接近,一个原则是尽量保持高位不变,地位在最小范围内按顺序顺序变化。

以12354为例,逆序区域为54,54两位已经是当前两个数字的最大组合。要想接近原数并且比原数大,就要从倒数第三位开始改变。

12345的倒数第3位是3,我们需要从后面的逆序区域中寻找到刚刚大于3的数字,和3的位置进行互换:

12453

互换后的临时结果是12453,倒数第3位已经确定,这时候最后两位仍然是逆序状态。我们需要把最后两位转变回顺序,以此保证在倒数第3位数值为4的情况下,后两位尽可能小:

12435

获得最近换位数的三个步骤:

  1. 从后向前查看逆序区域,找到逆序区域的前一位,也就是数字置换的边界
  2. 把逆序区域的前一位和逆序区域中刚刚大于它的数字交换位置
  3. 把原来的逆序区域转为顺序

这样,每一步都是O(N),整个算法的时间复杂度为O(N).

代码:


    private static int findTransferPoint(int[] nums) {
        for (int i = nums.length - 1; i > 0; i--) {
            if (nums[i] > nums[i - 1]) {
                return i;
            }
        }
        return 0;
    }


    private static int[] exchange(int[] nums, int index) {
        int head = nums[index - 1];
        for (int i = nums.length - 1; i > 0; i--) {
            if (nums[i] > head) {
                nums[index - 1] = nums[i];
                nums[i] = head;
                break;
            }
        }

        return nums;
    }


    private static int[] reverse(int[] nums, int index) {
        for (int i = index, j = nums.length - 1; i < j; i++, j--) {
            int temp = nums[i];
            nums[i] = nums[j];
            nums[j] = temp;
        }
        return nums;
    }


    public static int[] findNearestNum(int[] nums) {

        int[] numsCopy = Arrays.copyOf(nums, nums.length);
        int index = findTransferPoint(numsCopy);
        if (index == 0) {
            return null;
        }
        exchange(numsCopy, index);
        reverse(numsCopy, index);
        return numsCopy;
    }


测试:

    public static void main(String[] args) {

        int[] nums = {1,2,3,5,4};
        System.out.println("before: "+Arrays.toString(nums));

        findNearestNum(nums);
        System.out.println("result: "+Arrays.toString(findNearestNum(nums)));
    }

输出:

before: [1, 2, 3, 5, 4]
before: [1, 2, 4, 3, 5]

二、全排列

n个不相同的数字的全排列有n!个,写个函数求n的阶乘:

    private static int factorrail(int n) {
        if (n == 1) {
            return 1;
        } else {
            return n * factorrail(n - 1);
        }
    }

利用找最近最大换位数的方法,把数字从小到大排序,找出全部的换位数:

    public static void main(String[] args) {
        int[] nums = {5, 1, 2, 3, 4};
        Arrays.sort(nums);
        int n = factorrail(nums.length);
        System.out.println("1:" + Arrays.toString(nums));
        for (int i = 2; i <= n; i++) {
            nums = findNearestNum(nums);
            System.out.println(i + ":" + Arrays.toString(nums));
        }
    }

输出:

1:[1, 2, 3, 4, 5]
2:[1, 2, 3, 5, 4]
3:[1, 2, 4, 3, 5]
4:[1, 2, 4, 5, 3]
5:[1, 2, 5, 3, 4]
6:[1, 2, 5, 4, 3]
...
117:[5, 4, 2, 1, 3]
118:[5, 4, 2, 3, 1]
119:[5, 4, 3, 1, 2]
120:[5, 4, 3, 2, 1]
点赞