当前位置: 首页 > news >正文

算法提升之字符串练习-02(字符串哈希)

今天给大家分享的是字符串中常会用的一种方法,叫做字符串哈希方法,相较于传统的前后缀找公共字符串,这种方法可以更好地理解。是通过将字符变为数值大小来进行判断是否包含前后缀。

例题分析

问题描述

小桥是一名喜欢研究文学的学生,最近她在研究一种名为“诗歌双联”的文学形式,它可以将两个诗句拼接在一起,形成新的诗句。为了更好地研究这种文学形式,小桥准备了两个字符串 s 和 t。

她发现,如果从字符串 s中选出两个长度为 k 的不相交子串,并将它们拼接在一起(不能改变相对顺序),可能会形成一个包含 t的字符串。为了验证这个想法,她想设计一种算法来检验是否可以这样做。

输入格式

第一行包含三个整数 n,m,k(2≤m≤2⋅k≤n≤104),表示字符串 s 和字符串 t 的长度,以及可选子串的长度。

接下来两行是由小写字母组成的字符串 s 和 t。

输出格式

如果可以选出的两个子串,使得拼接后得到的字符串包含 t,则输出 "Yes",否则输出 "No"。

输入案例:

7 4 3
baabaab
aaaa

输出案例:

Yes

代码部分:

#include<bits/stdc++.h>
using namespace std;
const int N=1e4+5;
typedef unsigned long long ull;
char s[N],t[N];
const int base=131;
ull bases[N],h1[N],h2[N];
int n,m,k;
ull gethash(int l,int r,ull s[]){return s[r]-s[l-1]*bases[r-l+1];}
int main()
{  cin>>n>>m>>k;cin>>s+1>>t+1;bases[0]=1;for(int i=1;i<=n;i++){bases[i]=bases[i-1]*base;h1[i]=h1[i-1]*base+(int)s[i];if(i<=m)h2[i]=h2[i-1]*base+(int)t[i];}for(int i=1;i+m-1<=n;i++)           //特判一下拆分t数组,一边把t数组全选的情况{if(gethash(i,i+m-1,h1)==gethash(1,m,h2)){cout<<"Yes"<<'\n';return 0;}}for(int i=1;i<m;i++)     //拆分t数组,选择两边有用的选的字符串长度,确保算哈希值的时候区间合法{if(i>k||m-i>k)continue;int l=k,r=n-k+1;while(l<r)           //确保没有重合,左边选择的字符放在前面{if(gethash(l-i+1,l,h1)==gethash(1,i,h2)&&gethash(r,r+m-i-1,h1)==gethash(i+1,m,h2)){cout<<"Yes"<<'\n';return 0;}if(gethash(l-i+1,l,h1)!=gethash(1,i,h2))l++;if(gethash(r,r+m-i-1,h1)!=gethash(i+1,m,h2))r--;}}cout<<"No"<<'\n';return 0;
}

这道题运用了字符串哈希的方法,其中核心是:

for(int i=1;i<m;i++)     //拆分t数组,选择两边有用的选的字符串长度,确保算哈希值的时候区间合法{if(i>k||m-i>k)continue;int l=k,r=n-k+1;while(l<r)           //确保没有重合,左边选择的字符放在前面{if(gethash(l-i+1,l,h1)==gethash(1,i,h2)&&gethash(r,r+m-i-1,h1)==gethash(i+1,m,h2)){cout<<"Yes"<<'\n';return 0;}if(gethash(l-i+1,l,h1)!=gethash(1,i,h2))l++;if(gethash(r,r+m-i-1,h1)!=gethash(i+1,m,h2))r--;}}
  • 拆分逻辑

    • t拆分为两部分:前i个字符(t1)和后m-i个字符(t2)。
    • 要求两部分长度都不超过k(因为每个子串长度最大为k)。
  • 双指针搜索

    • lk开始向右移动,rn-k+1开始向左移动。
    • 检查:
      • s中以l结尾的长度为i的子串是否等于t1
      • s中以r开头的长度为m-i的子串是否等于t2
      • 如果同时满足且两区间不相交(l < r),则输出 "Yes"。

2.理解l与m

一、先明确背景:拆分t为两部分

代码中通过i枚举拆分点,将t拆分为:

  • 前半部分t1t[1..i](长度i);
  • 后半部分t2t[i+1..m](长度m-i)。

目标是在s中找到:

  • 一个子串匹配t1
  • 另一个子串匹配t2
  • 两个子串长度均不超过k,且不相交。

二、gethash(l-i+1, l, h1) == gethash(1, i, h2) 解析

这部分用于判断s中的某个子串是否匹配t1t的前i个字符)。

