一个面试题,20只球队,双循环,“所有球队不可能出现的分数”。引号部分的理解存疑。
该题非常不难的,但是当时自己脑子进水了,于是没答出来。尽管面试官给了提示,还是没答出来。
正好今天下午有空,就又想了下,下面是我刚刚想的思路。时间不长。就当是自己给自己的一个教训。做什么事,都要保持清醒的头脑,时刻要准备好。
一、一般思路。
按题意,每只球队要打19*2场比赛,胜 :平:负 = 3:1:0 的分数,也就是每场比赛三种可能结果。
所以,每只球队的胜负平的场数总和是38,最高分是3*38+114,最低分是0,也就是共有115种分数。
简单的,两个 for 循环就可以求到所有实际比赛中可能的分数。
#include <stdlib.h>
#include <stdio.h>
#include <limits.h>
int main (int argc, char**argv)
{
int win, draw, lose, total[115];
for(win=0; win<=38; win++)
{
for(draw=0; draw<=38-win; draw++)
{
total[3*win+draw]=1;
printf("win is %d; draw is %d; lose is %d; total score is %d. \n", win, draw, 38-win-draw, 3*win+draw);
}
}
int i;
for(i =0; i<3*38; i++)
{
if (total[i] !=1)
{
printf("%d :: this score can't happen!\n", i);
}
}
return 0;
}
分析:
1. 用 int total[115] 保存分数的出现情况,角标是分数。设为 1 表示分数的出现,不变表示没出现。
当然,我没有进行初始化,在gcc (GCC) 4.1.2(Red Hat 4.1.2-46)上编译后的结果是没问题的;如果有不同意的,可以自行初始化。
那么,在该程序里面,空间复杂度就是这个数组。
2. 里面使用三个for循环,前两个for循环遍历了所有可能出现的比赛结果,时间复杂度为:O(1+2+3+....n)=O(n(n+1)/2)~~O(n^2)
第三个循环查找不可能出现的分数,时间复杂度为O(n).
简单的计算运算次数,就是op = 38*(38+1)/2+38 = 779(只为宏观比较,准不准的先不说)。
效率肯定不高。
3. 从运行的结果上,我发现了个规律:当win的场次固定后,随后出现的分数都是连续的。
比如:
win = 0, score total:0,1,2,3,4,5 ~~ 38
win = 1, score total:3,4,5,6,7,8 ~~ 40
win = 2, score total:6,7,8,9,10,11 ~~ 42
0-38是连续的,3-40是连续,6-42同样是连续的。显然在上面的程序中,多做了很多无用的计算。
既然有这个规律,那就该好好利用一下。
二、减少运算次数
2.1 简单的
很显然,最高的分数,会随着获胜场次的增多而增加,那么什么情况的时候,才会有不可能出现的分数呢?
如果继续上面的推理,就可以得出在某获胜场次的情况下,总分数的可能情况:
那这样的话,岂不是可以直接就算出哪个分数不可能出现?
但是,总要把规律描述出来。
由于平一场的分数为“一分”,胜一场的分数为“三分”,
因此,如果胜的场次每增加一场,那么最后可能出现的最低分数和最高分会增加“两分”,可能出现的总分数的个数会减少一次。
--------------------------------------------------------------------------------
接着上面的分析
--------------------------------------------------------------------------------
但所得出的可能分数依然是连续的。设n为胜利场数,min_score(n)为胜场数为n时的可能的最小分数,max_score(n)为可能的最大分数。
由于比赛平的分数为一分,那么平局场数能够使得最终的可能分数连续。
如果要找到不可能出现的分数,则只要找到出现前后(胜场为n和n+1)不连续的位置。
也就是找到n满足:max_score(n) < min_score(n+1) (一)
max_score(n) = 3*n + (38-n) n <= 38
min_score(n) = 3*n n <= 38
当比赛场次为38时求不等式:3*n+(38-n) < 3*(n+1) 得:n > 35 ;所以:35 < n < 38;所以 n = 36、37
当 n = 36时,max_score(n) = 110 min_score(n+1) = 111 两个值连续;
当 n = 37时,max_score(n) = 112 min_score(n+1) = 114 两个值不连续;
所以,可以得出,113是不可能出现的分数。
#include <stdlib.h>
#include <stdio.h>
#include <limits.h>
#define MAX_ROUND 38 // 比赛场次最大值
int max_score(int n)
{
if(n<=38)
return 3*n+(38-n);
else
return 0;
}
int min_score(int n)
{
if(n<=38)
return 3*n;
else
return 0;
}
int main (int argc, char**argv)
{
int win, draw, lose, total[115];
int count = 0;
for(win=0; win<MAX_ROUND; win++)
{
int n, m;
n = max_score(win);
m = min_score(win+1);
if(n < m && m-n > MAX_ROUND - win)
{
printf("the score %d can't happen!\n", 1+max_score(win));
}
count++;
}
printf("count is %d\n",count);
return 0;
}
运行结果:
从前后两个方法的运行结果来看,前一个方法,主循环内执行的次数 count=780;后者只有count=38。改善了不少。
当总比赛场数大于38的时候呢?
当n=100时,看前后两个的运行结果:
由上面的结果可以看到,第二个方法是可行的。
2.2 扩展:不再是3.1.0
如果胜平负为3.2.0的情况下呢?或者别的什么分数下呢?
三、中间的思路
其实,在方法一和方法二中间,还有一种方法。也就是count数在两者之间。那就是,在面试的时候,面试官提示的。
思路是:从0--114,检查每种分数score是否能用3、1来表示。即:3*win+draw=score; 并且 win+draw <=38;
这种思路的关键点在于如何分解该分数。
最简单的一种就是判断score是否满足:win = score/3, win <= 38;
#include <stdlib.h>
#include <stdio.h>
#include <limits.h>
#define MAX_ROUND 38 // 比赛场次最大值
int main (int argc, char**argv)
{
int win, draw, lose, score;
int count = 0;
for(score=0; score<=3*MAX_ROUND; score++)
{
win = score/3;
if(win > 38 || 3*win+38-win < score)
{
printf("the score %d can't happen!\n", score);
}
count++;
}
printf("count is %d\n",count);
return 0;
}
结果是正确的。
========================================
好吧,这个就这样了,悲催的我~~~~~看来面试前,活动活动脑子,还是有好处的。至少不会在面试过程中秀逗。希望大家不会像我一样。