北理工.程序设计方法实践.效率分析
百丽宫小菜鸡的算法学习记录,谢谢各位提出建议。
效率分析题目分析
5、诸葛兔猜数
注释:这道题的x表示要猜出的数,n表示上线即右边界(最开始可取)
最少调用意味着每一次循环尽量少调用guess函数
void guessnumber(int n)
{
int left = 1;
int right = n;
int count = 25;
while(count){
int mid;
mid = (right+left)/2;
if(guess(mid)==1)left = mid;//第一次调用
else right = mid;
if((right-left)<=1){
if(guess(left))printf("%d\n",left);//第二次调用
else printf("%d\n",right);
break;
}
count--;
}
}
该次提交有多个样例没过,guess函数每次调用两次,能否减少每次的调用?
void guessnumber(int n)
{
int left = 1;
int right = n+1;
int count = 25;
while(count){
int mid;
mid = (right+left)/2;
if(guess(mid)==1)left = mid;//仅调用一次
else right = mid;
if((right-left)<=1){
printf("%d\n",left);
break;
}
count--;
}
}
这里有一个细节,为什么当左右边界距离小于1,就可以认为x为left?
因为初始化 right = n + 1;此时右边界与此后每一次都是不可取的了,所以只需要考虑左边界的取值。
6.萝卜三元组
这里的卡的函数调用次数很值得玩味。即
- 每次调用不超过3次,且进行n次函数循环。
- 进行三次n循环
这是对3n的理解。
然后结合这道题,我们知道实际上我们求的最小值,就是找到三个距离最近的点,于是只需要将每次最小的点向后移动再判断就行。
int findMinDiffWeight(int n)
{
long long ans = 1000000000;
int i = 0,j = 0,k = 0;
while(i!=n&&j!=n&&k!=n){
int temp = DiffWeight(i,j,k);
if(temp < ans){
ans = temp;
}
int cc = xls_min(i,j,k);
if(cc==1)i++;
if(cc==2)j++;
if(cc==3)k++;
}
return ans;
}
实际上是进行了3次n循环;符合第二种思路。
7.白兔让萝卜
这道题有2n个数据。如果进行遍历,则调用了4n次函数。
void findminandmax(int n)
{
int max = 1;
int min = 1;
for(int i = 2; i <= 2*n;i++){
if(cmp(max,i)==-1)max = i;
if(cmp(min,i)==1)min = i;
}
printf("%d %d\n",min,max);
}
一共调用了4n次,显然无法通过这道题。
于是我在思路不变的情况下进行了一个小优化。
void findminandmax(int n)
{
int max = 1;
int min = 1;
for(int i = 2; i <= 2*n;i++){
if(cmp(max,i)==-1)max = i;
else{//这里的改变使得调用次数减少
if(cmp(min,i)==1)min = i;
}
}
printf("%d %d\n",min,max);
}
这样不需要每一次都进行最小值的比较,**只需要当最大值不变的情况下比较一次就行。**这样的话,调用的次数显然在2n到4n之间,能不能小于3n次还得看后台数据是否赏脸。但是出题的学长显然考虑到了这一点。所以还是有几个测试点没过。
回到题目本身,3n还有一种拆分方法,就是思路一。n次循环,每次循环调用三次。对于2n的个数,显然进行分组即可。
void findminandmax(int n)
{
int max = 1;
int min = 1;
for(int i = 1; i <= 2*n;i+=2){//分组
int c = cmp(i,i+1);
if(c ==-1){
if(cmp(i,min)==-1)min = i;
if(cmp(i+1,max)==1)max = i+1;
}
if(c ==1){
if(cmp(i+1,min)==-1)min = i+1;
if(cmp(i,max)==1)max = i;
}
}
printf("%d %d\n",min,max);
}
分组,两两比较,然后大的和max比较,小的和min比较。
8.找大佬
小菜鸡提交了很多次都没过。后来发现这道题有5个测试点的输出都是-1,即没有找到大佬的情况。为什么发现呢,因为输出-1的时候输出了很多次-1,改了这个就过了
void guessdalao(int n)//这一段代码没考虑其他人没他强的那一个循环
{
int winer[1005];
int ans = 1;
for(int i = 2;i <= n;){
int c = better(ans,i);
if(c == 1){
i++;
}
if(c==0){
ans = i;
i++;
}
if(c == -1)return;
}
int beat = 0;
for(int i= 1;i <=n;i++){
if(i == ans)continue;
int temp = better(ans,i);
if(temp==1)beat++;
if(temp == 0)printf("-1\n");//这里可能出现重复输出-1的情况
}
if(beat==n-1)printf("%d\n",ans);
}
void guessdalao(int n)
{
int ans = 1;
for(int i = 2;i <= n;){//找到可能为大佬的人
int c = better(ans,i);
if(c == 1){
i++;
}
if(c==0){
ans = i;
i++;
}
if(c == -1)return;
}
int beat = 0;//比其他人都强
for(int i = 1;i <=n;i++){
if(i == ans)continue;
int temp = better(ans,i);
if(temp == 1)beat++;
if(temp == 0){
printf("-1\n");
return;
}
if(temp == -1)return;
}
int hh = 0;//其他人都没他强
for(int i = 1;i <= n;i++){
if(i == ans)continue;
int temp = better(i,ans);
if(temp == 0)hh++;
if(temp == 1){
printf("-1\n");
return;
}
if(temp == -1)return;
}
if(beat == n-1 && hh == n-1)printf("%d\n",ans);
}
找到可能为大佬的思路是,先假设第一个是大佬,和后面的依次比较,如果为0,说明没人家强,那么这个人一定不是大佬,所以将大佬的位置让给比他强的那个人,再在这个新大佬的位置继续向后比较,直到遍历完这n个人。
效率分析总结
该组程序题具有明显的特征即减小时间复杂度(虽然感觉没减少多少),但是分组意识尤其重要。对题目所限制的次数进行理解和总结思路。即对3n的划分:
- 每次调用不超过3次,且进行n次函数循环。
- 进行三次n循环