VOL5. 天梯赛笔记(2)

1.查验身份证

一个合法的身份证号码由17位地区、日期编号和顺序编号加1位校验码组成。校验码的计算规则如下:首先对前17位数字加权求和,权重分配为:{7,9,10,5,8,4,2,1,6,3,7,9,10,5,8,4,2};然后将计算的和对11取模得到值Z;最后按照以下关系对应Z值与校验码M的值:

Z012345678910
M10X98765432

现在给定一些身份证号码,请你验证校验码的有效性,并输出有问题的号码。

输入格式:
输入第一行给出正整数N(≤100)是输入的身份证号码的个数。随后N行,每行给出1个18位身份证号码。

输出格式:
按照输入的顺序每行输出1个有问题的身份证号码。这里并不检验前17位是否合理,只检查前17位是否全为数字且最后1位校验码计算准确。如果所有号码都正常,则输出All passed。

思路:这个题的主要考察点在于数字字符的判断和计算并判断校验码,首先使用字符串数组存储身份证号,这里为了节约存储空间并没有将所有字符串一次性读入,而是读取一个字符串随即判断再读取下一个。校验码部分,使用数组存储权重计算后用switch-case语句进行匹配。
#include<stdio.h>
#include<stdlib.h>

int main(){
    int flag = 1, allflag = 1;
    int num, z = 0;
    char str[19], m;
    int weight[17] = {7, 9, 10, 5, 8, 4, 2, 1, 6, 3, 7, 9, 10, 5, 8, 4, 2};
    scanf("%d", &num);
    for(int i = 0; i < num; i++){
        flag = 1;
        z = 0;
        getchar();
        scanf("%[^\n]", &str);
        for(int j = 0; j < 17; j++){
            if((int)str[j] < (int)('0') || (int)str[j] > (int)('9')) {
                flag = 0;
                break;
            }
        } 
         for(int j = 0; j < 17; j++){
             z += ((int)(str[j]) - (int)('0')) * weight[j];
         }
         z = z % 11;
         switch(z){
             case 0:
                 m = '1';
                 break;
             case 1:
                 m = '0';
                 break;
             case 2:
                 m = 'X';
                 break;
             case 3:
                 m = '9';
                 break;
             case 4:
                 m = '8';
                 break;
             case 5:
                 m = '7';
                 break;
             case 6:
                 m = '6';
                 break;
             case 7:
                 m = '5';
                 break;
             case 8:
                 m = '4';
                 break;
             case 9:
                 m = '3';
                 break;
             case 10:
                 m = '2';
                 break;
         } 
         if(str[17] != m) flag = 0;
         if(flag == 0){
             printf("%s\n", str);
             allflag = 0;
         }
    }
    if(allflag == 1) printf("All passed");
}

2.字符串减法

本题要求你计算A−B。不过麻烦的是,A和B都是字符串 —— 即从字符串A中把字符串B所包含的字符全删掉,剩下的字符组成的就是字符串A−B。

输入格式:
输入在2行中先后给出字符串A和B。两字符串的长度都不超过10​4​​ ,并且保证每个字符串都是由可见的ASCII码和空白字符组成,最后以换行符结束。

输出格式:
在一行中打印出A−B的结果字符串。

输入样例:
I love GPLT! It’s a fun game!
aeiou
输出样例:
I lv GPLT! It’s fn gm!

思路:由于两个字符串都是不定长的,而如果使用最大长度104,内存的消耗又会变得非常大。所以使用了链表来存储两个串(后来使用链表存储了A,用动态数组存储了B)。首先从缓冲区依次读取字符并链接到链表上,直到读取的字符为"\n",两个链表建立完成后,在遍历进行比较,若相同则删除节点,最后打印出来。
#include<stdio.h>
#include<stdlib.h>

typedef struct LinkNode{
	char ch;
	struct LinkNode* next;
}LinkNode;


