字符串专题详解

目录

字符串hash进阶

KMP算法

next数组

KMP算法

KMP算法优化

字符串hash进阶

字符串hash是指将一个字符串S映射为一个整数,使得该整数可以尽可能唯一地代表字符串S。那么在一定程度上,如果两个字符串转换成的整数相等,就可以认为这两个字符串相同。

针对字符串hash可以采用下面的式子:

H[i]=H[i-1]*26+index\left ( str[i] \right )

其中str[i]表示字符串的i号位,index函数将A~Z转换为0~25。在这个转换方式中,虽然字符串与整数是一一对应的,但是由于没有进行适当处理,因此当字符串长度较长时,产生的整数会非常大,没办法用一般的数据类型保存。为了应对这种情况,只能舍弃一些“唯一性”,将产生的结果对一个整数mod取模。

H[i]=\left ( H[i-1]*26+index\left ( str[i] \right ) \right ) MOD mod

通过这种方式可以把字符串转换成范围上能接受的整数。但这又会产生另外的问题,也就是可能有多个字符串的hash值相同,导致冲突。不过幸运的是,在实践中发现,在int数据范围内,如果把进制设置为一个10^{7}级别的素数p(例如10000019),同时把mod设置为一个10^{9}级别的素数(例如1000000007),那么冲突的概率将会变得非常小,很难发生冲突。

例题:给出N个只有小写字母的字符串,求其中不同的字符串的个数。

对这个问题,如果只用字符串hash来做,那么只需要将N个字符串使用字符串hash函数转换为N个整数,然后将它们排序去重即可。

#include<iostream>
#include<string>
#include<vector>
#include<algorithm>
using namespace std;
const int MOD=1000000007;
const int p=10000019;
vector<int> ans;
long long hashFunc(string str){long long H=0;for(int i=0;i<str.length();i++){H=(H*p+str[i]-'a')%MOD;}return H;
}
int main(){string str;while(getline(cin,str),str!="#"){long long id=hashFunc(str);ans.push_back(id);}sort(ans.begin(),ans.end());int count=0;for(int i=0;i<ans.size();i++){if(i==0||ans[i]!=ans[i-1]){count++;}}cout<<count<<endl;return 0;
}

接着考虑求解字符串的子串的hash值,也就是求解H[i…j].经过一系列推导可以得出hash函数如下

H[i\cdots j]=\left ( \left ( H[j]-H[i-1]\times p^{j-i+1} \right ) MOD mod+mod\right ) MOD mod

然后来看一个问题:输入两个长度均不超过1000的字符串,求他们的最长公共子串的长度。 

对这个问题,可以先分别对两个字符串求出hash值(同时记录对应的长度),然后找出两堆子串对应的hash值中相等的那些,便可以找出最大长度,时间复杂度为O\left ( n^{2}+m^{2} \right ),其中n和m分别为两个字符串的长度。

#include<iostream>
#include<cstdio>
#include<string>
#include<vector>
#include<map>
#include<algorithm>
using namespace std;
typedef long long LL;
const LL MOD=1000000007;
const LL P=100000019;
const LL maxn=1010;
LL powP[maxn],H1[maxn]={0},H2[maxn]={0};
vector<pair<int,int> > pr1,pr2;
void init(int len){powP[0]=1;for(int i=1;i<=len;i++){powP[i]=(powP[i-1]*P)%MOD;}
}
void calH(LL H[],string &str){H[0]=str[0];for(int i=1;i<str.length();i++){H[i]=(H[i-1]*P+str[i])%MOD;}
}
int calSingleSubH(LL H[],int i,int j){if(i==0){return H[j];}return ((H[j]-H[i-j]*powP[j-i+1])%MOD+MOD)%MOD;
}
void calSubH(LL H[],int len,vector< pair<int,int> > &pr){for(int i=0;i<len;i++){for(int j=0;j<len;j++){int hashValue=calSingleSubH(H,i,j);pr.push_back(make_pair(hashValue,j-i+1));}}
}
int getMax(){int ans=0;for(int i=0;i<pr1.size();i++){for(int j=0;j<pr2.size();j++){if(pr1[i].first==pr2[j].first){ans=max(ans,pr1[i].second);}}}return ans;
}
int main(){string str1,str2;getline(cin,str1);getline(cin,str2);init(max(str1.length(),str2.length()));calH(H1,str1);calH(H2,str2);calSubH(H1,str1.length(),pr1);calSubH(H2,str2.length(),pr2);printf("ans=%d\n",getMax());return 0;
}

