面试题:有限制几个全阵列

给定一个数组,例如
[45,54,2,99,1]
给定一个长度l,比如100

能够对数组中相邻的两个数交换,前提必须满足两个数的和不大于l

求通过这样的交换所可以产生的全排列有多少种?对于同样的数,也默认它们不同。


这是某外企的电面题,感觉要找到比較好的算法还是挺难的。眼下想到的是一种循环暴力搜索加暴力剪枝并回溯的,从測试数据来看。效果还凑合。

思路是

暴力搜索:查看不论什么一个数,看能否够交换到第一位。假设能够。则规模就能够减少而且递归,复杂度为O(n^3+n!)

int orderCount(int *a, int n, int l){
	if(n <= 1)return 1;
	int ans = 0;
	for(int i = 0;i < n;i ++){
		int j;
		for(j = i-1;j >= 0;j --)
			if(a[i]+a[j] > l)
				break;
		if(j >= 0)continue;
		int t = a[i];
		for(j = i-1;j >= 0;j --)
			a[j+1] = a[j];
		ans += orderCount(a+1,n-1,l);
		for(j = 0;j < i;j ++)
			a[j] = a[j+1];
		a[i] = t;
	}
	return ans;
}

剪枝:

1. 假设一个数能够和所以其它的都交换,这个数将相当于剩下的数的全排列然后这个数随意插入,即问题就变成n*f(n-1)

2. 假设一个数不能和不论什么其它的数交换,它将时问题变成左右的全排列相乘,及f(i)*f(n-i-1)

//some additional optimization
//If one number could not exchange with any other, it would divide the problem into two subproblem
//If one number could exchange with any other, n multiply the left exchange solutions
int order(int *a, int n, int l){
	int exn = 0;
	int *exp = new int[n];
	for(int i = 0;i < n;i ++){
		int ex = 0;
		int unex = 0;
		for(int j = 0;j < n;j ++){
			if(i == j)continue;
			if(a[i] + a[j] <= l)
				ex ++;
			else
				unex ++;
		}
		if(unex == n-1)
			return orderCount(a,i,l)*orderCount(a+i+1,n-i-1,l);
		if(ex == n-1){
			int t = a[i];
			for(int j = i-1;j >= exn;j --)
				a[j+1] = a[j];
			exp[exn] = i;
			a[exn ++] = t;
		}
	}
	int x = 1;
	for(int i = 0;i < exn;i ++)
		x *= n-i;
	return x*orderCount(a+exn,n-exn,l);
}

回溯:

上面的剪枝,我们仅仅是对最開始的数据进行了剪枝,后来的搜索就不再处理剪枝了,所以效率并不高。那我们就要考虑是否可以每个递归都来做剪枝。而这里由于数组的位置变化会影响终于的结果。所以无论是搜索剪枝的过程,完毕之后一定要恢复原来的数组。这样才不会影响其它接下来过程的结果。于是就得到终于的每一步都剪枝的循环方法。

经过測试,这样的方法对于測试数据可以高速得到结果,明显优于上面的单纯暴力方法。

int order(int *a, int n, int l);
//The code modified in the interview
int orderCount(int *a, int n, int l){
	if(n <= 1)return 1;
	int ans = 0;
	for(int i = 0;i < n;i ++){
		int j;
		for(j = i-1;j >= 0;j --)
			if(a[i]+a[j] > l)
				break;
		if(j >= 0)continue;
		int t = a[i];
		for(j = i-1;j >= 0;j --)
			a[j+1] = a[j];
		//ans += orderCount(a+1,n-1,l);
		ans += order(a+1,n-1,l);
		for(j = 0;j < i;j ++)
			a[j] = a[j+1];
		a[i] = t;
	}
	return ans;
}
//some additional optimization
//If one number could not exchange with any other, it would divide the problem into two subproblem
//If one number could exchange with any other, n multiply the left exchange solutions
int order(int *a, int n, int l){
	int exn = 0;
	int *exp = new int[n];
	for(int i = 0;i < n;i ++){
		int ex = 0;
		int unex = 0;
		for(int j = 0;j < n;j ++){
			if(i == j)continue;
			if(a[i] + a[j] <= l)
				ex ++;
			else
				unex ++;
		}
		if(unex == n-1)
			return orderCount(a,i,l)*orderCount(a+i+1,n-i-1,l);
		if(ex == n-1){
			int t = a[i];
			for(int j = i-1;j >= exn;j --)
				a[j+1] = a[j];
			exp[exn] = i;
			a[exn ++] = t;
		}
	}
	int x = 1;
	for(int i = 0;i < exn;i ++)
		x *= n-i;
	int ans = x*orderCount(a+exn,n-exn,l);
	while(exn){
		exn --;
		int t = a[exn];
		for(int j = exn;j < exp[exn];j ++)
			a[j] = a[j+1];
		a[exp[exn]] = t;
	}
	return ans;
}

int _tmain(int argc, _TCHAR* argv[])
{
	int b[] = {45,54,2,80,3,69,2,65,94,74,80,23,8,5,9,3,18,67,99};
	clock_t t1,t2,t3;
	t1 = clock();
	cout<<orderCount(b,19,100)<<endl;
	t2 = clock();
	cout<<order(b,19,100)<<endl;
	t3 = clock();
	cout<<t2-t1<<" vs "<<t3-t2<<endl;
	char ch;
	cin>>ch;
	return 0;
}


版权声明:本文博客原创文章,博客,未经同意,不得转载。

原文地址:https://www.cnblogs.com/blfshiye/p/4673833.html