第3章 数组和字符串
学习要求和笔记:
√ 掌握一维数组的声明和使用方法
√ 掌握二维数组的声明和使用方法
√ 掌握字符串的声明、赋值、比较和连接方法
√ 熟悉字符的ASCII码和ctype.h中的字符函数
√ 正确认识“++”、“+=”等能修改变量的运算符
√ 掌握fgetc 和 getchar的使用方法
√ 了解不同操作系统中换行符的表示方法
√ 掌握fgets的使用方法并了解gets的“缓冲去溢出”漏洞
√ 学会用常量表来简化代码
例题学习记录和源代码:
√ Example3-1 逆序输出
√ Example3-2 开灯问题
√ Example3-3 蛇形填数
√ Example3-4 竖式问题
√ Example3-5 Tex中的引号(UVa 272)
√ Example3-6 WERTYU (UVa10082)
√ Example3-7 回文词 Palindromes(UVa401)
√ Example3-8 猜数字游戏的提示 (UVa 340)
√ Example3-9 生成元Digit Generator (UVa1583)
√ Example3-10 环形序列Circular Sequence (UVa1584)
// Example_0301_逆序输出_数组.cpp
/**
* 题目名称:逆序输出
* 题目描述:将数组内的数逆序输出。
* 学习笔记:
* ①把int a[maxn]放在外面是有原因的,而且最好把数组定义的稍微大些,全局变量在静态存储区分配内存;局部变量在栈上分配内存空间
* ②把数据x存储在向数组中a[n++] = x
* ③这里需要注意一个细节,就是输出格式行首和行尾不需要加空格,所以想到最后一个a[0]单独输出
* ④
**/
/*
//.c
#include <stdio.h>
#define maxn 100 + 5
int a[maxn];
int main()
{
int i,x,n = 0;
while(scanf("%d", &x) == 1)
a[n ++] = x;
for(i = n - 1; i >= 1; i--)
printf("%d ", a[i]);
printf("%d\n", a[0]);
return 0;
}
*/
//.cpp
#include <iostream>
using namespace std;
#define maxn 100 + 5
int a[maxn];
int main()
{
int i,x,n = 0;
while(cin >> x)
a[n ++] = x;
for(i = n - 1; i >= 1; i--)
cout << a[i] << " ";
cout << a[0] << endl;
return 0;
}</span>
// Example_0302_开灯问题.cpp
/**
* 题目名称:开灯问题
* 题目描述:
* 有n盏灯,编号为1~n。第1个人把所有灯打开,第2个人按下所有编号为2的总数的开关(这些灯将补关掉),
* 第3个人按下所有编号为3的倍数的开关(其中关掉的灯将被打开,开着的灯将被关掉),依此类推。
* 一共有k个人,问最后有哪此灯开着?输入n和k,输出开着的灯的编号。 k <= n <= 1000.
* 样例输入: 7 3
* 样例输出: 1 5 6 7
**/
/**
* 题目分析:
* 这题并不难,只需要使用两个循环即可实现。
* 首先,我们可以先去理解一下开关灯实际上是什么,可以知道,开关灯,实际上是一种状态。
* 那么,我们可以使用一个数值来代表这种状态,下面的程序,用数值1表示开灯,数值0表示关灯。
* 由于这里有1 ~ n 盏灯,我们可以用一个数组来存放这n盏灯的信息。
* 解决开关灯状态问题后,可以开始算法思路:
* 题目中,要求第n个人就会将编号为n的倍数的灯的状态值改变,那么,我们可以在一个循环中负责实现“人”的改变。
* 然后,再在这个“人”的改变的循环中再嵌入一个处理”灯“的状态改变的循环函数。
* 学习笔记:
* ①告诉我们一个思想,A随B去变化,我们就必须先循环负责B的改变,之后再循环中再去找A变化的函数。
* ②这里教我们如何处理输出格式空格的问题,倒序那道题考虑的是,尾数据没有空格,所以把最后一个数据分开输出;
* 这里的方法是假设一个first,这样可以判定如果是第一个数据,则不需要空格,其他都需要空格。
* ③memset函数用来对一段内存空间全部设置为某个字符,一般用在定义的字符串进行初始化化'/0'
* 原型:char a[100]; memset(a, 0, sizeof(a));
**/
//.c
#include <stdio.h>
#include <string.h>
#define maxn 1010
int a[maxn];
int main()
{
int i,j,n,k,first = 1;
memset(a, 0, sizeof(a));
scanf("%d%d",&n, &k);
for(i = 1; i <= k; i++)
{
for(j = 1; j <= n; j++)
{
if(j % i == 0)
a[j] = !a[j];
}
}
for(i = 1; i <= n; i++)
{
if(a[i])
{
if(first) //first作用设置当前输入变量为第一个,很巧妙
first = 0;
else
printf(" ");
printf("%d", i);
}
}
printf("\n");
return 0;
}
//.cpp
#include <iostream>
#include <string.h>
using namespace std;
#define maxn 1010
int a[maxn];
int main()
{
int i,j,n,k,first = 1;
memset(a, 0, sizeof(a));
cin >> n >> k;
for(i = 1; i <= k; i++)
{
for(j = 1; j <= n; j++)
{
if(j % i == 0)
a[j] = !a[j];
}
}
for(i = 1; i <= n; i++)
{
if(a[i])
{
if(first) //first作用设置当前输入变量为第一个,很巧妙
first = 0;
else
cout << " ";
cout << i;
}
}
cout << endl;
return 0;
}
// Example3-3 蛇形填数.cpp
/**
* 题目名称:蛇形填数
* 题目描述:在n*n方阵里填入1, 2, ..., n*n, (n <=8)要求填成蛇形(多余的空格只是为了便于观察规律,不必严格输出)。
* 样例输入: 4
* 样例输出:
* 10 11 12 1
* 9 16 13 2
* 8 15 14 3
* 7 6 5 4
**/
/**
* 题目分析:
* 这里主要用到的是二维数组的知识,还有有点类似“迷宫”的一个判断方法。
* 首先原题说需要n*n (n <= 8)的方阵,那么,我们只需要定义一个a[n][n]的数组即可。
* 接下来,观察输出矩阵的蛇形规律,将数组中的每一元素初始化为0,将数字“1“从右上角开始填写,
* 然后,一直沿着 下 -> 左 -> 上 -> 右 的方向填充数字,直到全部0都填完为止。
* 那么,我们需要解决的问题,就只有如何能让它按这个方向走呢?我们可以将本来的0作为是否需要填充标记,
* 再将数组的边界作为另一个是否越界的标记。总之,就是根据这些标记,沿着指定方向,填充到不能填充为止。
* 最后,要解决的问题就是,如何知道它已经全部填充完了,然后跳出这个循环呢?我们可以用它本来需要填充的
* 数字个数作为标记,当它的数值小于方阵的总格子个数时,即符合要求。当然,需要注意的一个问题就是,当进行
* 确定它移动方向的判断时,需要先从数组边界作为越界条件去判断,然后再从数值上判断,这是为了防止内存溢出
* 的情况出现。
* 学习笔记:
* ①填数问题,学会用数组来做,而且数组的值可以作为标记,还有数组的边界也可以作为标记,先把需要的值填入数组中
* 之后再将这些数值按照一些输出格式进行输出。
* ②填数问题,我们要注意先判断再填数,否则悔棋会很麻烦
* ③关于输出格式问题,按一定长度输出,对于C而言 printf("%3d"....) 这里就是右对齐长度为3 printf("%-3d" 为左对齐
* c++而言,用setw(n)来控制,默认为右对齐,如果需要左对齐,加一个left,如cout << left << setw(n)
**/
/*
//.c
#include<stdio.h>
#include<string.h>
#define maxn 20
int a[maxn][maxn];
int main()
{
int x,y,n,step;
scanf("%d", &n);
//对字符串初始化
memset(a, 0, sizeof(a));
step = a[x = 0][y = n-1] = 1;
while(step < n*n)
{
//先向下
while(x + 1 < n && !a[x + 1][y])
a[++x][y] = ++step;
//再向左
while(y - 1 >= 0 && !a[x][y - 1])
a[x][--y] = ++step;
//之后向上
while(x - 1 >= 0 && !a[x - 1][y])
a[--x][y] = ++step;
//之后向右
while(y + 1 < n && !a[x][y + 1])
a[x][++y] = ++step;
}
//输出
for(x = 0; x < n; x++)
{
for(y = 0; y < n; y++)
printf("%3d", a[x][y]);
printf("\n");
}
return 0;
}
*/
//.cpp
#include<iostream>
#include<string.h>
#include<iomanip>
using namespace std;
#define maxn 20
int a[maxn][maxn];
int main()
{
int x,y,n,step;
cin >> n;
//对字符串初始化
memset(a, 0, sizeof(a));
step = a[x = 0][y = n-1] = 1;
while(step < n*n)
{
//先向下
while(x + 1 < n && !a[x + 1][y])
a[++x][y] = ++step;
//再向左
while(y - 1 >= 0 && !a[x][y - 1])
a[x][--y] = ++step;
//之后向上
while(x - 1 >= 0 && !a[x - 1][y])
a[--x][y] = ++step;
//之后向右
while(y + 1 < n && !a[x][y + 1])
a[x][++y] = ++step;
}
//输出
for(x = 0; x < n; x++)
{
for(y = 0; y < n; y++)
cout << left << setw(3) << a[x][y];
cout << endl;
}
return 0;
}
// Example_0304_竖式问题.cpp
/**
* 题目名称:竖式问题
* 题目描述:
* 找出所有形如abc*de(三位数乘以两位数)的算式,使得在完整的竖式中,所有数字都属于一个特定的数字集合。
* 输入数字集合(相邻数字之间没有空格),输出所有竖式。每个竖式前应有编号,之后应有一行空行。最后输出解的总数。
* (为了便于观察,竖式中的空格改用小数点显示,但你的程序应该输出空格,而非小数点)。
* 样例输入: 2357
* 样例输出:
* <1>
* ..775
* X..33
* -----
* .2325
* 2325.
* -----
* 25575
*
* The number of solutions = 1
**/
/**
* 题目分析:
* 尝试所有的abc和de,判断是否满足条件 if (" abc *de")是个合法的竖式)打印abc*de的竖式和其后的空行。
* 其实主要还是要找出主要需要解决的条件,在这题里面,需要解决的问题主要有两个。
* 第一个:输出格式控制,这个主要对cout或者prinf的使用方法熟悉即可,
* 第二个:“所有数字都属于一个特定的数字集合”,这里需要掌握筛选数字的技巧,下例中使用了“缓冲区”的方法。
* 首先,申请两个字符数组,第一个字符数组用于存放用户输入的字符,第二个字符数组用于存放进行乘法时出现过的数字的保存。
* 开始执行的过程,
* 第一步,将用户输入数字集合放到s字符组中。
* 第二步,计算,然后将计算时所有出现过的数字存放到另一个字符数组buf中。
* 第三步,开始逐个比较,判断是否buf中每个字符都能从s字符组中找到,起到了最终的筛选作用。
* 学习笔记:
* ①#include <stdio.h> int sprintf( char *buffer, const char *format, ... );
* sprintf()函数和printf()类似, 只是把输出发送到buffer(缓冲区)中.返回值是写入的字符数量.
* ②#include <string.h> char *strchr( const char *str, int ch );
* 功能:函数返回一个指向str 中ch 首次出现的位置,当没有在str 中找ch到返回NULL。
* 在C++里面可以用#include <cstring> size_type find( char ch, size_type index );
**/
//.c
#include <stdio.h>
#include <string.h>
int main()
{
int cnt = 0;
char s[20], buf[99];
scanf("%s", s);
for(int abc = 111; abc <= 999; abc ++)
{
for(int de = 11; de <= 99; de ++)
{
//首先计算第一行乘积abc*e,之后第二行abc*d,最后是总乘积abc*de
int x = abc * (de % 10), y = abc * (de / 10), z = abc * de;
//把abc,de,x,y,z全部保存在buf字符串中
sprintf(buf, "%d%d%d%d%d", abc, de, x, y, z);
int ok = 1;
//在输入字符串中分别查找刚刚保存在buf字符串的每一个字符
for(int i = 0; i < strlen(buf); i++)
if(strchr(s, buf[i]) == NULL)
ok = 0;
if(ok)
{
printf("<%d>\n", ++ cnt);
printf("%5d\nX%4d\n-----\n%5d\n%4d\n-----\n%5d\n\n",abc, de, x, y, z);
}
}
}
printf("The number of solutions = %d\n", cnt);
return 0;
}
//.cpp
#include <iostream>
#include <string>
#include <iomanip>
using namespace std;
int main()
{
int cnt = 0;
char s[20],buf[90];
cin >> s;
for(int abc = 111; abc < 999; abc++)
{
for(int de = 11; de < 99; de++)
{
int x = abc*(de%10);
int y = abc*(de/10);
int z = abc*de;
sprintf(buf, "%d%d%d%d%d", abc, de, x, y, z);
int ok = 1;
for(int i = 0;i < strlen(buf); i++)
if(strchr(s, buf[i]) == NULL)
ok = 0;
if(ok)
{
cout << "<" << ++cnt << ">" << endl;
cout << setw(5) << abc << endl << "X" << setw(4) << de << endl << "-----" << endl << setw(5) << x << endl << setw(4) << y << endl << "-----" << endl << setw(5) << z << endl <<endl;
}
}
}
cout << "The number of solutions = " << cnt << endl;
return 0;
}
// Example3-5TeX中的引号.cpp
/**
* 题目名称:TeX中的引号
* 题目描述:
* 在TeX中,左引号是“"”,右引号是”"“。输入一篇包含双引号的文章,你的任务是把它转成TeX格式。
* 样例输入: "To be or not to be," quoth the Bard, "that is the question".
* 样例输出: <To be or not to be,> quoth the Bard, <that is the question>.
**/
/**
* 题目分析:
* 本题目关键在于如何判断一个双引号是左引号还是右引号,方法很简单,使用一个标志变量。
* 但是本题需要解决另外一个问题,那就是输入字符串,我们首先考虑使用scanf("%s"),这里不能使用,因为它碰到空格或者TAB就会停下来。
* 所以从标准输入读取一个字符,可以用getchar,它等价于fgetc(stdin),将读取下一个字符。
* 本题的特点在于可以边读边处理,而不需要把输入字符串完整地存下来,因为getchar是一个不错的选择。
* 学习笔记:
* ①scanf("%s")碰到空格或者TAB就会停下来。
* ②从标准输入读取一个字符,可以用getchar,它等价于fgetc(stdin),将读取下一个字符。
* ③表达式“a?b:c”含义,这其实是if语句的表达式版
**/
//.c
#include <stdio.h>
int main()
{
int c,q = 1;
while((c = getchar()) != EOF)
{
if(c == "")
{
printf("%s", q ? "<" : ">");
q = !q;
}
else printf("%c", c);
}
return 0;
}
// Example3-6 WERTYU.cpp
/**
* 题目名称:WERTYU
* 题目描述:
* 把手放在键盘上时,稍不注意就会往右错一位,这样,输入Q会变成输入W,输入J会变成输入K等。输入一个错位后敲出的字符串(所有的字母均大写),
* 输出打字员本来想打出的句子,输入保证合法,即一定是错位之后的字符串。例如输入中不会出现大写字母A
* 样例输入: O S, GOMR YPFSU/
* 样例输出: I AM FINE TODAY.
**/
/**
* 题目分析:
* 本题目和上面例题一样,每输入一个字符,都可以直接输出一个字符,因此getchar是输入的理想方法,问题在于如何进行输入输出变换呢?
* 方法1:使用if语句或者switch语句,如"if(c = 'W')putchar('q')"。这样太麻烦了
* 方法2:使用常量数组
* 学习笔记:
* 善用常量数组往往能简化代码,定义常量数组时无须指明大小,编译器会计算。
* 运用常量数组时,我们常常需要查找输入字符在常量表中的位置,for(i = 1; s[i] && s[i] != c; i++)这里我们需要理解为什么是s[i] != c呢?
* 因为我们开始是从i = 1开始的
*
**/
//.c
#include <stdio.h>
char s[] = "`1234567890-=QWERTYUIOP[]\\ASDFGHJKL;'ZXCVBNM,./";
int main()
{
int i,c;
while((c = getchar()) != EOF)
{
for(i =1; s[i] && s[i] != c; i++); //找错位之后的字符在常量表中的位置
if(s[i]) putchar(s[i - 1]);//这里注意i刚好已经指到了错位字符的位置了
else putchar(c);
}
return 0;
}
// Example3-7回文词
/**
* 题目名称:回文词
* 题目描述:
* 输入一个字符串,判断它是否为回文串以及镜像串。输入字符串保证不含数字0。所谓回文串就是反转以后和原串相同,如abba和madam.所有的镜像串,就是左右镜像之后和
* 原串相同,如2S和3AIAE。注意,并不是每个字符在镜像之后都能得到一个合法字符
* 输入的每行包含一个字符串保证只有上述字符,不含空白字符,判断它是否为回文串和镜像串(共4种组合)。每组数据之后输出一个空行。
* 样例输入: NOTAPALINDROME
* ISAPALINILAPASI
* 2A3MEAS
* ATOYOTA
* 样例输出: NOTAPALINDROME -- is not a palindrome.
* ISAPALINILAPASI -- is a regular palindrome.
* 2A3MEAS -- is a mirrored string.
* ATOYOTA -- is a mirrored palindrome.
*
**/
/**
* 题目分析:
* 本题不包含空白字符,可以安全使用scanf进行输入,回文串和镜像判断都不复杂,我们可以放一起来判断
* 我们这道题仍然采用常量数组来完成,关键之处如何处理输入一个字符,找到镜像?这个也是本题的难点。
* 因为我们字母和数字的镜像不一样,而且没有一定的规则,我们这里就想着把所有镜像按顺序放在一个常量数组中
* 我们观察到一个顺序,前面26个是大写字母表,后面是1-9按大小顺序排列
* 这里还有一难点,就是我们怎么去将回文串和镜像串放在一起判断,这里我们采用了二维字符串数组,这里很巧妙
* 把is not a palindrome. is a regular palindrome. is a mirrored string. is a mirrored palindrome.放在一个字符串数组中
* 再利用两个变量p m来控制输出。很巧妙 很棒
* 学习笔记:
* ①善用常量数组往往能简化代码,定义常量数组时无须指明大小,编译器会计算,记住定义常量数组之后往往需要找一个标记,找出输入一个字符,对应
* 常量数组哪个字符,这里我们需要下一定的功夫,例如前面一道题目利用for(i = 1; s[i] && s[i] != c; i++)找出输入字符的位置;这道题采用与初始
* 之间的关系来找出对应字符的位置。
* ②学会用二维字符串数组解决一些问题
* ③头文件ctype.h中定义的isalpha、isdigit、isprint等工具可以用来判断字符的属性,而toupper、tolower等工具可以用来转换大小写,如果ch是大写字母,
* 则ch-'A'就是它在字母表中的序号;类似,如果ch是数字,则ch-'0'就是这个数字的数值本身
*
**/
//.c
#include <stdio.h>
#include <string.h>
#include <ctype.h>
const char *rev = "A 3 HIL JM O 2TUVWXY51SE Z 8 ";
const char *msg[] = {"not a palindrome", "a regular palindrome","a mirrored strin", "a mirrored palindrome"};
char Reverse(char ch)
{
if(isalpha(ch))
return rev[ch - 'A'];
return rev[ch - '0' + 25];
}
int main()
{
char s[30];
while(scanf("%s", s) == 1)
{
int len = strlen(s);
int p = 1,m = 1;
for(int i = 0; i < (len+1)/2; i++)
{
if(s[i] != s[len - 1 -i])
p = 0; //不是回文串
if(Reverse(s[i]) != s[len-i-i])
m = 0; //不是镜像串
}
printf("%s -- is %s.\n\n", s, msg[m*2 + p]);
}
return 0;
}
// Example3-8 猜数字游戏的提示
/**
* 题目名称:猜数字游戏的提示
* 题目描述:
* 实现一个经典“猜数字”游戏,给定答案序列和用户猜的序列,统计有多少数字位置正确(A),有多少数字在两个序列都出现过但位置不对(B)
*输入包含多组数据,每组输入第一行为序列的长度n,第二行为答案序列,接下啦是猜测序列,猜测序列全0时改组数据结束,n=0时输入结束
* 样例输入: 4
* 1 3 5 5
* 1 1 2 3
* 4 3 3 5
* 6 5 5 1
* 6 1 3 5
* 1 3 5 5
* 0 0 0 0
* 样例输出: Game 1:
* (1,1)
* (2,0)
* (1,2)
* (1,2)
* (4,0)
**/
/**
* 题目分析:
* 本题主要需要做的就是
* ①先统计A的个数,这个很简单,我们只需要建立两个数组,a[maxn] b[maxn]直接a[i] == b[i]就可以统计了;
* ②再统计B的个数,对于每个数字(1-9)统计二者出现的次数c1和c2,之后再求一下min(c1,c2),之后这个就是数字出现对B的贡献,最后在减去A的部分,就得到答案B了
* 学习笔记:
* ①当我们要将一组数据和很多数据一一对比时,我们可以选择设两个a[maxn] b[maxn]之后循环一一对比
* ②这道题我们每个数字是单个单个输入的,所以我们要有n个数,要采用循环i来scanf输入
* ②多组猜测数据,我们这里采用for( ; ;),当然里面设置了当为0,时候直接break出来,我们要学会用这种思想
**/
//.c
#include <stdio.h>
#define maxn 1010
int main()
{
int n, a[maxn], b[maxn];
int kase = 0;
while(scanf("%d", &n) == 1 && n)
{
printf("Game %d:\n", ++kase); //输出第一句话
for(int i=0; i < n; i++) //输出答案序列
{
scanf("%d", &a[i]);
}
for( ; ; ) //输出猜测序列
{
int A = 0, B = 0;
for(int i = 0; i < n; i++)
{
scanf("%d", &b[i]);
if(a[i] == b[i])
A++;
}
if(b[0] == 0) break; //正常的猜测序列不会又0,所以只需要判断第一个数是否为0即可
for(int d = 1; d <= 9; d++)
{
int c1 = 0, c2 = 0; //统计数字d在答案序列和猜测序列中各出现多少次
for(int i = 0; i < n; i++)
{
if(a[i] == d) c1++;
if(b[i] == d) c2++;
}
if(c1 < c2)
B += c1;
else
B += c2;
}
printf(" (%d,%d)\n", A, B-A);
}
}
return 0;
}
// Example3-10 环形序列(Circular Sequence)
/**
* 题目名称:环形序列
* 题目描述:
* 长度为n的环形串有n种表示法,分别为从某个位置开始顺时针得到,我们会有很多种表示,在这些表示法中,字典序最小的成为“最小表示”
* 输入一个长度为n(n小于等于100)的环状DNA串(只包含A、C、G、T这四种字符)的一种表示法,你的任务就是输出这环状串的最小表示
* 样例输入:CTCC的最小表示为CCCT CGAGTCAGCT 最小表示为AGCTCGAGTC
**/
/**
* 题目分析:
* 本题提出了一个新的概念:字典序,所谓的字典序就是字符串在字典中的顺序,对于两个字符串,从第一个字符开始,当某个位置的字符不同时,该位置
* 字符较小的串,字典序较小例如abc比bcd小;如果其中一个字符串已经没有更多字符,但另一个字符串还没结束,则较短的字符串的字典序较小例如
* hi比history小,字典序概念推广带任意序列,例如序列1 2 4 7比1 2 5 小
* 本题求最小表示即是需要求n个元素中的最小值一样,用变量ans表示目前为止,字典序最小串在输入串中的起始位置,之后不断更新ans
* 学习笔记:
* 我们要学会用一次全部枚举法,之和查表来输出这种高效的编程方法,关键在于注重这个理范围
**/
//.c
#include <stdio.h>
#include <string.h>
#define maxn 105
//环形串s的表示法p是否比表示法q的字典序小
int less(const char *s, int p, int q)
{
int n = strlen(s);
for(int i = 0; i < n; i++)
{
if(s[(p+i)%n] != s[(q+i)%n])
return s[(p+i)%n] < s[(q+i)%n];
return 0; //相等 一个个比较,且不断的更新
}
}
int main()
{
char s[maxn];
while(scanf("%s",s) == 1)
{
int ans = 0;
int n = strlen(s);
for(int i = 1; i < n; i++)
{
if(less(s, i, ans))
ans = i;
}
for(int i = 0; i < n; i++)
putchar(s[(i+ans)%n]);
putchar('\n');
}
return 0;
}
To_捭阖_youth 2014.6.29. 10:47