面试中关于排列的题目还是蛮常见的,之前有个同学面百度,就遇到了一个排列题目,分享一下。
题目描述:
给你一个数组,如果是按照数字的大小排序,那么请你输出当前数组的下一个排列是什么
例如, 下面的数据,就是按照排列序生成的四组数据。
3 1 2 4 5
3 1 2 5 4
3 1 4 2 5
3 1 4 5 2
虽然有个函数叫next_permutation, 不过做OJ还好,面试这个一定不行啦,所以还是自己分析一下。
分析:
我们用字典序的排列生成方法:
生成给定全排列的下一个排列
所谓一个的下一个就是这一个与
下一个之间没有其他的。这就要求这一个与下一个有尽可能长的共同前缀,也即变化限制在尽可能短的后缀上。
算法分三个步骤:
一般而言,设P是[1,n]的一个全排列。
1. P=P1P2…Pn=P1P2…Pj-1PjPj+1…Pk-1PkPk+1…Pn
j=max{i|Pi<Pi+1},k=max{i|Pi>Pj}
2. 对换Pj,Pk,将Pj+1…Pk-1PjPk+1…Pn翻转,
3. P’= P1P2…Pj-1PkPn…Pk+1PjPk-1…Pj+1即P的下一个。
代码如下:
- Memory: 168K Time: 469MS
- Language: C++ Result: Accepted
- Source Code
- #include<stdio.h>
- #include <memory.h>
- void swap( int &a, int &b)
- {
- a = a ^ b;
- b = a ^ b;
- a = a ^ b;
- }
- bool GetNextPermutation(int a[], int size)
- {
- int m = size – 1;
- int i,j;
- while(m > 0 && a[m-1] > a[m]) // step 1
- {
- m–;
- }
- //in this case, the current permutation is the last
- if(m == 0) //
- {
- for( i = 0, j = size – 1; i < j; i++, j–)
- {
- swap(a[i], a[j]);
- }
- return false;
- }
- for( j = size – 1; j > m – 1; j–)//step 2
- {
- if(a[j] > a[m-1])
- {
- swap(a[j], a[m-1]);
- break;
- }
- }
- for( i = m, j = size – 1; i < j; i++, j–)//step 3
- {
- swap(a[j], a[i]);
- }
- return true;
- }
- void printArray(int a[], int size)
- {
- int i;
- for( i = 0; i < size; i++)
- {
- if( i == 0)
- {
- printf(“%d”, a[i]);
- }
- else
- {
- printf(” %d”, a[i]);
- }
- }
- printf(“\n”);
- }
- int main()
- {
- int nSize;
- int a[1024];
- int ncase;
- scanf(“%d”, &ncase);
- int n,k;
- while(ncase–)
- {
- scanf(“%d%d”, &n, &k);
- for( int i = 0; i < n; i++)
- {
- scanf(“%d”, &a[i]);
- }
- while(k–)
- {
- GetNextPermutation(a, n);
- }
- printArray(a, n);
- }
- return 0;
- }
不过既然提到求出排练顺便再给出一个生成所有排列的方法。就是组合数学里面说的“交换法”
这个方法运行的流程如下:
考虑{1,2…n}的一个排列,其上每一个整数都给了一个方向,我们称整数k是可移的(Mobile&Active),如果它的箭头所指的方向的邻点小于它本身。例如
中6、3、5都是可移的。显然1永远不可移,n除了以下两种情形外,它都是可移的:
(1) n是第一个数,且其方向指向左侧
(2) n是最后一个数,且其方向指向右侧
于是,我们可由
按如下算法产生所有排列
算法
1,开始时:
2,当存在可移数时
(a)找最大的可移数m
(b)将m与其箭头所指的邻数互换位置
(c)将所得排列中比m大的数p的方向调整,即改为相反方向。
代码如下:
- #include <stdio.h>
- #include <memory.h>
- enum Direction{LEFT, RIGHT}; //方向枚举
- struct Num //每个数有一个方向和值
- {
- Direction d;
- int value;
- };
- Num permutation[100];
- //if return value is -1, it means we can’t find the permutation anymore
- int GetFirstMoveable(int nsize) //在所有数里面找到最大的可以移动的数,返回下标
- {
- int maxValue = 0;
- int idx = -1;
- for( int i = 0;i < nsize; i++)
- {
- if( i == 0 && permutation[i].d == LEFT)//最左边,并且移动方向是左
- {
- continue;
- }
- if( i == nsize – 1 && permutation[i].d == RIGHT)//最右边,并且移动方向向右
- {
- continue;
- }
- if(permutation[i].d == LEFT)//如果方向是左,并且比左侧元素大
- {
- if(permutation[i].value > permutation[i-1].value)
- {
- if(maxValue < permutation[i].value)
- {
- maxValue = permutation[i].value;
- idx = i;
- }
- }
- }
- else
- {
- if(permutation[i].value > permutation[i+1].value)//方向向右,比右侧大
- {
- if(maxValue < permutation[i].value)
- {
- maxValue = permutation[i].value;
- idx = i;
- }
- }
- }
- }
- return idx;
- }
- void reverseDirection(int r, int nsize) //交换完了以后,将所有大于当前交换的值的 方向调反
- {
- for( int i = 0; i < nsize; i++)
- {
- if(permutation[i].value > permutation[r].value)
- {
- if(permutation[i].d == LEFT)
- {
- permutation[i].d = RIGHT;
- }
- else
- {
- permutation[i].d = LEFT;
- }
- }
- }
- }
- void swap(Num & a, Num & b)
- {
- Num t = a;
- a = b;
- b = t;
- }
- void swapValue(int r, int &nr)//交换相邻的值
- {
- if(permutation[r].d == LEFT)
- {
- swap(permutation[r], permutation[r-1]);
- nr = r – 1;
- }
- else
- {
- swap(permutation[r], permutation[r+1]);
- nr = r + 1;
- }
- }
- void printAll(int nsize)
- {
- for(int i = 0; i < nsize; i++)
- {
- printf(“%d “, permutation[i].value );
- }
- printf(“\n”);
- }
- int main()
- {
- int n;
- while(scanf(“%d”,&n) != EOF)
- {
- for( int i = 0; i < n;i++)
- {
- permutation[i].value = i + 1;
- permutation[i].d = LEFT;
- }
- int r;
- int nr;
- printAll(n);
- while( (r = GetFirstMoveable(n)) != -1)//算法核心循环
- {
- swapValue(r, nr);
- reverseDirection(nr, n);
- printAll(n);
- }
- }
- return 0;
- }