参数含义:
  • h1:字符串s的哈希数组(存储s的前缀哈希值);
  • h2:字符串t的哈希数组(存储t的前缀哈希值);
  • 1, i, h2
    表示取t中从第1位到第i位的子串(即t1)的哈希值。
    对应t的前半部分:t[1]t[2]...t[i]
  • l-i+1, l, h1
    表示取s中从第l-i+1位到第l位的子串的哈希值。
    这个子串的长度是i(因为l - (l-i+1) + 1 = i),与t1长度相同,用于匹配t1
逻辑:

如果s[l-i+1, l]区间的子串哈希值,与t[1, i]区间的子串哈希值相等,则说明这两个子串完全匹配(忽略哈希冲突的小概率情况)。

三、gethash(r, r+m-i-1, h1) == gethash(i+1, m, h2) 解析

这部分用于判断s中的某个子串是否匹配t2t的后m-i个字符)。

参数含义:
  • i+1, m, h2
    表示取t中从第i+1位到第m位的子串(即t2)的哈希值。
    对应t的后半部分:t[i+1]t[i+2]...t[m],长度为m-i
  • r, r+m-i-1, h1
    表示取s中从第r位到第r+m-i-1位的子串的哈希值。
    这个子串的长度是(r+m-i-1) - r + 1 = m-i,与t2长度相同,用于匹配t2
逻辑:

如果s[r, r+m-i-1]区间的子串哈希值,与t[i+1, m]区间的子串哈希值相等,则说明这两个子串完全匹配。

四、为什么这样设计?

  1. 长度匹配

    • t1长度为i,因此s中匹配的子串长度也必须是il - (l-i+1) + 1 = i)。
    • t2长度为m-i,因此s中匹配的子串长度也必须是m-i(r+m-i-1) - r + 1 = m-i)。
  2. 位置约束

    • ls中匹配t1的子串的结束位置(方便后续确保两子串不相交)。
    • rs中匹配t2的子串的起始位置(确保l < r即可保证两子串不相交)。
  3. 哈希函数特性
    gethash(l, r, h)的定义是计算h数组中[l..r]区间的哈希值,因此参数必须严格对应子串的起止位置才能正确比较。

好了今天的分享就到这里,希望对大家能有所帮助。

http://www.xdnf.cn/news/1137367.html

相关文章:

  • 小红书获取关键词列表API接口详解
  • MongoDB 与MySQL 及es的区别
  • AllDup(重复文件查找)v4.5.70 便携版
  • 基于MATLAB和ZEMAX的光学传递函数与调制传递函数联合仿真
  • 初试Spring AI实现聊天功能
  • mysql——搭建MGR集群
  • 分布式分片策略中,分片数量的评估与选择
  • 基于单片机公交车报站系统/报站器
  • Jenkins Git Parameter 分支不显示前缀origin/或repo/
  • 2024年ASOC SCI2区TOP,基于干扰模型的灰狼优化算法IIE-GWO+复杂丘陵地形农业无人机轨迹规划,深度解析+性能实测
  • 医院各类不良事件上报,PHP+vscode+vue2+element+laravel8+mysql5.7不良事件管理系统源代码,成品源码,不良事件管理系统
  • 板凳-------Mysql cookbook学习 (十一--------12)
  • Python22 —— 标准库(random库)
  • Linux的Ext系列文件系统
  • 【JVM】深入理解 JVM 类加载器
  • 【推荐100个unity插件】使用C#或者unity实现爬虫爬取静态网页数据——Html Agility Pack (HAP)库和XPath 语法的使用
  • Java学习--JVM(2)
  • 学习C++、QT---27(QT中实现记事本项目实现行列显示、优化保存文件的功能的讲解)
  • 【Linux手册】缓冲区:深入浅出,从核心概念到实现逻辑
  • 数据结构:集合操作(Set Operations): 并集(Union)、交集(Intersection)、 差集(Difference)
  • 【37】MFC入门到精通——MFC中 CString 数字字符串 转 WORD ( CString, WORD/int 互转)
  • 编译原理第六到七章(知识点学习/期末复习/笔试/面试)
  • 【真·CPU训模型!】单颗i7家用本,4天0成本跑通中文小模型训练!Xiaothink-T6-mini-Preview 技术预览版开源发布!
  • 数据投毒技术之标签翻转
  • 题解:CF1829H Don‘t Blame Me
  • React Native 基础tabBar和自定义tabBar - bottom-tabs
  • 【开源软件推荐】 SmartSub,一个可以快速识别视频/音频字幕的工具
  • JavaScript进阶篇——第八章 原型链、深浅拷贝与原型继承全解析
  • 性能优化实践:Modbus 在高并发场景下的吞吐量提升(二)
  • 【Linux】第一个小程序—进度条