运用排除法来解析一道经典的智力题

首先题目描述如下:

张老师的生日是以下10天中一天:

3月4日

3月5日

3月8日

6月4日

6月7日

9月1日

9月5日

12月1日

12月2日

12月8日

张老师把月份告诉了小明,把日子告诉了小强,张老师问他们知道他的生日是那一天吗?
小明说:如果我不知道的话,小强肯定也不知道。
小强说:本来我也不知道,但是现在我知道了。
小明说:哦,那我也知道了。
请根据以上对话推断出张老师生日是哪一天?


这道题大家估计都曾经看到过,通过小明和小强的三句话确实可以推算出张老师的生日。这里我想编写一段代码来推算出结果,主要的算法思路是通过那三句话来翻译成三个排除条件,以此来排除掉9个不可能的日子,最终留下的那个就是张老师的生日。

下面是对那三句话的简单的分析:

“小明说:如果我不知道的话,小强肯定也不知道。”--->由于小明知道的是月份,小强是日子,所以这句话翻译成排除条件:在这10天中,日子是唯一的那几天以及唯一日子所落到月份里所有的天数都是应该排除掉的。--->翻译成更加直白的伪算法就是唯一的日子所在的月份的天数全部应该排除掉

小强说:本来我也不知道,但是现在我知道了。”--->在剩下的天数中,小强根据已知的日子,已经确定了生日,那说明重复的日子应该排除掉。--->在剩下的天数中,日子重复的日期应该排除掉

小明说:哦,那我也知道了。”--->小明也确定了生日,说明他所知道的月份在剩下的日期中肯定是唯一的--->在剩下的天数中,月份重复的应该排除掉

下面贴上代码(C++写的),数据结构主要采用了三个下标统一的数组来构造一个矩阵模型,具体见代码:

#include <iostream>

using namespace std;

int main(int argc, char * argv[])
{
    int month[] = {3,3,3,6,6,9,9,12,12,12};//月份
    int date[]  = {4,5,8,4,7,1,5,1, 2, 8};//日子
    bool flag[] = {true,true,true,true,true,true,true,true,true,true};//排除标记,最终只有一个为true
    int i,j;
    bool isHandled;
    
    /* 用第一个排除条件来排除掉部分日期:

       唯一的日子所在月份的日期应该设置成false
     */
    for (i = 0; i < 10 - 1; i++)
    {
      isHandled = false;
      //Seach in existing check elements, and copy the flag from the existing check element
      for(j = 0; j < i; j++)
      {
        if(date[j] == date[i])
         {
           flag[i] = flag[j];
           isHandled = true;
           break;
         }
      }

      if(!isHandled)
      {
        for(j = i + 1; j < 10; j++)
        {
          if(date[j] == date[i])
          break;
        }
        if(j == 10)
          flag[i] = false;
      }
    }
    for(i = 0; i < 10; i++)
    {
      if(!flag[i])
      {
        for(j = 0; j < 10; j++)
        {
          if(month[j] == month[i])
          flag[j] = false;
        }
      }
    }

    /* 继续用第二个排除条件来继续排除掉部分日期:

       再剩下的日期中日子重复的应该设置成false

    */
    for(i = 0; i < 10 - 1; i++)
    {
      if(!flag[i])
        continue;
    
      for(j = i + 1; j < 10; j++)
      {
        if(!flag[j])
          continue;
        if(date[j] == date[i])
        {
          flag[i] = false;
          flag[j] = false;
          break;
        }
      }
    }

    /* 用最后一个排除条件来排除掉部分日期:

       在剩下flag为true的日期中,月份重复的日期全部设置为false

    */
    for(i = 0; i < 10 - 1; i++)
    {
      if(!flag[i])
        continue;
    
      for(j= i + 1; j < 10; j++)
      {
        if(!flag[j])
          continue;
        if(month[j] == month[i])
        {
          flag[i] = false;
          flag[j] = false;
          break;
        }
      }
    }

    /* Print the only left date which is the brithday */
    for(i = 0; i < 10; i++)
    {
      if(flag[i])
        cout << "The birthday is " << month[i] << "-" << date[i] << endl;
    }

    return 0;
}


以上代码通过g++在linux下编译运行测试通过。在编码过程中,具体的筛选算法没有过多考虑时间复杂度,应该还有更高效的算法,比如打点法等。大家有兴趣可以去尝试下其它的筛选算法。

posted on 2011-11-22 21:40  Jordan_Fei  阅读(1900)  评论(4编辑  收藏  举报

导航