逝者如斯,不舍昼夜

尘世中一个迷途小书童,读书太少,想得太多
  博客园  :: 首页  :: 新随笔  :: 联系 :: 订阅 订阅  :: 管理

《算法竞赛入门经典》第三章精选

Posted on 2015-08-17 18:03  SteveWang  阅读(852)  评论(1编辑  收藏  举报

例题3-1 开灯问题

 

  题目:有n盏灯,编号为1~n。第1个人把所有灯打开,第2个人按下所有编号为2的倍数的开关(这些灯将被关掉),第3个人按下所有编号为3的倍数的开关(其中关掉的灯将被打开,开着的灯将被关闭),依此类推。一共k个人,问最后有那些灯开着?输入:n和k,输出开着的灯编号。k<=n<=1000。
  样例输入:7 3
  样例输出:1 5 6 7
  分析:用sign[1],sign[2],...,sign[n]表示编号为1,2,3,...,n的灯是否开着。模拟这些操作即可:
  源码

#include<stdio.h>
#include<string.h>
#define MAXN 1000+10
int sign[MAXN];

int main()
{
    int n,k,i,j;
    scanf("%d%d",&n,&k);            //n盏灯,k个人
    memset(sign,0,sizeof(sign));    //初始置0关闭所有灯
    for(i=1;i<=k;i++)                //第i个人按开关
        for(j=1;j<=n;j++)            //对于第j号灯
            if(j%i==0)    sign[j] = !sign[j];//按开关
    for(i=1;i<=n;i++)
    {
        if(sign[i]==1)                //如果灯开着
            printf("%d ",i);
    }
    printf("\n");

    return 0;
}

 

 

例题3-2 蛇形填数

 

  题目(2016腾讯实习生校招笔试题):在n*n方阵里填入1,2,...,n*n,要求填成蛇形。例如n=4时方阵为:

  

  上面的方阵中,多余的空格只是为了便于观察规律,不必严格输出。n<=8
  分析:类比数学中的矩阵,我们可以用一个二维数组来存储题目中的方阵。数组的初始值为0,从起点开始判断各个方向是否可行(由于从顺时针右上角出发,所以判定顺序为下->左->上->右),并予以赋值,最后输出整个二维数组即可。
  源码

#include<stdio.h>
#include<string.h>
#define MAXN 10
int matrix[MAXN][MAXN];

int main()
{
    int n,x,y,i=0;                         //x,y分别是数组行标和列标
    scanf("%d",&n);
    memset(matrix,0,sizeof(matrix));        //所有格子初始化为0    
    i = matrix[x=0][y=n-1] = 1;                //起始点(0,n-1)
    while (i<n*n)        //由于是顺时针蛇形填数,且起点在右上角,判定方向的优先原则为下->左->上->右
    {
        while(x+1<n && !matrix[x+1][y])  matrix[++x][y] = ++i;    //当下侧未越界且未走过(值为0),向下移动赋值
        while(y-1>=0 && !matrix[x][y-1]) matrix[x][--y] = ++i;    //当左侧未越界且未走过(值为0),向左移动赋值
        while(x-1>=0 && !matrix[x-1][y]) matrix[--x][y] = ++i;    //当上侧未越界且未走过(值为0),向上移动赋值
        while(y+1<n && !matrix[x][y+1])  matrix[x][++y] = ++i;    //当右侧未越界且未走过(值为0),向右移动赋值
    }
    for(x=0;x<n;x++)
    {
        for (y=0;y<n;y++)
            printf("%3d",matrix[x][y]);  //控制每个格子的输出格式为右对齐宽度至少占3位
        printf("\n");
    }

    return 0;
}

 

 

例题3-3 竖式问题

 

  题目:找出所有形如abc*de(三位数乘以两位数)的算式,使得在完整的竖式中,所有数字都属于一个特定的数字集合。输入这个数字集合(相邻数字之间没有空格),输出所有竖式,最后输出解的总数。具体格式如下:

  

  分析:本题的思路应该是很清晰的,尝试所有的abc和de,判断是否满足条件。

  源码

#include<stdio.h>
#include<string.h>

int main()
{
    int abc,de,sign,i,x,y,z,count=0;    //sign用来标志满足与否
    char s[20],buf[99];    //字符串s就是输入的数字集合
    scanf("%s",s);        
    for (abc=111;abc<=999;abc++)
        for(de=11;de<=99;de++)
        {
            x=abc*(de%10);    //x=abc*e
            y=abc*(de/10);    //y=abc*d
            z=abc*de;
            sprintf(buf,"%d%d%d%d%d",abc,de,x,y,z);    //把可能出现的所有数字事先(覆盖)输出到字符串buf
            sign=1;        
            for(i=0;i<strlen(buf);i++)
                if(strchr(s,buf[i])==NULL)    sign=0;    //如果buf中有未在s中出现的数字,标记不满足
            if(sign)
            {
                printf("<%d>\n",++count);
                printf("%5d\nX%4d\n-----\n%5d\n%4d\n-----\n%5d\n\n",abc,de,x,y,z);    //满足则按规定格式输出
            }
        }
    printf("The number of solution = %d\n",count);

    return 0;
}

 

 

例题3-4 最长回文子串

 

  题目(2016腾讯实习生校招笔试题):输入一个字符串,求出其中最长的回文子串(子串的含义是:在原串中连续出现的字符串片段。回文的含义是:正着看和倒着看相同,如abba和abcba),在判断时,应该忽略所有标点符号和空格,且忽略大小写,但输出应该保持原样(在回文串的首部和尾部不要输出多余字符)。输入字符串长度不超过5000,且占据单独一行。应该输出最长的回文串,如果有多个,输出起始位置最靠左的。

  样例输入:Confuciuss say:Madam,I'm Adam.

  样例输出:Madam,I'm Adam

  分析:首先,我们不能用scanf("%s")读入样例输入的字符串,因为它碰到空白字符就会停下来。这里我们使用fgets(buf,MAXN,stdin)来读取样例输入。接下来,我们要解决”判断时忽略标点,输出时却要按原样“。这里使用一个通用的方案:预处理。构造一个新的字符串,不包含原来的标点符号,而且所有字符变成大写(顺便解决了大小写问题),用ctype.h中的isalpha(c)判断c是否为大写字母或小写字母,用toupper(c)返回c的大写形式。最后,就只剩下唯一的问题”原样输出“了。我们必须增加一个数组用来保存预处理后的字符在原字符串中的位置。

  源码

#include<stdio.h>
#include<string.h>
#include<ctype.h>            // 用到isalpha、touuper等工具 
#define MAXN 5000 + 10
char buf[MAXN], tmp[MAXN];      // buf用来存放样例输入,tmp用来存放经过预处理的输入   
int p[MAXN];                  // p用来保存经过预处理后字符串tmp中的字符在原字符串buf中的位置

int main()
{
    int n, m = 0, max_len = 0, x, y;
    int i, j;
    fgets(buf, MAXN, stdin);    // 从标准输入流中读取最多MAXN个字符并且把他们转储到buf中,按回车停止读取
    n = strlen(buf);            // n为buf中的字符数

    for (i = 0; i < n; i++)        // 预处理操作
    {
        if (isalpha(buf[i]))    // 从buf中取出大小写字母
        {
            tmp[m] = toupper(buf[i]); // 全部转换成大写字母存入tmp中
            p[m++] = i;         // 保存tmp[m]在buf中的位置i
        }
    }

    for (i = 0; i < m; i++)     // 以i为中心向两边扩展  
    {

        for (j = 0; i - j >= 0 && i + j < m; j++)   // 长度为奇数的回文子串(形如abcba)
        {
            if (tmp[i - j] != tmp[i + j])    // 如果已不对称相等,跳出循环
                break;
            if (j * 2 + 1 > max_len)        // 因为回文子串的长度为奇数,所以子串的长度应该等于(i+j)-(i-j)+1=j*2+1
            {
                max_len = j * 2 + 1;        // 保存当前最长回文子串长度        
                x = p[i - j];                // 记录子串在buf中的范围x~y
                y = p[i + j];
            }
        }
        for (j = 0; i - j >= 0 && i + 1 + j < m; j++)// 长度为偶数的回文子串(形如abba),中心点(两个b)位置分别为i,i+1,向两边扩展范围(i-j,i+1+j)
        {
            if (tmp[i - j] != tmp[i + 1 + j])
                break;
            if (j * 2 + 2 > max_len)        // 回文子串长度为(i+1+j)-(i-j)+1=j*2+2
            {
                max_len = j * 2 + 2;
                x = p[i - j];
                y = p[i + 1 + j];
            }
        }
    }
    for (i = x; i <= y; i++)
        printf("%c", buf[i]);    // 在原字符串buf中定位最长回文子串并输出
    printf("\n");

    return 0;
}

 

  以上解答用的是中心扩展法,关于该题的其他做法,参考最长回文子串

 

 

习题3-3 乘积的末3位(product)

 

  题目:输入若干个整数(可以是正数、负数或者零),输出它们的乘积的末3位。这些整数中会混入一些由大写字母组成的字符串,你的程序应当忽略它们。

  样例输入:12HQ-3T4GS

  样例输出:-144

  样例输入:1G1024ABS656S4

  样例输出:976

  源码

#include<stdio.h>
#include<string.h>
#include<ctype.h>
#define MAXN 1000
char buf[MAXN];         //buf用来存放样例输入
int d[MAXN],p[MAXN];    //d用来存放经过预处理(忽略其他符号,只保留数字)的输入,p用来保存经过预处理后数字串d中的数字在原字符串buf中的位置

int main()
{
    int i,n,m=0,x=0,y=1,fu_num=0;//fu_num表示负数的个数(决定最后结果的正负)
    fgets(buf,MAXN,stdin);       //从标准输入流中读取MAXN个字符并且把他们转储到buf中,按回车停止读取
    n = strlen(buf);
    for(i=0;i<n;i++)
    {
        if(isdigit(buf[i]))      //从buf中取出字符'0'~'9'
        {
            d[m]=buf[i]-'0';     //全部转换成整型存入d中
            p[m++]=i;            //保存d[m]在buf中的位置i
        }
        if(buf[i]=='-')
            fu_num++;            //负号计数器
    }
    for(i=0;i<m;i++)
    {
        x+=d[i];
        if(p[i+1]-p[i]==1)       //字符串d中相连的数字可组成多位数
        {
            x = x*10;            //左移一位与下次循环的数字组成多位数
            continue;            //跳出本次循环
        }
        y = y * x % 1000;        //计算乘积
        x=0;                     //数字复位
    }
    if(fu_num%2==1&&y!=0)        //结果为负
        printf("-%3d\n",y);
    else
        printf("%3d\n",y);

    return 0;
}

 

 

习题3-4 计算器(calculator)

 

  题目:编写程序,读入一行恰好包含一个加号、减号或乘号的表达式,输出它的值。这个运算符保证是二元运算符,运算数和运算符可以紧挨着,也可以用一个或多个空格、TAB隔开。行首末尾均可以有空格。

  样例输入:1+1    

  样例输出:2

  样例输入:2-  5  

  样例输出:-3

  样例输入:0    *1982  

  样例输出:0

  源码

#include <stdio.h>
#include <string.h>
#define MAXN 100
char a[MAXN];
 
int main()
{
   int i, n, x = 0, y = 0, middle; 
   while(fgets(a, MAXN, stdin))    //按回车结束输入,同时回车符'\n'也会被读入到a
   {
      n = strlen(a);            //长度为键盘输入的字符串长度+1('\n'),strlen统计的长度不包括'\0'
      for(i = 0; i < n-1; i++)        //a[n-1]='\n'
      {
         if(a[i] == '+' || a[i] == '-' || a[i] == '*')
           middle = i; 
      }
      for(i = 0; i < middle; i++)    //前半部分找第一个运算数x
      {
         if(a[i] == ' ' || a[i] == '    ') continue;//跳过空格和制表符
         x = x*10 + a[i] - '0';
      }
      for(i = middle+1; i < n-1; i++ )//后半部分找第二个运算数y
      {
          if(a[i] == ' ' || a[i] == '    ') continue;
          y = y*10 + a[i] - '0';
      }
      
      switch(a[middle])
      {
      case '+':
           printf("%d\n", x+y);break;
      case '-':
           printf("%d\n", x-y);break;
      case '*':
           printf("%d\n", x*y);break;
      }
      x = 0;    //运算数复位
      y = 0;
   }
  return 0;
}

 

  注意:用fgets输入最后还有一个'\n'(按回车时被输入进去)和'\0',strlen函数不将'\0'算进去,但'\n'还是会算进去的。

 

  另外还有个种更简单的方法,它利用了scanf除了按%c格式读入,其他情况均忽略空白字符(空格、换行符和制表符),源码如下:

#include<stdio.h>   
int main() { int x,y,z; char f; scanf("%d",&x); //scanf仅在按%c格式读入时不忽略空白字符(空格、换行符和制表符) scanf("%c",&f); while((f!='+')&&(f!='-')&&(f!='*')) //若读入空白字符则继续读取 scanf("%c",&f); scanf("%d",&y); if (f=='+') z=x+y; else if(f=='-') z=x-y; else z=x*y; printf("%d\n",z); return 0; }

 

 

习题3-8 手机键盘(keyboard)

 

  题目:输入一个由小写字母组成的英文单词,输出用手机的默认英文输入法的敲键序列。例如要打出pig这个单词,需要按1次p,3次i,(稍作停顿后)1次g,记为p1i3g1。

  源码

#include<stdio.h>

int main()
{
    int d=0;
    char x;
    while(scanf("%c",&x)==1)//a~z对应的ASCII码为97~122
    {
        if (((d=x-96)<=3)&&(x!='\n')) {printf("%c%d",x,d);}    //先判定是不是abc,判定是要忽略以%c格式读取进来的换行符
        else if (((d=x-99)<=3)&&(x!='\n')) {printf("%c%d",x,d);}        //def
        else if (((d=x-102)<=3)&&(x!='\n')){printf("%c%d",x,d);}        //ghi
        else if (((d=x-105)<=3)&&(x!='\n')){printf("%c%d",x,d);}        //jkl
        else if (((d=x-108)<=3)&&(x!='\n')){printf("%c%d",x,d);}        //mno
        else if (((d=x-111)<=4)&&(x!='\n')){printf("%c%d",x,d);}        //pqrs
        else if (((d=x-115)<=3)&&(x!='\n')){printf("%c%d",x,d);}        //tuv
        else if (((d=x-118)<=4)&&(x!='\n')){printf("%c%d",x,d);}        //wxyz
    }
    return 0;

}