待字闺中之四个数的和

时间:2022-03-15 11:10:21

盒子中有n张卡片,上面的数字分别为k1,k2,...,kn。你有4次机会,每抽一次,记录下卡片上的数字,再将卡片放回盒子中。如果4个数字的和等于m。则你就赢得游戏,否则就是输。直觉上,赢的可能性太低了。请你给出程序,判断是否有赢的可能性。尽量提高方法的效率。

分析:这个题目,和之前Google的一个概率的题目是类似的,当然解决的方法也是类似的,思路大家可以找找前面的题目,不再赘述。这个题目其实想和大家讲一下思考问题、改进解法的思路。

这个题目最直接的方法就是四重循环遍历,n^4的时间复杂度,比较恐怖,但确实一个很好的起点。不用觉得很丢人,只要我们持续改进即可。假设a,b,c是k1到kn中的三个数字,是否存在d使得,a+b+c+d=m?d在k1到kn中。和题目中的意思是一样的,变换等式如下:d = m - a - b - c

如果满足上式,就是说,要在k1到k2中查找d,即:查找m - a - b - c,找到就存在;找不到,就是不存在。查找有线性查找,二分查找等。四重循环最内一层循环,可以认为是线性的查找,如果想更快一些,可以应用二分查找,而二分查找需要k1到kn是排序的,则先对n个数字进行排序,时间复杂度O(nlogn)。然后最内一层循环,改为二分查找,时间复杂度为O(n^3logn)。所以总的时间复杂度为O(n^3logn)。

经过上面的分析,我们可以得到启发,既然可以提出一个d,那么就可以再提出一个c。将a+b+c+d=m转换为c+d=m-a-b。这样,我们可以枚举k1到kn的两个数的和,然后对n^2个和,进行排序,时间复杂度为o(n^2logn)。然后遍历n^2个和,设每一个和为pair,查找是否存在m-pair,同样二分查找,时间复杂度为O(n^2logn)。总的时间复杂度为O(n^2logn)。

经过上面的层层分析,大家能否发现,对于算法的持续改进,还是有一些窍门的,大家细心领悟。领悟得多了,必会有更大的进步。具体代码如下:

#include<iostream>
#include <algorithm>
#include<vector>
#include <set>
using namespace std;

double digtalGame1(vector<int> num,int m)//方法一:四层循环
{
	sort(num.begin(),num.end());//先排序
	int i1,i2,i3,length = num.size(),count = 0;
	for(i1 = 0 ;i1 < length;++i1)
	{
		for(i2 = 0;i2 < length;++i2)
		{
			for(i3 = 0;i3 <length;++i3)
			{
				int d = m - (num[i1]+num[i2]+num[i3]);
				int start = 0,end = length-1;
				while(start <= end)//二分查找对有重复数字的情况统计的结果会偏小
				{
					int mid = start + ((end - start) >> 1);
					if(num[mid] < d)start = mid +1;
					else if(num[mid] > d)end = mid -1;
					else 
					{
						count ++;
						cout << num[i1] << " " << num[i2] << " " << num[i3] << " " << d << endl;
						break;
					}
				}
			}
		}
	}
	return (1.0*count)/(length * length * length * length);
}
double digtalGame2(vector<int> num,int m)//方法二:先求两个数的和
{
	int i1,i2,length = num.size(),count = 0;
	multiset<int> sum;
	for(i1 = 0;i1 < length;++i1)//计算所有两个数的和
	{
		for(i2 = 0;i2 < length;++i2)
		{
			sum.insert(num[i1]+num[i2]);
		}
	}
	multiset<int>::iterator iter = sum.begin();
	for(;iter != sum.end();iter++)//遍历和组成的集合,查看另一半的和是否存在,也可以将所有的n^2个和排序,然后用两个指针,但两个指针可以交叉,即必须走到两头
	{
		int otherSum = m - *iter;
		count += sum.count(otherSum);
	}
	return (1.0*count)/(length*length*length*length);
}
int main()
{
	int n,m,i;
	while(cin >> n >> m)
	{
		vector<int> num(n);
		for(i = 0;i < n;i++)cin >> num[i];
		cout << digtalGame1(num,m) << endl;
		cout << digtalGame2(num,m) << endl;
	}
}