看,抽签问题还能这样写。。

本文通过一场游戏挑战,探讨了算法优化的过程,从最初的暴力破解方法(O(n^4)),逐步改进至更高效的O(n^2*logn)算法,通过折半查找和排序技巧,实现了对大量数据的有效处理。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

题目描述:

你的朋友提议和你玩一个游戏:将写有数字的 n 个纸片放入口袋中,你可以从口袋中抽取四次纸片。每次记下纸片上的数字后都将其放回口袋中,如果四个数字的和是 m ,就是你赢了,否则就是你的朋友赢。你挑战了好几次,结果一次也没有胜利,于是怒而撕破口袋,取出里面所有的纸片,检查自己是否真的有胜利的可能性。请你编写一个程序,判断当纸片上所写的数字是k1,k2,… kn,是否存在抽取4次和为m的方案。如果存在便输出 Yes ,否则,输出 No。(1<=n<=1000,1<=m<=1e8,1<=k<=1e8)

解题思路:

我相信很多人和我一样,看到这个题目的第一眼就是暴力破解;

#include<stdio.h>
int k[1000],n;
int judge(int m)
{
	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(k[a]+k[b]+k[c]+k[d]==m)return 1;
	}
	return 0;
}
int main()
{
	int m;
	scanf("%d %d",&n,&m);
	for(int i=0;i<n;i++)
	{
		scanf("%d",&k[i]);
	}
	if(judge(m))printf("Yes\n");
	else printf("No\n");
	return 0;
} 

但是呢,你是爽了,带来的后果就是巨大的时间复杂度。。。足足 O(n^4)
在这里插入图片描述
相对而言,n的值取的小还好,这样是大了,程序肯定就罢工了;
此时,我们便要寻找下新的方法了。
依题意我们可知,最后的判断条件是

k[a] + k[b] + k[c] + k[d] == m
通过对式子的移动,可以变成
k[d] == m - k[a] - k[b] - k[c]
也就是说,我只需要判断k数组中是否存在 m - k[a] - k[b] - k[c]
再用上折半查找法,于是乎,就有了这 O(n^3*logn)
#include<stdio.h>
#include<iostream>
int k[1000],n;
int bisearch(int num)                           //折半查找法
{
	int x=0,y=n;
	while(x<=y)
	{
		int i=(x+y)/2;
		if(k[i]==num)return 1;
		else if(num>k[i])x=i+1;
		else y=i;
	}
	return 0;
}
int judge(int m)                        //判断
{
	for(int a=0;a<n;a++)
	for(int b=0;b<n;b++)
	for(int c=0;c<n;c++)
	{
		if(bisearch(m-k[a]-k[b]-k[c]))return 1;
	}
	return 0;
}
int main()
{
	int m;
	scanf("%d %d",&n,&m);
	for(int i=0;i<n;i++)
	{
		scanf("%d",&k[i]);
	}
	if(judge(m))printf("Yes\n");
	else printf("No\n");
	return 0;
} 

这样子,我们的时间复杂度就会少很多了,毕竟折半查找法的复杂度才 O(logn)
在这里插入图片描述
看到这里,心里还是有点欣慰的,啊哈哈哈。。
但是将1000带进去, n 的三次方还是不能让人接受的,所以又需要去优化。。。刚刚我们针对的内循环,现在我们可以在外循环做做文章。。
还是一样的思路,将上方的式子再变:

k[c] + k[d] == m - k[a] - k[b]
但是这种情况下并不能直接的使用二分搜索。但是,我们只要先将 k[c] + k[d] 所得的 n^2 个元素保存在数组中并排好序,便可以利用二分查找法了。。
因为只用了两重循环的缘故,此算法时间复杂度可达到 O(n^2*logn)
#include<stdio.h>
#include<iostream>
#include<algorithm>
using namespace std;
int k[10000],kk[10000],n;
int bisearch(int num)
{
	int x=0,y=n;
	while(x+1<=y)
	{
		int i=(x+y)/2;
		if(kk[i]==num)return 1;
		else if(num>kk[i])x=i+1;
		else y=i;
	}
	return 0;
}
int judge(int m)
{
	for(int a=0;a<n;a++)
	for(int b=0;b<n;b++)
	{
		if(bisearch(m-k[a]-k[b]))return 1;
	}
	return 0;
}
int main()
{
	int m;
	scanf("%d %d",&n,&m);
	for(int i=0;i<n;i++)
		scanf("%d",&k[i]);
	int s=0;
	for(int i=0;i<n;i++)
		for(int j=0;j<n;j++)
			kk[i*n+j]=k[i]+k[j];
	sort(kk,kk+n*n);
	if(judge(m))printf("Yes\n");
	else printf("No\n");
	return 0;
} 

这样,总的时间便可达到 O(n^2*logn) ,就可以放心去跑了。。。
即使n达到1000也能够妥善的处理了。

这也是一次和朋友的交流中学习到的,也让我明白了一个道理,路还是一步一步来的,从刚刚开始一个,到最后的优化,我和我同学都受益匪浅,一步一步来和直接看最好的代码,完全是两种感觉。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

木木不会

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值