关于不可能足球分数问题的分析

一个面试题,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;
}

结果是正确的。

========================================

好吧,这个就这样了,悲催的我~~~~~看来面试前,活动活动脑子,还是有好处的。至少不会在面试过程中秀逗。希望大家不会像我一样。



评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值