百度面试题——需找下一个排列(Find next permuation, POJ 1883)

面试中关于排列的题目还是蛮常见的,之前有个同学面百度,就遇到了一个排列题目,分享一下。


题目描述:

给你一个数组,如果是按照数字的大小排序,那么请你输出当前数组的下一个排列是什么

例如, 下面的数据,就是按照排列序生成的四组数据。

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),如果它的箭头所指的方向的邻点小于它本身。例如

  

635都是可移的。显然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;
}









 

  • 1
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值