现在考虑解决最长回文子串问题,这里将用字符串hash+二分的思路去解决它,时间复杂度为O\left ( nlogn \right )

对一个给定的字符串str,可以先求出其字符串hash数组H1,然后再将str反转,求出反转字符串rstr的hash数组H2,接着分回文串的奇偶情况进行讨论。

(1)回文串的长度为奇数:枚举回文中心点i,二分子串的半径k,找到最大的使子串[i-k,i+k]是回文串的k。其中判断子串[i-k,i+k]是回文串等价于判断str的两个子串[i-k,i]与[i,i+k]是否是相反的串。而这等价于判断str的[i-k,i]子串与反转子串rstr的[len-1-(i+k),len-1-i]子串是否相同([a,b]在反转字符串中的位置为[len-1-b,len-1-a]),因此只需要判断H1[i-k…i]与H2[len-1-(i+k)…len-1-i]是否相等即可

(2)回文串的长度为偶数:枚举回文空隙点,令i表示空隙左边第一个元素的下标,二分字串的半径为k,找到最大的使子串[i-k+1,i+k]是回文串的k。其中判断子串[i-k+1,i+k]是回文串等价于判断str的两个子串[i-k+1,i]与[i+1,i+k]是否是相反的串。而这等价于判断str的[i-k+1,i]子串与反转字符串rstr的[len-1-(i+k),len-1-(i+1)]子串是否相同,因此只需要判断H1[i-k+1…i]与H2[len-1-(i+k)…len-1-(i+1)]是否相等即可。

#include<iostream>
#include<cstdio>
#include<cstring>
#include<vector>
#include<algorithm>
using namespace std;
typedef long long LL;
const LL MOD=1000000007;
const LL P=10000019;
const LL maxn=200010;
LL powP[maxn],H1[maxn],H2[maxn];
void init(){powP[0]=1;for(int i=1;i<maxn;i++){powP[i]=(powP[i-1]*P)%MOD;}
}
void calH(LL H[],string &str){H[0]=str[0];for(int i=1;i<str.length();i++){H[i]=(H[i-1]*P+str[i])%MOD;}
}
int calSingleSubH(LL H[],int i,int j){if(i==0){return H[j];}return ((H[j]-H[i-1]*powP[j-i+1])%MOD+MOD)%MOD;
}
int binarySearch(int l,int r,int len,int i,int isEven){while(l<r){int mid=(l+r)/2;int H1L=i-mid+isEven,H1R=i;int H2L=len-1-(i+mid),H2R=len-1-(i+isEven);int hashL=calSingleSubH(H1,H1L,H1R);int hashR=calSingleSubH(H2,H2L,H2R);if(hashL!=hashR){r=mid;}else{l=mid+1;}}return l-1;
}
int main(){init();string str;getline(cin,str);calH(H1,str);reverse(str.begin(),str.end());calH(H2,str);int ans=0;//奇回文for(int i=0;i<str.length();i++){int maxLen=min(i,(int)str.length()-1-i)+1;int k=binarySearch(0,maxLen,str.length(),i,0);ans=max(ans,k*2+1);} //偶回文for(int i=0;i<str.length();i++){int maxLen=min(i+1,(int)str.length()-1-i)+1;int k=binarySearch(0,maxLen,str.length(),i,1);ans=max(ans,k*2);} cout<<ans<<endl;return 0;
}

KMP算法

主要解决的是字符串的匹配问题,如果给定两个字符串text和pattern,需要判断字符串pattern是否是字符串pattern是否是字符串text的子串。

next数组

