算法竞赛入门经典第3章【小结与习题】

习题3-1 分数统计(stat)

输入一些学生的分数,哪个分数出现的次数最多?如果有多个并列,从小到大输出

任务1:分数均不超过100的非负整数

任务2:分数均不超过100的非负实数,但最多保留两位小数。

这个类似单词统计词频,按字典序输出频率最高的那些。


【思路】

pic

任务1和任务2差不多,换成double类型即可;现在以任务1为例解答——

一种思路是从第二个读入的数字开始,从已有的集合(数组)中寻找,若重复则该元素的频率变量自增;否则添加新元素;

最后根据频率变量排序,然后截取频率最高的那段(多个或者1个),再对该段按分数变量排序,最后输出这段数组的分数;

#include <stdio.h>
#include <stdlib.h>
#define N 10000

typedef struct node {
	int val;
	int freq;	
}node;

node a[N];//集合
int len = 0;//集合大小

int
isInclude(int key) {
	int i;
	for (i = 0; i != len; i++) {
		if (key == a[i].val) {
			a[i].freq++;
			return 1;
		}
	}
	return 0;
}

int
cmp1(const void* a, const void *b)
{
	return (*(node *)b).freq - (*(node *)a).freq;//descending order
}

int
cmp2(const void* a, const void *b)
{
	return (*(node *)a).val - (*(node *)b).val;
}

int cmp1(const void* a, const void *b);
int cmp2(const void* a, const void *b);
int isInclude(int key);

int
main(void)
{
	int n, tmp;
	int i, j;
	
	scanf("%d", &n);
	for (i = 0; i != n; i++) {
		a[i].val = -1;
		a[i].freq = 1;
	}

	extern int len;
	scanf("%d", &(a[len++].val));	
	for (i = 0; i != n-1; i++) {
		scanf("%d", &tmp);
		if (!isInclude(tmp)) {
			a[len++].val = tmp;
		}
		//else the element's freq++ @isInclude();
	}
	
	qsort(a, len, sizeof (node), cmp1);
	
	for (i = 0; i != len; i++) {
		if (a[i+1].freq != a[i].freq) {
			tmp = i;
			break;
		}
	}
	qsort(a, tmp+1, sizeof (node), cmp2);
	
	for (i = 0; i != tmp+1; i++)
		printf("%d\n", a[i].val);
	
	return 0;
}


习题3-2 单词的长度(word)

输入若干个单词,输出它们的平均长度。单词只包含大写字母和小写字母,用一个或多个空格隔开。

#include <stdio.h>
#include <string.h>
#define N 10000

char str[N];
char buf[N/100];

int len1(char *s);
int len2(char *s);

int
len1(char *s)
{
	int count = 0;
	int words = 0;
	char *p = s;
	
	for (; *p != '\n'; p++) {
		if (*p != ' ')			
			++count;
		if (*p != ' ' && (*(p+1) == ' ' || *(p+1) == '\n'))
			++words;
	}	
	return count/words;
}

int
len2(char *s)
{
	int words = 0;
	int count = 0;
	char *p = s;
	
	while ( 1 == sscanf(p, "%s", buf) ) {
		p += strlen(buf)+1;
		count += strlen(buf);  	
		++words;
	}	
	return count/words;	
}

int
main(void)
{
	fgets(str, sizeof (str), stdin);
	printf("%d\n", len1(str));//or you can call len2(str);	
	return 0;
}
对这个题目做了一个假定。约定输入的总长度不超过10000个字符,每个单词长度不超过100个字符。

(其实也可以只利用一个getchar()写一个就地的算法(不需要额外辅助空间),也就不需要这个假设了)

这样求单词平均长度的方法可以有两个,分别写成函数len1()和len2()



习题3-3 乘积的末3位

输入若干个整数(可以是正数、负数或者零),输出它们的乘积的末3位。这些整数中会混入一些由大写字母组成的字符串,你的程序应该忽略它们。提示:试试看,在执行scanf("%d")时输入一个字符串会怎样?

输入:AB123CC   BB123123321321          DDD22    888888888888888888888888888ZZ      -411B

输出:968

输入:AA-11BBB   D2CCC

输出:-22

假定末3位是指,不足3位就输出数字本身,如果是负数则包括负号,比如结果是-12,则输出-12;结果是11,则输出11,结果是0,则输出0;


分析:

必须假定整数(包括字母)长度小于某个数,下面假定长度不超过1000,整数个数不超过100;

设计算法如下:

每读入一个字符串,保存数字部分的后三位(位数不足则保存数字本身),同时判断符号,是负数则标志变量flag(初始为1)改变符号;

初始product为1,按先后逐个处理数字串, 每次处理过程中,product与当前数字串的数字相乘后再模1000,更新到product;

如果product<100,则输出flag*product,否则直接输出product;

为了确保乘积不会溢出,所以必须每步相乘后都要取模。

