《算法笔记》学习日记——4.2 散列

4.2 哈希

Codeup Contest ID:100000582

问题 A: 谁是你的潜在朋友

题目描述
“臭味相投”——这是我们描述朋友时喜欢用的词汇。两个人是朋友通常意味着他们存在着许多共同的兴趣。然而作为一个宅男,你发现自己与他人相互了解的机会 并不太多。幸运的是,你意外得到了一份北大图书馆的图书借阅记录,于是你挑灯熬夜地编程,想从中发现潜在的朋友。
首先你对借阅记录进行了一番整理,把N个读者依次编号为1,2,…,N,把M本书依次编号为1,2,…,M。同时,按照“臭味相投”的原则,和你喜欢读同一本书的人,就是你的潜在朋友。你现在的任务是从这份借阅记录中计算出每个人有几个潜在朋友。
输入
每个案例第一行两个整数N,M,2 <= N ,M<= 200。接下来有N行,第i(i = 1,2,…,N)行每一行有一个数,表示读者i-1最喜欢的图书的编号P(1<=P<=M)
输出
每个案例包括N行,每行一个数,第i行的数表示读者i有几个潜在朋友。如果i和任何人都没有共同喜欢的书,则输出“BeiJu”(即悲剧,^ ^)
样例输入

4 5
2
3
2
1

样例输出

1
BeiJu
1
BeiJu

思路
这题比较简单,散列函数是恒等变换的关系(H(key)=key),只要把输入的编号作为数组的下标,然后输入一个对应下标的值就++,最后再看一下,如果是大于等于2的,那就输出对应的值减1,如果不是大于等于2的,就输出“BeiJu”。
因为要输入N行之后再顺序输出N行对应的量,所以我这里选择用一个temp数组把输入的编号依次存了下来,这样输出的时候只要遍历该数组就行了。
代码

#include<stdio.h>
#include<string.h>
#include<stdlib.h>
#include<math.h>
#include<algorithm>
using namespace std;
int main(){
	int N, M;
	int book[201]={0};//下标是书的编号,其内容是喜欢这本书的人数
	int temp[201]={0};//存放输入的编号 
	while(scanf("%d%d", &N, &M) != EOF){
		for(int i=1;i<=N;i++){
			int x;
			scanf("%d", &x);
			temp[i] = x;
			book[x]++;
		}
		for(int i=1;i<=N;i++){
			if(book[temp[i]]==1) printf("BeiJu\n");
			else printf("%d\n", book[temp[i]]-1);
		}
		memset(book, 0, sizeof(book));
		memset(temp, 0, sizeof(temp));
	} 
	return 0;
} 

问题 B: 分组统计

题目描述
先输入一组数,然后输入其分组,按照分组统计出现次数并输出,参见样例。
输入
输入第一行表示样例数m,对于每个样例,第一行为数的个数n,接下来两行分别有n个数,第一行有n个数,第二行的n个数分别对应上一行每个数的分组,n不超过100。
输出
输出m行,格式参见样例,按从小到大排。
样例输入

1
7
3 2 3 8 8 2 3
1 2 3 2 1 3 1

样例输出

1={2=0,3=2,8=1}
2={2=1,3=0,8=1}
3={2=1,3=1,8=0}

思路
这题也是一个经典的恒等变换的hash映射,我的想法是先将输入的数存储在一个数组中(我命名为number),然后将输入的分组情况存储在另一个数组中(我命名为tempgroup),最后构造一个二维数组group(这里的数组一定要开大一点!!这里的数组一定要开大一点!!这里的数组一定要开大一点!! 重要的事说三遍!而且要开在main()函数外面,不然devc++会爆的,我就是在这个问题上整了大半天都没明白哪儿错了,后来上了牛客网对比了一下正确答案和我的答案,才发现是这个二维数组开得太小了,应该牛客网和codeup上测试点都是最大到1000,所以要开1001*1001的数组),group数组的行对应于分组情况(即tempgroup),列对应于数(即number)。

