[刷题之旅no38]P1381 单词背诵

本文分享了一位大学一年级学生如何使用字符串哈希(如bkdrhash)计算字符串值,并利用排序和队列技术解决字符串区间问题。通过排序提高查找效率,通过两个队列分别存储字符串编号和区间长度信息,实现区间长度的计算并找出答案。
摘要由CSDN通过智能技术生成

具体代码就不放出来了,毕竟是一个连数据结构课都还没学的大一萌新,只是在这里分享一下我的思路,一个初学者的小想法。

步骤:

1.读取n,循环读取字符串

2.把每个读到的字符串用哈希求出一个具体的数值(具体怎么求之后说),并且按顺序存入到一个名字叫dictionary的数组之中。

3.对dictionary排序(升降无所谓)

4.读取m,循环读取字符串

5.把每个读取到的字符串用哈希算出一个值

6.在我们已经排好序的dictionary的数组里面找到他的下标(即编号)

7.进行入队等一系列操作(之后再说,重点)通过这个步骤,我们就能把题目的答案求出来了,需要用到两个队列,一个用来存当前字符串对应数值在原dictionary里面的下标,一个储存当我们在第4步读取m后,用这样一个for循环读取字符串时:

for(int i=1;i<=m;i++)
{
  scanf("%s",str);
}

这里面的i,我们也要入队,目的就是为了计算区间长度

8.把结果输出即可

让我细说:

好,一个一个解决问题:

1.哈希值如何算?

我们用到字符串哈希里面的一个常用方法:
叫bkdrhash。有兴趣的自己了解一下,我也是没怎么了解就直接用了吼吼

那么具体如何操作呢:
简单,就是对于一个字符串,循环他的每一个字符,由于每一个字符都有对应的ASCII码值,所以我们做这样一个计算:

unsigned int bkdrhash()
{
	unsigned int sum=0;
	for(int i=1;i<strlen(str);i++)
	{
		sum=sum*31+str[i];
	}
	return sum;
}

我知道你可能有很多问号?

为什么要乘以31?

为什么要用unsigned int

如果发生冲突咋办?

好吧,我们一个一个解决:

问题一和问题二属于原理问题,涉及到数学知识,我还是那句话,可以自己找资料学

问题三:
这道题数据比较水,或者说我们的这个bkdrhash冲突几率很小,反正能过这道题。

哈哈
感觉说了跟没说一样啊。

2.为什么要排序

由于每个字符串都能用哈希算出来一个特定大小的数值

所以我们排序之后,再后来的循环之后就可以用二分查找提升效率了

3.细讲第七步

好的,容我细琐:

首先需要用到的:

1.两个队列:
一个为编号队列,一个是位置队列

编号队列用来解决你是谁的问题

位置队列用来解决你在哪里的问题

2.cntline数组,初始化都是0;

下标填入字符串在dictionary数组里面对应的编号

返回值就是他目前在队列中的个数

解析原理:

遇到一个字符串,我们都能用哈希算出其值,
而我们总可以在dictionary数组中找到这个值

并且,都能找到一个独一无二的下标

此时,我们将得到的这个下标(比如a)入队(编号队列)
cntline[a]++;

如果cntline[a]=1,那么说明队列之前都没有这个元素,他是新来的

    cntnum++(记录不同的数字个数)
	
    把cntnum和maxnum比较,
		
        如果cntnum大于等于maxnum,则更新maxnum
		
        你问我为什么等于也要更新,别着急,除了求最多,我们还要求最短
		
        此时再比较队列尾部元素对应原下标end和首元素first,长度就是length=end-first+1;
		
        然后让这个length和minlength比较,
			
            如果小于minlength,那么就更新最短值
			
            不小于那就什么都不干
		
        如果cntnum小于maxnum,那么什么都不干(如果按照我这个思路,是不会小于的)

如果cntline[b[i]]大于1,那么说明此时数组中有重复的数字,我们需要保证把不需要的重复数组剔除

如果重复数字在队列中,那么我们就不管,因为它被包含在里面了
	此时判断队首元素,line[head]对应的cntline[line[head]]是否大于1
	不大于1,不管
	大于1
	一直出队直到首元素不大于1为止

最终把整个数组2都遍历结束之后,我们就可以把minlength和maxnum输出就可以得到想要的答案了

小细节

1.如果没有出现最大值和最小值,我们不可以直接输出maxnum和minlength,用一个flag,如果maxnum和minlen更新我们让flag=1;最后输出时候看一下就好了

