【数据结构与算法】Z算法(扩展KMP)(C++和Python写法)

news2024/11/17 14:47:04

Z算法(扩展KMP)

文章目录

  • Z算法(扩展KMP)
    • 朴素求法
    • 线性求法
    • 力扣类型题
      • 变种题:[3303. 第一个几乎相等子字符串的下标](https://leetcode.cn/problems/find-the-occurrence-of-first-almost-equal-substring/)

所谓Z算法,就是求一个字符串中,每个后缀子串和主串的前缀匹配字符数的数组,其也成为Z数组

eg:主串为aaaab(首位总为0,因为包含首位即本体,无意义)

  • aaaab aaab -> 3
  • aaaab aab -> 2
  • aaaab ab -> 1
  • aaaab b -> 0
  • 结果集[0, 3, 2, 1,0]

朴素求法

时间复杂度为O(n^2),暴力获取Z数组。

每次都从头匹配,如果符合往后++,不符合则返回,下一次又从头匹配。

vector<int> z_function_trivial_simple(string s)
{
    int n = (int)s.length();
    vector<int> z(n);
    for (int i = 1; i < n; ++i)
    {
        while (i + z[i] < n && s[z[i]] == s[i + z[i]])
            ++z[i];
    }
    return z;
}

线性求法

image-20240929233046116

我们使用一个滑动窗口[l,r],这个滑动窗口总是往右移动,我们可以称之为Z_box

这个z_box具有特性:s[l, r] = s[0, r-l](s为字符串,l和r总是从0开始)

我们再次复习一下z数组的含义:z[i]表示从s[i]开始直到末尾的子字符串和s整个字符串匹配的前缀和

问题一:如何获取这个滑动窗口?

由于滑动窗口(z_box)总是向右移动,所以我们要用z数组及i来辅助获取。

具体方法为:当i+z[i] -1 > r时,修改l和r的位置,是l = i , r = i + z[i] - 1

原因:1. 我们希望滑动窗口会比需要匹配的数字更靠后,或者说能够包含未来匹配的位置,并且滑动窗口总是往右的。

  1. i这里代表新窗口的起始位,z[i]代表匹配的长度, -1 是因为z[i]的数字里包含i的位置。

换句话说,所谓新的z_box就是更往右的匹配上的子串前缀。这么说可能比较抽象,请以下图例辅助理解:

image-20240929234003463

问题二:这个滑动窗口的具体作用?

这个滑动窗口只在i ∈[l, r]时发生作用。

我们以上图例作为一个例子,作为讲解:

  • 此时 i = 5 ,5包含在[4,6]中,而且刚好是中间

  • 因为 s[0,2] == s[4,6] ,那么z[5] 可以直接参考z[1]获取

    ​ == > 即z[i] = z[i - l]

  • 但这只是上图的可能性,因为上图中z[i-l] == 1 这个值小于r - i + 1 -> 6- 5 + 1 -> 2,我们已经知道了最多只能匹配到这里

但是!还有一种可能,就是z[i-1] == (r - i + 1),这种情况我们无法预测r后面是否可以继续匹配,那么我就需要从r的后一位开始匹配。而这种匹配方式则回到了原始的匹配中,不再进行讲解,但是这种情况我们依然可以省略已经处于滑动窗口中的匹配。

下面代码展示(如果还不理解:可以用这个网站模拟:演示Z函数)

C++ 代码

vector<int> z_function(string s)
{		
    vector<int> z(s.size(), 0);
    int l = 0, r = 0;
    for (int i = 1; i < s.size(); i++)
    {
        if (i <= r && z[i - l] < r - i + 1)
        {
            z[i] = z[i - l];
        }
        else 
        {
            z[i] = max(0, r - i + 1);
            // 从头开始暴力求解
            while (i + z[i] < s.size() && s[z[i]] == s[i + z[i]])
                ++z[i];
        }
        if (i + z[i] - 1 > r)
        {
            l = i, r = i + z[i] - 1;
        }
        // 可以打印进行看看
        cout << "i: "<< i << ", z[i]: "<< z[i] << ", [l, r]: ["<< l <<", " << r<<"]"<<endl;
    }
    return z;
}

Python代码

def getZArray(self, s : str) -> List[int]:
    # z[i] 为从i开始能和主串从头匹配的字符总数
    z = [0] * len(s)
    l, r = 0, 0
    for i in range(1, len(s)):
        # 当i在窗口内
        # 如果z[i-l] < (r-i+1),说明z[i-l]能匹配的字符数已经可知,直接获取
        # 否则,有可能超出这个数字,需要从末尾继续暴力寻找
        if i <= r:  # i在窗口内
            z[i] = min(z[i - l], r - i + 1)
        while i + z[i] < len(s) and s[z[i]] == s[i + z[i]]:  # 暴力匹配剩余部分
            z[i] += 1
        if i + z[i] - 1 > r:  # 更新窗口边界
            l, r = i, i + z[i] - 1
    return z

力扣类型题

变种题:3303. 第一个几乎相等子字符串的下标

这道题在Z算法的基础上,变形为前缀+后缀的组合,详情可以看这篇题解,写得很好,我不班门弄斧了。贴上我的代码。

C++

class Solution {
public:
	int minStartingIndex(string s, string pattern) {
		int m = pattern.size(), n = s.size();
		string combine = pattern + s;
		reverse(pattern.begin(), pattern.end());
		reverse(s.begin(), s.end());
		string combinervs = pattern + s;
		vector<int> pre = getZArray(combine);			// pre_l = z[m+l]
		vector<int> suf = getZArray(combinervs);		// suf_r = z[m+(n-r-1)]
		for (int l = 0, r = m - 1; r < n; l++, r++)
		{
			if (pre[m + l] + suf[m + (n - r - 1)] + 1 >= m)
				return l;
		}
		return -1;
	}

private:
	vector<int> getZArray(string& s)
	{
		vector<int> z(s.size(), 0);
		int l = 0, r = 0;
		for (int i = 1; i < s.size(); i++)
		{
			if (i <= r && z[i - l] < r - i + 1)
			{
				z[i] = z[i - l];
			}
			else 
			{
				z[i] = max(0, r - i + 1);
				while (i + z[i] < s.size() && s[z[i]] == s[i + z[i]])
					++z[i];
			}
			if (i + z[i] - 1 > r)
			{
				l = i, r = i + z[i] - 1;
			}
		}
		return z;
	}
};

Python

from typing import List

class Solution:
    def getZArray(self, s: str) -> List[int]:
        # z[i] 是从索引 i 开始的子串与主串前缀匹配的长度
        z = [0] * len(s)
        l, r = 0, 0
        for i in range(1, len(s)):
            if i <= r:  # i在窗口内
                z[i] = min(z[i - l], r - i + 1)
            while i + z[i] < len(s) and s[z[i]] == s[i + z[i]]:  # 暴力匹配剩余部分
                z[i] += 1
            if i + z[i] - 1 > r:  # 更新窗口边界
                l, r = i, i + z[i] - 1
        return z

    def minStartingIndex(self, s: str, pattern: str) -> int:
        m, n = len(pattern), len(s)
        
        # 生成前缀和后缀Z数组
        combined = pattern + s
        reversed_combined = pattern[::-1] + s[::-1]
        pre = self.getZArray(combined)
        suf = self.getZArray(reversed_combined)
        
        # 检查匹配位置
        for l in range(n - m + 1):
            r = l + m - 1
            if pre[m + l] + suf[m + (n - r - 1)] + 1 >= m:
                return l
        return -1

参考:

[1] Z函数(扩展KMP)

[2] 3303 第一个几乎相等子字符串的下标——题解

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

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

相关文章

[题解] Codeforces Round 976 (Div. 2) A ~ E

A. Find Minimum Operations 签到. void solve() {int n, k;cin >> n >> k;if (k 1) {cout << n << endl;return;}int ans 0;while (n) {ans n % k;n / k;}cout << ans << endl; }B. Brightness Begins 打表发现, 翻转完后的序列为: 0…

基于SSM+小程序的流浪动物领养管理系统(救助1)(源码+sql脚本+视频导入教程+文档)

&#x1f449;文末查看项目功能视频演示获取源码sql脚本视频导入教程视频 1、项目介绍 本系统功能为信息发布管理、领养记录管理、动物小圈管理、求助日报管理等。本系统的使用角色为管理员和用户&#xff0c;用户可以发布自己捡到的流浪动物、求领养信息、申请领养&#xff…

从零开发操作系统

没有操作系统 要考虑放到什么位置 org 07c00h 我用nasm&#xff08;汇编编译&#xff09; 放到7c00处 ibm兼容机 AX发生变化 -寄存器 不可能做存储 内存- 代码段数据段 if else --指令 代码 int a -数据段 必须告诉计算机代码段从哪里开始 改变cs寄存器里面的值可以改变推进寄…

【MYSQL】MYSQL约束

约束是在创建表的时候用的 1、概念 约束英文&#xff1a;constraint 约束实际上就是表中数据的限制条件 2、作用 表在设计的时候加入约束的目的就是为了保证表中的记录完整性和有效性&#xff0c;比如&#xff1a;用户表有些列的值&#xff08;手机号&#xff09;不能为空…

STM32自动下载电路分享及注意事项

文章目录 简介ISP下载启动配置 USB转串口芯片CH340C手动isp下载自动isp下载RTS、DTR电平变化分析注意事项 简介 在嵌入式开发中&#xff0c;使用STM32下载程序&#xff0c;可以通过仿真器下载&#xff0c;也可以通过串口下载。在stm32串口下载时&#xff0c;我们需要手动配置启…

gradle的入门及kotlin的了解

gradle项目创建方式 1.idea springboot initalizer 2.命令行 gradle目录结构 gradle命令 gradle wrapper 一个解决不同项目需要不同版本gradle的问题 比如&#xff0c;对方电脑没用安装gradle 对方电脑安装了gradle&#xff0c;但是版本太旧了 于是&#xff0c;在项目根目…

Qt 学习第十一天:QTableWidget 的使用

一、创建QTableWidget对象&#xff0c;设置大小&#xff0c;在窗口的位置 //创建tablewidgetQTableWidget *table new QTableWidget(this);table->resize(550, 300);table->move(100, 100); //移动 二、设置表头 //设置表头QStringList headerList; //定义headerList…

Webpack 特性探讨:CDN、分包、Tree Shaking 与热更新

文章目录 前言包准备CDN 集成代码分包Tree Shaking原理实现条件&#xff1a;解决 treeShaking 无效方案&#xff1a;示例代码&#xff1a; 热更新&#xff08;HMR&#xff09; 前言 Webpack 作为现代前端开发中的核心构建工具&#xff0c;提供了丰富的特性来帮助开发者优化和打…

63.5 注意力提示_by《李沐:动手学深度学习v2》pytorch版

系列文章目录 文章目录 系列文章目录注意力提示生物学中的注意力提示查询、键和值注意力的可视化使用 show_heatmaps 显示注意力权重代码示例 代码解析结果 小结练习 注意力提示 &#x1f3f7;sec_attention-cues 感谢读者对本书的关注&#xff0c;因为读者的注意力是一种稀缺…

Python 读取与处理出入库 Excel 数据实战案例(HTML 网页展示)

有如下数据&#xff0c;需要对数据合并处理&#xff0c;输出到数据库。 数据样例&#xff1a;&#x1f447; excel内容&#xff1a; 出入库统计表河北库.xlsx: 出入库统计表天津库.xlsx: 01实现过程 1、创建test.py文件&#xff0c;然后将下面代码复制到里面&#xff0c;最后…

original多因子图绘制

成品参考 首先导入数据 设置过程 设置X轴 设置图 双击空白部分设置图层宽度&#xff08;也需要设置高度&#xff09; 颜色配置 1.删除边框 合适的参数与颜色&#xff08;设置为单色&#xff09;

PDF转换为TIF,JPG的一个简易工具(含下载链接)

目录 0.前言&#xff1a; 1.工具目录 2.工具功能&#xff08;效果&#xff09;&#xff0c;如何运行 效果 PDF转换为JPG&#xff08;带颜色&#xff09; PDF转换为TIF&#xff08;LZW形式压缩&#xff0c;可以显示子的深浅&#xff09; PDF转换为TIF&#xff08;CCITT形…

Squaretest单元测试辅助工具使用

1、idea安装插件 Squaretest 然后关掉idea 2、安装字节码软件&#xff08;jclasslib&#xff09; 3、找到idea里面的Squaretest安装目录 找到包含TestStarter的jar包 4、打开 com.squaretest.c.f 打开后选择常量池 5、找到第16个修改 Long value值&#xff0c;修改的数字即为使…

英飞凌 PSoC6 评估板 Wi-Fi 无线通信

PSoC™ 62 with CAPSENSE™ evaluation kit 开发板&#xff08;以下简称 PSoC 6 RTT 开发板&#xff09;是英飞凌&#xff08;Infineon&#xff09;联合 RT-Thread 发布一款面向物联网开发者的 32 位双核 MCU 开发套件&#xff0c;其默认内置 RT-Thread 物联网操作系统。本文主…

[RabbitMQ] 7种工作模式详细介绍

&#x1f338;个人主页:https://blog.csdn.net/2301_80050796?spm1000.2115.3001.5343 &#x1f3f5;️热门专栏: &#x1f9ca; Java基本语法(97平均质量分)https://blog.csdn.net/2301_80050796/category_12615970.html?spm1001.2014.3001.5482 &#x1f355; Collection与…

【Linux】如何用shell脚本一键安装Java和Maven环境

Shell脚本安装环境 前言脚本Java安装脚本使用方式 Java卸载脚本Maven安装脚本Maven卸载脚本 前言 无论是在云服务器上部署Java项目 还是在本地的Linux虚拟机上运行Java项目 都需要Java的环境 设置环境则需要一些繁琐的操作 为了简化并复用这些操作 我们可以封装这些操作为一个…

AD导出gerber文件(光绘文件)

第一步&#xff1a; 英寸 2:5 勾选你想显示的层 默认默认 第二步&#xff1a; 第三步&#xff1a; 默认

开关电源为什么要进行负载测试,负载测试都包含哪些项目?

开关电源在现代电子设备中占据着重要的地位&#xff0c;其性能的稳定性和可靠性直接影响着电子设备的正常运行。为了确保开关电源的质量&#xff0c;需要对其进行负载测试。负载测试可以模拟实际工作环境中的负载情况&#xff0c;检测开关电源在不同负载条件下的输出特性、稳定…

如何创建虚拟环境并实现目标检测及验证能否GPU加速

创建虚拟环境&#xff1a; 先创建一个虚拟python环境&#xff0c;敲如下代码 然后再到该虚拟环境里面安装自己想要的包 激活虚拟环境 然后再聚类训练这些 验证GPU加速 阿里源 pip install torch torchvision -i http://mirrors.aliyun.com/pypi/simple/ --trusted-host mir…

Python的风格应该是怎样的?除语法外,有哪些规范?

写代码不那么pythonic风格的&#xff0c;多多少少都会让人有点难受。 什么是pythonic呢&#xff1f;简而言之&#xff0c;这是一种写代码时遵守的规范&#xff0c;主打简洁、清晰、可读性高&#xff0c;符合PEP 8&#xff08;Python代码样式指南&#xff09;约定的模式。 Pyth…