这里还有一个问题,就是即便分组中没有某个数,还是要输出0的,比如样例里的1={2=0,所以我们需要将二维数组的所有数初始化为-1,然后先遍历一遍,将所有出现过的数都置0(每一行都要,比如样例中的话就是第1行、第2行、第3行的2、3、8三列全部赋0),然后再遍历1到n,把行列对应的元素加1(因为是一一对应的关系,所以只要一个for循环就行了,比如num[1]正好对应tempgroup[1],num[2]正好对应tempgroup[2]……),最后再遍历整个二维数组(当然,不用全部遍历,只要遍历到行和列对应的最大值即可),将不是-1的元素输出即可。

这是我做题的时候画的思路哈,应该图更好解释这个hash映射的关系:
这里的temp其实就是tempgroup
代码

#include<stdio.h>
#include<string.h>
#include<stdlib.h>
#include<math.h>
#include<algorithm>
using namespace std;
int findmax(int a[], int n){
	int max = 0;
	for(int i=1;i<=n;i++){ //为了方便理解,除了二维数组,其他两个数组我都是从1~n存储的
		if(a[i]>max) max=a[i];
	}
	return max;
}
int number[150]={0};//存储输入的数
int tempgroup[150]={0};//存储输入的组 
int group[1001][1001];//记得开在main函数外面,不然devc++运行不了
int main(){
	int m;
	while(scanf("%d", &m) != EOF){
		while(m--){
			int n;
			memset(group, -1, sizeof(group));;//初始化为-1 
			scanf("%d", &n);
			for(int i=1;i<=2;i++){
				if(i==1){
					for(int j=1;j<=n;j++) scanf("%d", &number[j]);
				}
				else{
					for(int j=1;j<=n;j++) scanf("%d", &tempgroup[j]);
				}
			}
			for(int i=1;i<=n;i++){
				for(int j=1;j<=n;j++){
					group[tempgroup[i]][number[j]] = 0;//把出现过的数字都赋0 
				}
			}
			for(int i=1;i<=n;i++) group[tempgroup[i]][number[i]]++;//输入对应的元素
			for(int i=0;i<=findmax(tempgroup, n);i++){  //组别作为行
				int count = 0;
				for(int k=0;k<=findmax(number, n);k++){
					if(group[i][k]==-1) count++;
				}
				if(count==findmax(number, n)+1) continue; 
                            //上面这一段话是为了防止分组是断开的而输出不必要的内容(比如分组为1、2、4,这时就需要把3滤除)
				else{
					printf("%d={", i);
					int k = 1;
					for(int j=0;j<=findmax(number,n);j++){ //数字作为列
						if(group[i][j]!=-1){
							if(k==1){
								printf("%d=%d", j, group[i][j]);
								k++;
							}
							else{
								printf(",%d=%d", j, group[i][j]);
								k++;
							}
						}
					}
					printf("}\n");
				}
			}
			memset(number, 0, sizeof(number));
			memset(tempgroup, 0, sizeof(tempgroup));
			memset(group, -1, sizeof(group));
		}
	}	
	return 0;
}

问题 C: Be Unique (20)

题目描述
Being unique is so important to people on Mars that even their lottery is designed in a unique way. The rule of winning is simple: one bets on a number chosen from [1, 104]. The first one who bets on a unique number wins. For example, if there are 7 people betting on 5 31 5 88 67 88 17, then the second one who bets on 31 wins.
输入
Each input file contains one test case. Each case contains a line which begins with a positive integer N (<=105) and then followed by N bets. The numbers are separated by a space.
输出
For each test case, print the winning number in a line. If there is no winner, print “None” instead.
样例输入

7 5 31 5 88 67 88 17
5 888 666 666 888 888

样例输出

31
None

思路
首先这一题也是一个恒等关系的hash映射,直接将输入的数作为下标,然后把下标对应的值加1即可。
但是这里有个问题,比如样例1中的31、67和17都只出现了一次,那么要输出第一个只出现一次的数,所以我这里为了记录顺序,准备了两个数组:number用来直接存储输入的数(同时也能记录顺序),num用来记录数出现的次数(数作为下标,内容是出现的次数),最后遍历整个number数组去寻找num[number[i]]==1的数即可。
代码

#include<stdio.h>
#include<string.h>
#include<stdlib.h>
#include<math.h>
#include<algorithm>
using namespace std;
int number[100000]={0};//存储N个数(这里是为了记录顺序)
int num[10000]={0};//记录所有数对应出现的次数(之所以开这么大是因为下标是数,而数最大可到10^4)
int main(){
	int N;
	while(scanf("%d", &N) != EOF){
		for(int i=0;i<N;i++) scanf("%d", &number[i]);
		for(int i=0;i<N;i++) num[number[i]]++;
		int k = 0;
		for(int i=0;i<N;i++){
			if(num[number[i]]==1){
				printf("%d\n", number[i]);
				break;
			}
			else k++;
		}
		if(k==N) printf("None\n");
		memset(number, 0, sizeof(number));
		memset(num, 0, sizeof(num));
	}
	return 0;
} 

问题 D: String Subtraction (20)

题目描述
Given two strings S1 and S2, S = S1 - S2 is defined to be the remaining string after taking all the characters in S2 from S1. Your task is simply to calculate S1 - S2for any given strings. However, it might not be that simple to do it fast.
输入
Each input file contains one test case. Each case consists of two lines which gives S1 and S2, respectively. The string lengths of both strings are no more than 104. It is guaranteed that all the characters are visible ASCII codes and white space, and a new line character signals the end of a string.
输出
For each test case, print S1 - S2 in one line.
样例输入

They are students.
aeiou

样例输出

Thy r stdnts.

思路
这题的大致意思是要在第一个字符串中删去所有第二个字符串中出现的字符,比如“They are students”删去“aeiou”之后就是“Thy r stdnts”。要想一个一个字符删,显然是比较麻烦的,比如首先要遍历第二个字符串,先要找字符a,然后再把对应的第一个字符串中的所有a删掉,再找字符e,再把第一个字符串中所有的e删掉……当第二个字符串特别长的时候,这种操作显然是过于麻烦了。

那么我的思路是,按照ascii码hash。首先还是把读入的两个字符串存入s1和s2中,然后再开一个构造hash关系的数组,这个数组以s1字符的ascii码作为下标,其对应的值1代表已经出现,0代表未出现。那么,我们只要将s2中每个字符的ascii码对应过去,把相应的1改成0即可。最后再遍历s1,如果他的ascii码对应值是1,则输出,否则不输出。这样,就完成了删字母的操作。
代码

#include<stdio.h>
#include<string.h>
#include<stdlib.h>
#include<math.h>
#include<algorithm>
using namespace std;
char s1[10000]={0};
char s2[10000]={0};
char hashtable[150]={0};
int main(){
	while(gets(s1) != NULL){
		gets(s2);
		for(int i=0;i<strlen(s1);i++) hashtable[s1[i]] = 1;//把对应字符的值置1,代表已经出现过
		for(int i=0;i<strlen(s2);i++) hashtable[s2[i]] = 0;//把对应字符的值置0,代表要删去
		for(int i=0;i<strlen(s1);i++){
			if(hashtable[s1[i]]==0) continue;//如果是0,直接进入下一轮循环
			else printf("%c", s1[i]); 
		}
		printf("\n");
		memset(s1, 0, sizeof(s1));
		memset(s2, 0, sizeof(s2));
		memset(hashtable, 0, sizeof(hashtable)); 
	}
	return 0;
} 

小结

如果掌握了hash的精髓,那么这一系列的题也是不难的,主要的过程是构建一个hash对应关系的数组(或是一维数组,抑或是二维数组),把题目要求的东西以下标和值的形式一一对应起来,另外,26进制转10进制的方法是字符串的hash,而最后一题是字符的hash,直接用ascii码就好了,不要想得太复杂。
当你把hash对应关系的数组构建出来以后,那么相信剩下的问题也难不倒你了。

发布了54 篇原创文章 · 获赞 27 · 访问量 5001

猜你喜欢

转载自blog.csdn.net/weixin_42257812/article/details/104972668
今日推荐