2.此道题不存在大小写敏感

算了,感觉我自己说不清楚,还是放代码把!

代码:

#include<stdio.h>
#include<string.h>
typedef unsigned int ui;
int linew[100005]={0},linep[100005]={0},cntline[1500]={0};
int minlen=20000,maxnum=0,head=1,tail=0,cntnum=0,n,m,flag=0;
char str[15];
ui diction[1500]={0};

ui bkdrhash()
{
	ui sum=0;
	for(int i=1;i<strlen(str);i++)
	{
		sum=sum*31+str[i];
	}
	return sum;
}

int find()
{
	ui val=bkdrhash();
	int left=1,right=n,mid;
	while(1)
	{
		if(left>right)
		{
			return -1;
		}
		mid=(left+right)/2;
		if(diction[mid]==val)
		{
			return mid;
		}
		else if(diction[mid]>val)
		{
			right=mid-1;
		}
		else if(diction[mid]<val)
		{
			left=mid+1;
		}
	}
}

void sort()
{
	ui tmp;
	for(int gap=n/2;gap>0;gap=gap/2)
	{
		for(int i=gap+1;i<=n;i++)
		{
			for(int j=i;j-gap>0&&diction[j-gap]>diction[j];j=j-gap)
			{
				tmp=diction[j-gap];
				diction[j-gap]=diction[j];
				diction[j]=tmp;
			}
		}
	}
}


void cal(int gps)//核心函数,每次读取strlen的时候更新队列 
{
	int no=find();
	if(no==-1)
	{
		return ;
	}
	tail++;
	linew[tail]=no;
	cntline[no]++;
	linep[tail]=gps;
	if(cntline[no]==1)
	{
		cntnum++;
		if(cntnum>maxnum)
		{
			flag=1;
			maxnum=cntnum;
			minlen=linep[tail]-linep[head]+1;
		}
		else if(cntnum==maxnum)
		{
			flag=1;
			if(linep[tail]-linep[head]+1<minlen)
			{
				minlen=linep[tail]-linep[head]+1;
			}
		}
	}
	else//如果出队,要随时更新minlen 
	{
		while(1)
		{
			if(cntline[linew[head]]>1)//出队 
			{
				cntline[linew[head]]--;
				head++;//此时判断最小值 
				if(linep[tail]-linep[head]+1<minlen)
				{
					flag=1;
					minlen=linep[tail]-linep[head]+1;
				}
			}
			else
			{
				break;
			}
		} 
	}
}
void scan()
{
	scanf("%d",&n);
	for(int i=1;i<=n;i++)
	{
		scanf("%s",str);
		diction[i]=bkdrhash();
	}
}

int main()
{
	scan();//扫描+建表
	sort();//排序 
	scanf("%d",&m);
	for(int i=1;i<=m;i++)
	{
		scanf("%s",str);
		cal(i);
	}
	if(flag)
	{
		printf("%d\n%d\n",maxnum,minlen);
	}
	else
	{
		printf("0\n0\n"); 
	}
	return 0; 
}
根据提供的引用内容,P1381题目是关于单词背诵的。题目要求在一篇文章中找出一个连续的段落,其中包含最多的要背诵单词(重复的只算一个),并且在背诵单词量尽量多的情况下,还要使选出的文章段落尽量短,这样可以用尽量短的时间学习尽可能多的单词。 以下是使用C++的双指针(尺取法)解题思路: ```cpp #include <iostream> #include <unordered_map> #include <vector> using namespace std; int main() { int n, m; cin >> n >> m; vector<string> words(n); unordered_map<string, int> wordCount; for (int i = 0; i < n; i++) { cin >> words[i]; wordCount[words[i]]++; } int left = 0, right = 0; // 左右指针 int maxCount = 0; // 最多的目标单词个数 int minLength = n; // 最短长度 unordered_map<string, int> window; // 当前区间内的单词计数 while (right < n) { window[words[right]]++; if (window.size() <= m) { // 当前区间内的单词种类不超过m if (window.size() == m) { // 当前区间内的单词种类等于m,更新最多的目标单词个数和最短长度 int count = 0; for (auto it : window) { count += min(it.second, wordCount[it.first]); } if (count > maxCount || (count == maxCount && right - left + 1 < minLength)) { maxCount = count; minLength = right - left + 1; } } right++; } else { // 当前区间内的单词种类超过m,左指针右移 window[words[left]]--; if (window[words[left]] == 0) { window.erase(words[left]); } left++; } } cout << minLength << endl; return 0; } ```
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值