面试中关于排列的题目还是蛮常见的,之前有个同学面百度,就遇到了一个排列题目,分享一下。
题目描述:
给你一个数组,如果是按照数字的大小排序,那么请你输出当前数组的下一个排列是什么
例如, 下面的数据,就是按照排列序生成的四组数据。
3 1 2 4 5
3 1 2 5 4
3 1 4 2 5
3 1 4 5 2
百度一下发现POJ上居然有一个一模一样题目: http://poj.org/problem?id=1833.
虽然有个函数叫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,当存在可移数时
#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;
}