31. Next Permutation

Implement next permutation, which rearranges numbers into the lexicographically next greater permutation of numbers.

If such arrangement is not possible, it must rearrange it as the lowest possible order (ie, sorted in ascending order).

The replacement must be in-place, do not allocate extra memory.

Here are some examples. Inputs are in the left-hand column and its corresponding outputs are in the right-hand column.
1,2,3 → 1,3,2
3,2,1 → 1,2,3
1,1,5 → 1,5,1

全排列生成算法:next_permutation
先对序列大小的比较做出定义:两个长度相同的序列,从两者的第一个元素开始向后寻找,直到出现一个不同元素(也可能就是第它们的第一个元素),该元素较大的序列为大,反之序列为小;若一直到最后一个元素都相同,那么两个序列相等。
设当前序列为pn,下一个较大的序列为pn+1,这里蕴藏的含义是再也找不到另外的序列pm,使得pn < pm < pn+1。
问题
给定任意非空序列,生成下一个较大或较小的排列。
过程
  根据上述概念易知,对于一个任意序列,最小的排列是增序,最大的为减序。那么给定一个pn要如何才能生成pn+1呢?先来看下面的例子:
  设3 6 4 2为pn,下一个序列pn+1应该是4 2 3 6。观察第一个序列可以发现pn中的6 4 2已经为减序,在这个子集中再也无法排出更大的序列了,因此必须移动3的位置且要找一个数来取代3的位置。在6 4 2中6和4都比3大,但6比3大的太多了,只能选4。将4和3的位置对调后形成排列4 6 3 2。注意,由于4和3大小的相邻关系,对调后产生的子集6 3 2仍保持逆序,即该子集最大的一种排列。而4是第一次移动到头一位的,需要后面的子集为最小的排列,因此直接将6 3 2倒转为2 3 6便得到了正确的一个序列pn+1。
  下面归纳分析该过程。假设一个有m个元素的序列pn,其下一组较大排列为pn+1:
  若pn的最后的2个元素构成一个最小的增序子集,那么直接反转这2个元素使该子集成为减序即可得到pn+1。理由是pn和pn+1的前面m-2个元素都相等(没有对前面的元素进行操作),仅能靠最后2个元素来分出大小。而这2个元素只能出现2种排列,其中较大的一种是减序。
  若pn的最后最多有s个元素构成一个减序子集,令i = m - s,则有pn(i) < pn(i+1),因此若将pn(i)和pn(i+1)调换必能得到一个较大的排列(不一定是下一个),因此必须保持pn(i)之前的元素不动,并在子集{pn(i+1), pn(i+2), …, pn(m)}中找到一个仅比pn(i)大的元素pn(j),将二者调换位置。此时只要得到新子集{pn(i+1), pn(i+2), …, pn(i), …,pn(m)}的最小排列即可。注意到新子集仍保持减序,那么直接将其反转即可得到最小的增序子集。
  按以上步骤便可从pn得到pn+1了。
  复杂度
  最好的情况为pn的最后的2个元素构成一个最小的增序子集,交换次数为1,复杂度为O(1),最差的情况为1个元素最小,而后面的所有元素构成减序子集,这样需要先将第1个元素换到最后,然后反转后面的所有元素。交换次数为1+(n-1)/2,复杂度为O(n)。这样平均复杂度即为O(n/2)。

package com.gloomy.leetcode.medium;

import java.util.Arrays;

/**
 * Implement next permutation, which rearranges numbers into the
 * lexicographically next greater permutation of numbers.
 * 
 * If such arrangement is not possible, it must rearrange it as the lowest
 * possible order (ie, sorted in ascending order).
 * 
 * The replacement must be in-place, do not allocate extra memory.
 * 
 * Here are some examples. Inputs are in the left-hand column and its
 * corresponding outputs are in the right-hand column. 1,2,3 → 1,3,2 3,2,1 →
 * 1,2,3 1,1,5 → 1,5,1
 * 
 * @author 过路的守望
 *
 */
public class NextPermutation {
    public static void main(String[] args) {
        int[] nums = { 1 };
        new NextPermutation().nextPermutation(nums);
        System.out.println(Arrays.toString(nums));
    }

    public void nextPermutation(int[] nums) {
        if (nums == null || nums.length == 0) {
            return;
        }
        int len = nums.length;
        /*
         * index为-1代表数组自身就是按降序排列的
         */
        int index = -1;
        for (int i = len - 1; i >= 1; i--) {
            if (nums[i] > nums[i - 1]) {
                index = i;
                break;
            }
        }
        /**
         * 数组自身按照降序排列
         */
        if (index == -1) {
            reverse(nums, 0, len - 1);
        }
        /*
         * 最后两个元素按照升序排列,交换即可得下一个序列
         */
        else if (index == len - 1) {
            swap(nums, index, index - 1);
        } else {
            /*
             * index————len-1之间的元素按照降序排列。
             */
            int pivot = index - 1;
            /*
             * 找出index到len-1之间恰好比nums[piovot]大的元素,交换两者位置。然后翻转index到len-1之间的元素。
             */
            for (int i = len - 1; i > index; i--) {
                if (nums[i] > nums[pivot]) {
                    index = i;
                    break;
                }
            }
            swap(nums, index, pivot);
            reverse(nums, pivot + 1, nums.length - 1);
        }
    }

    /**
     * 交换元素
     * 
     * @param nums
     * @param i
     * @param j
     */
    private void swap(int[] nums, int i, int j) {
        int temp = nums[i];
        nums[i] = nums[j];
        nums[j] = temp;
    }

    /**
     * 翻转数组
     * 
     * @param nums
     * @param start
     * @param end
     */
    private void reverse(int[] nums, int start, int end) {
        int temp = 0;
        while (start < end) {
            temp = nums[start];
            nums[start] = nums[end];
            nums[end] = temp;
            start++;
            end--;
        }
    }

}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值