有放回抽签求和解法的层层优化

题目:将写有数字的n个纸片放入口袋中,你可以从口袋中抽取4次纸片,每次记下纸片上的数字后都将其放回口袋中。在抽取前心中记住一个数字m,编写一个程序,判断当纸片上所写的数字一次为Ki(i=1,2,3.....n)时,这四次抽取的纸片上的数字之后有没有可能为m,如果可能就输出"Yes",不可能则输出"No".

限制:1<=n<=50

         1<=m<=10^8

          1<=Ki<=10^8

输入示例1:                                                输出示例1:

 n=3                                                            Yes

 m=10

k={1,3,5}

输入示例2:                                                输出示例2:

 n=3                                                            No

 m=9

k={1,3,5}

其实拿到这题,有个很简单的思路,就是四重for循环遍历出所有的情况,判断是否存在上述情况,很好想,代码也很好写,如下:

#include<iostream>
#include<cstdlib>
#include<algorithm>
using namespace std;
int main()
{
	int n,m,arr[50];
	cin>>n>>m;
	bool isOK=false;
	for(int i=0;i<n;i++)
	cin>>arr[i];
	
	for(int a=0;a<n;a++)
	  for(int b=0;b<n;b++)
	    for(int c=0;c<n;c++)
		   for(int d=0;d<n;d++)
		      if(arr[a]+arr[b]+arr[c]+arr[d]==m)
		        isOK=true;
		        
     if(isOK) cout<<"Yes"<<endl;
     else cout<<"No"<<endl;
     return 0;
}

这种方法虽然好像,但是大家也自然能够看到这种方法所带来的代价是不小的,复杂度为O(n^4),在n还小时还可以勉强使用,但是一旦n变大,例如1<=n<1000,那么这种方法带来的代价为10^12,显然这时这种方法就不靠谱了。

其实大家这里可以使用一个逆向思维,当我们摸出了三张纸片ka,kb,kc时,在摸第四次之前,我们这时扒开袋子看看袋子中是否存在数字为m-ka-kb-kc的纸片。而在查找时,我们不使用线性搜索的方式查找,而是将已经排好序的数字纸片利用二分查找,这样查找的复杂度为O(logn),这样,算上之前三次摸纸片的复杂度,则复杂度就变成了O(n^3logn),其实就相当于在第一种方法的四重for循环中将最后一个循环变为二分查找。

至于单纯二分查找的代码如下:

#include<iostream>
#include<cstdlib>
#include<algorithm>
using namespace std;
int Binary_search(int n,int a[],int key)
{
	int start=0,end=n-1;
	int mid=(start+end)/2;
	while(end>=start && a[mid]!=key)
	{
		cout<<"start="<<start<<" mid="<<mid<<" end="<<end<<endl;
		if(a[mid]>key) end=mid-1;
		else start=mid+1;
		mid=(start+end)/2;
	}
	if(end>=start)
	     return mid;
    else
         return -1;
}
int main()
{
	int a[10]={1,2,3,4,5,6,7,8,9,10},key;
	sort(a,a+10);
	cin>>key;
	cout<<Binary_search(10,a,key);
	return 0;
}


上面我的代码中,找到则返回元素在数组中下标(从0开始),找不到则返回-1,

但真实的比赛时,STL库中已经有了现成的函数binary_search(start,end,key),这样对于第一种方法的改进而来的代码如下:

#include<iostream>
#include<cstdlib>
#include<algorithm>
using namespace std;
int main()
{
	int n,m,arr[50];
	cin>>n>>m;
	bool isOK=false;
	for(int i=0;i<n;i++)
	cin>>arr[i];
	
	sort(arr,arr+n);
	
	for(int a=0;a<n;a++)
	  for(int b=0;b<n;b++)
	    for(int c=0;c<n;c++)
		  if(binary_search(arr,arr+n,m-arr[a]-arr[b]-arr[c]))//binary_search(头迭代器指针,尾迭代器指针,查找值)
		        isOK=true;
		        
     if(isOK) cout<<"Yes"<<endl;
     else cout<<"No"<<endl;
     return 0;
}

对于上述复杂度为O(n^3logn)的方法,在1<=n<1000时,显然还是力不从心的,按照上述的改进的思路,我们能不能将四重循环中的内两层循环再"脱掉",在第一次和第二次摸出纸片ka,kb后,我们就"超前"想象一下在后两次的摸取后,后两次的数字kc,kd能否凑成和为m-ka-kb。

这样的话我们如何进行"超前的想象"呢?那就是在总体抽取前将n张纸片上的数字两两相加,变成一个大小为n*n的数组arryN,这样,在前两次摸取后,我们再在arryN中利用二分查找m-ka-kb。就相当于在两层for循环找到ka,kb后,再内嵌一个二分查找,具体的代码如下:

#include<iostream>
#include<cstdlib>
#include<algorithm>
using namespace std;
int main()
{
	int n,m,arr[50],arrN[50*50];
	cin>>n>>m;
	bool isOK=false;
	for(int i=0;i<n;i++)
	cin>>arr[i];
	
	
	for(int k=0;k<n;k++)
	  for(int j=0;j<n;j++)
	     arrN[k*n+j]=arr[k]+arr[j];
	   
    sort(arrN,arrN+n*n);
	   
	for(int a=0;a<n;a++)
	  for(int b=0;b<n;b++)
		      if(binary_search(arrN,arrN+n*n,m-arr[a]-arr[b]))//binary_search(头迭代器指针,尾迭代器指针,查找值)
		        isOK=true;
		        
     if(isOK) cout<<"Yes"<<endl;
     else cout<<"No"<<endl;
     return 0;
}


现在来计算下该方法的复杂度:

大小为n^2二分查找的复杂度为O(log n^2)=O(2logn),两层for循环的复杂度为O(n^2),这样总体的复杂度为O(n^2*2logn),去掉常系数则复杂度为(n^2*logn),而对于大小为n^2的数组进行排序,复杂度为O(n^2log(n^2))=O(n^2*2logn),去掉常系数则复杂度仍为(n^2*logn),综上情况的话,第三种方法的复杂度则为(n^2*logn),显然相对于O(n^4)优化了不少。

可能有的同学会继续进行"超前想象",在值摸出一张纸片后,就想象后三次纸片上数字,这样,光对大小为n^3的数组进行排序复杂度就有(n^3*logn)显然没有达到优化的效果。

还有个小细节,那就是本题实质是针对有放回的排列组合的优化,如果取出的纸片不能放回,那么这种方法就不适用了,目前我能想到的就是利用递归将所有不重复的排列组合列出来,再进行求和判断~

PS:这题在层层进行优化的过程中,其实很有意思,让我掌握了对于复杂度较高的程序的一个优化思想,挺好~

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值