#include <stdio.h>
#include <string.h>
#include <ctype.h>
#include <stdlib.h>
#define N 100000
#define M 5000
#define L 5

char input[N];
char str[M];
char tmp[L];
char rev[L];

int
main(void)
{
	int i, len, k;
	int flag = 1;
	int product = 1;
	char *p = NULL;

	fgets(input, sizeof (input), stdin);
	p = input;
	while (sscanf(p, "%s", str) == 1) {
		len = k = 0;
		for (i = strlen(str)-1; i != -1; i--) {
			if (len != 3 && 1 == isdigit(str[i]))
				tmp[len++] = str[i];
			if (str[i] == '-') {
				flag = -flag;
				break;
			}
		}		
		tmp[len] = '\0';
		while (len > 0)
			rev[k++] = tmp[--len];
		rev[k] = '\0';	
		product *= atoi(rev);
		product %= 1000;
		p += strlen(str)+1;
		while (*p == ' ') p++;/* 滤空 */			
	}
	printf("%d\n", product < 100 ? product*flag : product);
	return 0;
}


习题3-4

编程程序读入一行恰好包括一个+或-或*的表达式,输出它的值。运算符保证是二元运算符,且两个运算数均不超过100的非负整数。运算数和运算符可以紧挨也可以有一个或多个空格、TAB隔开。行首尾均可以有空格。提示:选择合适的输入方法可以将问题简化。

#include <stdio.h>
#define N 1000

char str[N];

int
main(void)
{
	char *p = NULL;
	int op1, op2;
	
	fgets(str, sizeof(str), stdin);//it will contains '\n';
	
	for (p = str; *p != '\n'; p++) {
		if (*p == '+' || *p == '-' || *p == '*')
			break;
	}
	if (*p != '\n'){
		switch(*p) {
		case '+':
			sscanf(str, "%d + %d", &op1, &op2);
			printf("%d\n", op1+op2);
			break;
		case '-':
			sscanf(str, "%d - %d", &op1, &op2);
			printf("%d\n", op1-op2);
			break;
		case '*':
			sscanf(str, "%d * %d", &op1, &op2);
			printf("%d\n", op1*op2);
			break;
		}	
	}
	return 0;
}

注意"%d + %d"这个+号两边必须有空格才会吃掉诸如3    +1这种情况。。。



习题3-5 旋转

输入一个n*n字符矩阵,把它左旋90度后输出

#include <stdio.h>
#define N 100

char a[N][N];


int
main(void)
{
	int n;
	int i, j;

	scanf("%d", &n);

	for (i = 0; i != n; i++) {
		for (j = 0; j != n; j++)
			scanf("%s", &a[i][j]);		
	}
	
	for (j = n-1; j != -1; j--) {
		for (i = 0; i != n; i++)
			printf("%c ", a[i][j]);
		printf("\n");
	}
	
	return 0;
}

注意:a[i][j]

i表示的是列

j表示的是行

比如输出

1 2 3

4 5 6

7 8 9

的第三列,应该是

for (int i = 0; i != n; i++)

printf("%d ", a[i][2]);


习题3-6 进制转换1

输出基数b( 2 <= b <= 10)和正整数n(十进制),输出n的b进制表示

思路:关键是除留余数法时,结束的条件可以是当前剩下的数字<=基数或者当前剩下的数字的==它的模

至于逆序输出,可以考虑用递归实现

递归版本:

#include <stdio.h>

void
trans(int n, int b)
{
	if (n >= b)
		trans(n/b, b);
	printf("%d", n%b);
}

void trans(int n, int b);

int
main(void)
{
	int b, n;

	scanf("%d %d", &b, &n);
	trans(n, b);
	printf("\n");
	return 0;
}

非递归版本:

#include <stdio.h>
#define N 100
int a[N];

int
main(void)
{
	int b, n;
	int k = 0;
	int i;
	
	scanf("%d %d", &b, &n);
	while (n >= b) {
		a[k++] = n%b;
		n /= b;
	}
	a[k++] = n%b;
	for (i = k-1; i != -1; i--)
		printf("%d", a[i]);
	printf("\n");
	
	return 0;
}



习题3-7 进制转换2

输出基数b( 2 <= b <= 10)和正整数n(b进制),输出n的十进制表示

思路,比如八进制的678,换成十进制就是8*8的0次方+7*8的1次方+6*8的2次方,因此很容易编写

#include <stdio.h>
#include <string.h>
#define N 100

char str[N];

int
main(void)
{
	int n, i, k, tmp;
	int res = 0;

	scanf("%d %s", &n, str);
	for (i = strlen(str)-1; i != -1; i--) {
		tmp = str[i]-'0';
		k = strlen(str)-1-i;
		while(k--)
			 tmp *= n;
		res += tmp;
	}
	printf("%d\n", res);
	
	return 0;
}

习题3-8 手机键盘




你可能感兴趣的:(算法竞赛入门经典第3章【小结与习题】)