LinkNode* init(){
	LinkNode* node = (LinkNode*)malloc(sizeof(LinkNode));
	node->ch = '\0';
	node->next = NULL;
	return node;
}
int main(){
	LinkNode* head = init();
	LinkNode* node = init();
	head->next = node;
	while(1){
		scanf("%c", &node->ch);
		if(node->ch == '\n') break;	
		LinkNode* new_node = init();
		node->next = new_node;
		node = node->next; 
	}
	int lenth = 1;
	char* str = (char*)malloc(sizeof(char) * lenth);
	scanf("%c", &str[lenth - 1]);
	while(str[lenth - 1] != '\n'){
		lenth++;
		str = (char*)realloc(str, sizeof(char) * lenth);
		scanf("%c", &str[lenth - 1]);
	}
	LinkNode* pre_node = head;
	node = head->next;
	int flag;
	while(node->ch != '\n'){
		flag = 1;
		for(int i = 0; i < lenth; i++){
			if(node->ch == str[i]) flag = 0; //这里应该加一个break,后续的比较是没有任何意义的。
		}
		if(flag == 0){
			pre_node->next = node->next;
			node = node->next;
		}
		else{
			node = node->next;
			pre_node = pre_node->next; 
		}
	}
	node = head->next;
	while(node->ch != '\n'){
		if(node->ch == (char)(32)) printf(" ");
		else printf("%c", node->ch);
		node = node->next;
	}
	return 0;
}
补充:在写反思的过程中又有一个想法,可以先将字符串B用链表存储起来,字符串A依次从缓冲区读取字符,若读取的字符在B中没有,则打印,若有,则跳过。这样省下了存储字符串A所需要的空间,并且省去了建立链表的时间。另外,在循环作比较的过程中,如果比较出结果了要及时使用break跳出循环,后续部分的循环没有任何意义。

3.N个数求和

本题的要求很简单,就是求N个数字的和。麻烦的是,这些数字是以有理数分子/分母的形式给出的,你输出的和也必须是有理数的形式。

输入格式:
输入第一行给出一个正整数N(≤100)。随后一行按格式a1/b1 a2/b2 …给出N个有理数。题目保证所有分子和分母都在长整型范围内。另外,负数的符号一定出现在分子前面。

输出格式:
输出上述数字和的最简形式 —— 即将结果写成整数部分 分数部分,其中分数部分写成分子/分母,要求分子小于分母,且它们没有公因子。如果结果的整数部分为0,则只输出分数部分。

思路:题目描述并不复杂,就是做分数加减法,并将计算结果保留为带分数的形式。做分数加减法便需要进行通分,而通分的关键在于求公倍数,在化简的时候需要进行约分,约分的关键在于求公约数。还是老问题,如果循环不需要走到最后,及时break。最后对本题来说要注意一下输出问题,如果整数部分为零分数部分不为零则只输出分数,反过来就只输出整数,而都不为零的话要用一个空格分开,都为零的话要输出整数部分!
#include<stdio.h>
#include<stdlib.h>

int getGBS(int num, int* down){
    int max = down[0];
    for(int i = 1; i < num; i++){
        if(max < down[i]) max = down[i];
    }
    int gbs = max;
    int flag = 1;
    while(1){
        flag = 1;
        for(int i = 0; i < num; i++){
            if(gbs % down[i] != 0) flag = 0;
        }
        if(flag == 1) return gbs;
        else gbs++;
    }
}

int getGYS(int a, int b){
	int min = a<b?a:b;
	int gys;
	for(int i = 1; i <= min; i++){
		if(a % i == 0 && b % i == 0) gys = i;
	}
	return gys;
}

int* getJFS(int num, int* up, int* down){
	int* jfs = (int*)malloc(sizeof(int) * 2);
	jfs[0] = 0;
	jfs[1] = getGBS(num, down);
	for(int i = 0; i < num; i++){
		jfs[0] += up[i] * jfs[1] / down[i];
	}
	return jfs;
}

int* getZFS(int* jfs){
	int* zfs = (int*)malloc(sizeof(int) * 3);
	zfs[0] = jfs[0] / jfs[1];
	zfs[1] = jfs[0] - zfs[0] * jfs[1];
	zfs[2] = jfs[1];
	if(zfs[1] != 0){
		int gys = getGYS(zfs[1], zfs[2]);
		zfs[1] /= gys;
		zfs[2] /= gys;	
	}
	return zfs;
}

int main(){
    int num;
    scanf("%d", &num);
    int* up = (int*)malloc(sizeof(int) * num);
    int* down = (int*)malloc(sizeof(int) * num);
    for(int i = 0; i < num; i++){
        scanf("%d/%d", &up[i], &down[i]);
    }
	int* arr = getJFS(num, up, down);
	int* arr2 = getZFS(arr);
	if(arr2[0] != 0) printf("%d", arr2[0]);
    if(arr2[0] != 0 && arr2[1] != 0) printf(" ");
	if(arr2[1] != 0) printf("%d/%d", arr2[1], arr2[2]);
    if(arr2[0] == 0 && arr2[1] == 0) printf("0");
    return 0;
}