next[i]表示使子串s[0……i]的前缀s[0……k]等于后缀s[i-k……i]的最大的k(前缀和后缀可以重叠,但不能是s[0……i]本身,next[i]就是所求最长相等前后缀中前缀最后一位的下标。求解next数组的过程其实就是模式串pattern自我匹配的过程。

void getNext(char s[],int len){int j=-1;next[0]=-1;for(int i=1;i<len;i++){while(j!=-1&&s[i]!=s[j+1]){j=next[j];}if(s[i]==s[j+1]){j++;}next[i]=j;}
}

KMP算法

KMP算法的一般思路:

(1)初始化j=-1,表示pattern当前已被匹配的最后位。

(2)让i遍历文本串text,对每个i,执行(3)(4)来试图匹配text[i]和pattern[j+1]。

(3)不断令j=next[j],直到j回退为-1,或是text[i]=pattern[j+1]成立。

(4)如果text[i]==pattern[j+1],则令j++。如果j达到m-1,说明pattern是text的子串,返回true。

//KMP算法,判断pattern是否是text的子串
bool KMP(char text[],char pattern[]){int n=strlen(text),m=strlen(pattern);getNext(pattern,m);int j=-1;for(int i=0;i<n;i++){while(j!=-1&&text[i]!=pattern[j+1]){j=next[j];}if(next[i]==pattern[j+1]){j++;}if(j==m-1){return true;}}return false;
} 

统计模式串pattern出现次数的KMP算法代码如下:

int KMP(char text[],char pattern[]){int n=strlen(text),m=strlen(pattern);getNext(pattern,m);int ans=0,j=-1;for(int i=0;i<n;i++){while(j!=-1&&text[i]!=pattern[j+1]){j=next[j];}if(text[i]==pattern[j+1]){j++;}if(j==m-1){	ans++;j=next[j];//让j回退到next[j]继续匹配 }}return ans;
}

KMP算法优化

可以使用nextval数组进行优化,nextval[i]的含义可以理解为当模式串pattern的i+1位发生失配时,i应回退到的最佳位置。求解nextval数组的代码如下:

void getNextval(char s[],int len){int j=-1;nextval[0]=-1;for(int i=1;i<len;i++){while(j!=-1&&s[i]!=s[j+1]){j=nextval[j];}if(s[i]==s[j+1]){j++;}if(j==-1||s[i+1]!=s[j+1]){nextval[i]=j;}else{nextval[i]=nextval[j];}}
}

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/854404.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

麻了,5年Java竟然不知道幂等......

在分布式系统中&#xff0c;接口幂等性是确保操作一致性的关键特性。 啥是幂等性 幂等性 指的是在给定的条件下&#xff0c;无论操作执行多少次&#xff0c;其结果都保持不变。在接口设计中&#xff0c;幂等性意味着使用相同的参数多次调用接口&#xff0c;应产生与单次调用相…

STM32学习笔记(五)--TIM输出比较PWM详解

&#xff08;1&#xff09;配置步骤1.配置RCC外设时钟 开启GPIO以及TIM外设2.配置时基单元的时钟 包含时钟源选择配置初始化时基单元3.配置输出比较单元 包含CCR的值 输出比较模式 极性选择 输出使能等4.配置GPIO口 初始化为复用式推挽输出的配置5.运行控制 启动计数器 输出PWM…

Windows CSC服务特权提升漏洞(CVE-2024-26229)

文章目录 前言声明一、漏洞描述二、漏洞成因三、影响版本四、漏洞复现五、CVE-2024-26229 BOF六、修复方案 前言 Windows CSC服务特权提升漏洞。 当程序向缓冲区写入的数据超出其处理能力时&#xff0c;就会发生基于堆的缓冲区溢出&#xff0c;从而导致多余的数据溢出到相邻的…

QT 的文件

QT 和C、linux 一样&#xff0c;也有自带的文件系统. 它的操作和C、c差不多&#xff0c;不过也需要我们来了解一下。 输入输出设备类 QObject 有一个子类&#xff0c;名为 QIODevice 类&#xff0c;如其名字&#xff0c;该类是管理所有输入输出设备的类。 比如文件、网络套…

【ic-tool】timegen使用

一、前言 TimeGen是一个用于时序波形编辑的CAD工具&#xff0c;它允许数字设计工程师快速有效地绘制数字时序图。TimeGen时序图可以很容易地导出到其他窗口程序&#xff0c;如microsoftword&#xff0c;用于编写设计规范。可直接从官网下载TimeGEN软件&#xff1a;TimeGen Pro…

vue音乐播放条

先看效果 再看代码 <template><div class"footer-player z-30 flex items-center p-2"><div v-if"isShow" class"h-12 w-60 overflow-hidden"><div :style"activeStyle" class"open-detail-control-wrap&…

有什么可以创建ai聊天的软件?5个软件帮助你快速创建ai聊天

有什么可以创建ai聊天的软件&#xff1f;5个软件帮助你快速创建ai聊天 AI聊天软件是一种利用人工智能技术构建的聊天机器人系统&#xff0c;它能够模拟人类的对话方式&#xff0c;回答用户提出的问题或者进行对话。这类软件在各个领域都有广泛的应用&#xff0c;可以用于客户服…

【产品经理】订单处理5-可售库存管理

可售库存即销售库存&#xff0c;本文讲解订单处理过程中的可售库存的管理。 本次讲解订单处理过程中的可售库存的管理。 可售库存即销售库存&#xff0c;电商ERP中的可售库存共分三种&#xff1a;商品的可售现货库存、商品的预售库存以及赠品的可售库存。 一、商品的可售现货…

Python基础用法 之 转义字符

将两个字符进⾏转义 表示⼀个特殊的字符 \n ---> 换⾏&#xff0c;回⻋ \t ---> 制表符, tab键 注意&#xff1a; print( end\n)&#xff1a; print() 函数中默认有⼀个 end\n, 所以,每个 print 结束之后, 都会输出⼀ 个换行。 未完待续。

HTML中的资源提示关键词

渲染阻塞问题 之前在学习浏览器的渲染原理的时候我们就知道&#xff1a;因为浏览器一次只能开启一个渲染主线程&#xff0c;所以当浏览器解析到script标签时会停止DOM树的构建&#xff0c;转而去执行script&#xff0c;如果script中引用的是外部脚本&#xff0c;则浏览器会先从…

MySQL Server和Server启动程序(一)

MySQL Server mysqld&#xff0c;也称为MySQL Server&#xff0c;是一个单线程多任务的程序&#xff0c;它在MySQL安装中执行大部分工作。它不会生成额外的进程。MySQL Server管理对包含数据库和表的MySQL数据目录的访问。数据目录也是其他信息&#xff08;如日志文件和状态文…

目标检测:NMS代码

非极大值抑制NMS是目标检测常用的后处理算法&#xff0c;用于剔除冗余检测框 总体概要&#xff1a; 对NMS进行分类&#xff0c;大致可分为以下六种&#xff0c;这里是依据它们在各自论文中的核心论点进行分类&#xff0c;这些算法可以同时属于多种类别。 分类优先&#xff1a;…

专业学习|博弈论-课程沿革

学习来源&#xff1a;北京大学刘霖《博弈论》MOOC公开课 备注&#xff1a;仅做学习分享&#xff0c;请勿转载&#xff0c;转载必究&#xff01; &#xff08;一&#xff09;博弈论的预备知识 基本的微积分的知识和概率论的知识。简单的说会求导数&#xff0c;会求简单的积分&am…

消息队列-Rabbit运行机制

Producer(生产者) 和 Consumer(消费者) Producer(生产者) :生产消息的一方&#xff08;邮件投递者&#xff09;Consumer(消费者) :消费消息的一方&#xff08;邮件收件人&#xff09; 消息一般由 2 部分组成&#xff1a;消息头&#xff08;或者说是标签 Label&#xff09;和 …

【已解决】chrome视频无法自动播放的问题

问题&#xff1a; 在用datav开发大屏的时候&#xff0c;放了一个视频组件&#xff0c;但是发现视频组件即使设置了自动播放&#xff0c;仍然无法自动播放 原因&#xff1a; 76 以上版本的谷歌浏览器只能在系统静音下自动播放 解决&#xff1a; 音频自动播放浏览器白名单设置&…

kafka在windows上的启动

启动zookeeper 解压kafka安装包到对应目录下&#xff0c;找到对应config目录下的zookeeper.properties文件 新建一个data文件夹&#xff0c;随便放哪 打开该文件&#xff0c;找到 dataDir/tmp/zookeeper 属性 将原来的属性值&#xff0c;修改为新建data文件夹地址&#xff0c;…

如何修改倍福CX7000PLC IP地址

我们可以通过登录网页修改PLC的IP地址,这个需要我们知道PLC的初始IP地址 1、浏览器直接输入PLC 的IP地址 2、点击修改按钮(就是那个旋转) 修改IP地址前DHCP要先disable关闭 。 3、DHCP关闭 4、点击保存 5、在CAT3里搜索 在SYSTEM双击,之后点击搜索,具体过程可以参考下…

【html】如何利用id选择器实现主题切换

今天给大家介绍一种方法来实现主题切换的效果 效果图&#xff1a; 源码&#xff1a; <!DOCTYPE html> <html lang"zh"> <head><meta charset"UTF-8"><meta name"viewport" content"widthdevice-width, initia…

充电学习—5、healthed 电池服务

1、healthed服务监听接收内核kernel的电池事件&#xff0c;然后上传数据给framware层的batterysevice&#xff0c;BatteryService计算电池的电量&#xff0c;显示&#xff0c;绘制动画等 android电池系统框架&#xff1a; 2、healthd服务入口&#xff1a;android/system/cor…

2024年设计、数字化技术与新闻传播国际学术会议(ICDDTJ 2024)

2024年设计、数字化技术与新闻传播国际学术会议(ICDDTJ 2024) 2024 International Conference on Design, Digital Technology and Journalism 会议地点&#xff1a;哈尔滨&#xff0c;中国 网址&#xff1a;www.icddtj.com 邮箱: icddtjsub-conf.com 投稿主题请注明:ICDD…