万书网 > 文学作品 > 漫画算法:小灰的算法之旅 > 5.8寻找全排列的下一个数

5.8寻找全排列的下一个数





5.8.1 一道关于数字的题目

下面我来考查你一道算法题,假设给出一个正整数,请找出这个正整数所有数字全排列的下一个数。

题目

给出一个正整数,找出这个正整数所有数字全排列的下一个数。

说通俗点就是在一个整数所包含数字的全部组合中,找到一个大于且仅大于原数的新整数。让我们举几个例子。

如果输入12345,则返回12354。

如果输入12354,则返回12435。

如果输入12435,则返回12453。

让我想一想啊……

我发现了,这里面有个规律!让我来解释一下。

小灰发现的“规律”如下。

输入12345,返回12354,那么

12354  -  12345  =  9,

刚好相差9的一次方。

输入12354,返回12435,那么

12435  -  12354  =  81,

刚好相差9的二次方。

所以,每次计算最近的换位数,只需要加上9的n次方即可。

怎么样,我是不是很机智?

这算哪门子规律?  12453-12435=  18,24135-23541=594,也并不都是9的整数次幂啊!

啊,尴尬了……

呵呵,今天就到这里,回家等通知去吧!



5.8.2 解题思路

小灰,你刚刚去面试了?结果怎么样?

唉……

大黄,你能不能给我讲讲,怎么样寻找一个整数所有数字全排列的下一个数?

好啊,在给出具体解法之前,小灰你先思考一个问题:由固定几个数字组成的整数,怎样排列最大?怎样排列最小?

让我想一想啊……

知道了,如果是固定的几个数字,应该是在逆序排列  的情况下最大,在顺序排列  的情况下最小。

举一个例子。

给出1、2、3、4、5这几个数字。

最大的组合:54321  。

最小的组合:12345  。

没错,数字的顺序和逆序,是全排列中的两种极端情况。那么普遍情况下,一个数和它最近的全排列数存在什么关联呢?

例如给出整数12354,它包含的数字是1、2、3、4、5,如何找到这些数字全排列之后仅大于原数的新整数呢?

为了和原数接近,我们需要尽量保持高位不变,低位在最小的范围内变换顺序  。

至于变换顺序的范围大小,则取决于当前整数的逆序区域  。

如图所示,12354的逆序区域是最后两位,仅看这两位已经是当前的最大组合。若想最接近原数,又比原数更大,必须从倒数第3位  开始改变。

怎样改变呢?12345的倒数第3位是3,我们需要从后面的逆序区域中找到大于3的最小的数字,让其和3的位置进行互换。

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

这样一来,就得到了想要的结果12435  。

有些明白了,不过还真是复杂呀!

看起来复杂,其实只要3个步骤。

获得全排列下一个数的3个步骤。

1.  从后向前查看逆序区域,找到逆序区域的前一位,也就是数字置换的边界。

2.  让逆序区域的前一位和逆序区域中大于它的最小的数字交换位置。

3.  把原来的逆序区域转为顺序状态  。

最后让我们用代码来实现一下。这里为了方便数字位置的交换,入参和返回值的类型都采用了整型数组。

1.  public  static  int[]  findNearestNumber(int[]  numbers){

2.  //1.  从后向前查看逆序区域,找到逆序区域的前一位,也就是数字置换的边界

3.  int  index  =  findTransferPoint(numbers);

4.  //  如果数字置换边界是0,说明整个数组已经逆序,无法得到更大的相同数

5.  //  字组成的整数,返回null

6.  if(index  ==  0){

7.  return  null;

8.  }

9.  //2.把逆序区域的前一位和逆序区域中刚刚大于它的数字交换位置

10.  //复制并入参,避免直接修改入参

11.  int[]  numbersCopy  =  Arrays.copyOf(numbers,  numbers.length);

12.  exchangeHead(numbersCopy,  index);

13.  //3.把原来的逆序区域转为顺序

14.  reverse(numbersCopy,  index);

15.  return  numbersCopy;

16.  }

17.

18.  private  static  int  findTransferPoint(int[]  numbers){

19.  for(int  i=numbers.length-1;  i>0;  i--){

20.  if(numbers[i]  >  numbers[i-1]){


21.  return  i;

22.  }

23.  }

24.  return  0;

25.  }

26.

27.  private  static  int[]  exchangeHead(int[]  numbers,  int  index){

28.  int  head  =  numbers[index-1];

29.  for(int  i=numbers.length-1;  i>0;  i--){

30.  if(head  <  numbers[i]){

31.  numbers[index-1]  =  numbers[i];

32.  numbers[i]  =  head;

33.  break;

34.  }

35.  }

36.  return  numbers;

37.  }

38.

39.  private  static  int[]  reverse(int[]  num,  int  index){

40.  for(int  i=index,j=num.length-1;  i
41.  int  temp  =  num[i];

42.  num[i]  =  num[j];

43.  num[j]  =  temp;

44.  }

45.  return  num;

46.  }

47.

48.  public  static  void  main(String[]  args)  {

49.  int[]  numbers  =  {1,2,3,4,5};

50.  //打印12345  之后的10个全排列整数

51.  for(int  i=0;  i<10;i++){

52.  numbers  =  findNearestNumber(numbers);

53.  outputNumbers(numbers);

54.  }

55.  }

56.

57.  //  输出数组

58.  private  static  void  outputNumbers(int[]  numbers){

59.  for(int  i  :  numbers){

60.  System.out.print(i);

61.  }

62.  System.out.println();

63.  }

这种解法拥有一个“高大上”的名字:字典序算法  。

小灰,你说说这个解法的时间复杂度是多少?

该算法3个步骤每一步的时间复杂度都是O(n),所以整体时间复杂度也是O(n)  !

完全正确。关于这道算法题的解答就介绍到这里,咱们下一节再会!