4.树的遍历

给定一棵二叉树的后序遍历和中序遍历,请你输出其层序遍历的序列。这里假设键值都是互不相等的正整数。

输入格式:
输入第一行给出一个正整数N(≤30),是二叉树中结点的个数。第二行给出其后序遍历序列。第三行给出其中序遍历序列。数字间以空格分隔。

输出格式:
在一行中输出该树的层序遍历的序列。数字间以1个空格分隔,行首尾不得有多余空格。

很恼人的是,这个题目说的是求层序遍历,而我不张眼睛的看成了求先序遍历。但思路可以借用一下的,我就整理一下先序遍历的思路吧。

思路:这里我采用了递归的思想,递归在树的遍历里面经常用到,比较难想但代码真的非常简洁。首先看一下题目里给的测试样例,我这里整理成表格:
中序1234567
后序2315764
后序的特点是最后访问的是根节点,即这里的4就是根节点。而中序是先左再根最后右,现在我们知道了这里4是根节点,就可以通过中序看到123是根节点的左子树,567是根节点的右子树。而树结构本身就是一种递归结构,那用同样的方法,我可以从后序里知道1是左子树的根节点,6是右子树的根节点;进一步从中序可以看出1没有左子树,其右子树是23,6的左子树是5右子树是7。通过这样的方式,即使再深的树也可以递归的很明白。先说明一下函数调用时用到的参数列表fun(int* rf, int* rm, int* rl, int ms, int me, int ls, int le)分别表示先序、中序、后序数组,中序起点、中序终点、后序起点、后序终点(这四个主要是递归的时候用,因为每一层递归用到的数组部分是不相同的)接下来看一下递归的过程。
第一次fun(rf, rm, rl, 0, 6, 0, 6)
序号0123456
中序1234567
后序2315764
通过刚才的分析知道了4是根节点,123是左子树,567是右子树,那我们就得到了先序的第一个元素,接下来就是递归的调用左右子树。接下来就是要对包含左右子树的数组部分进行处理,第二层递归要处理的左子树数组部分是中序的0-2(数组中的物理位置序号)后序的0-2,要处理的右子树的数组部分是中序的4-6和后序的3-5,将这些序号作为中序起点终点和后序起点终点作为参数传递给函数。那什么时候是递归的终点呢?如果当处理的数组长度为0或1时,显然就不需要再进行递归了,这里就可以结束了。由于我是将先序遍历存入一个数组最后进行打印操作,因此我需要给一个计数器来获取递归次数以确定某一次调用得到的根节点需要该存进哪一个位置,使用一个全局变量 来记录,每次函数被调用这个计数器都加一便实现了计数(一定要用全局变量,不然这个数会在从深层递归进入到浅层递归时错误的被还原)。
#include<stdio.h>
#include<stdlib.h>

int ord = -1;

void fun(int* rf, int* rm, int* rl, int ms, int me, int ls, int le) {
	ord++;
	rf[ord] = rl[le];
	if (ls == le) return;
	int lenth = me - ms + 1;
	int left, right, i;
	for (i = ms; i <= me; i++) {
		if (rf[ord] == rm[i]) {
			left = i - ms;
			right = me - i;
			break;
		}
	}
	if (left > 0) fun(rf, rm, rl, ms, i - 1, ls, ls + left - 1);
	if (right > 0) fun(rf, rm, rl, i + 1, me, ls + left, ls + left + right - 1);
}

int main() {
	int num;
	scanf("%d", &num);
	int* rf = (int*)malloc(sizeof(int) * num);
	int* rm = (int*)malloc(sizeof(int) * num);
	int* rl = (int*)malloc(sizeof(int) * num);
	for (int i = 0; i < num; i++) {
		scanf("%d", &rl[i]);
	}
	for (int i = 0; i < num; i++) {
		scanf("%d", &rm[i]);
	}
	fun(rf, rm, rl, 0, 6, 0, 6);
	for (int i = 0; i < num; i++) {
		printf("%d", rf[i]);
	}
	return 0;
}
这就是递归吗???40行代码就结束了……可是想起来是真的难。
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值