暴力匹配或KMP算法解决字符串匹配问题

news2024/12/27 18:51:19

字符串匹配问题

  • 1. 字符串匹配问题
  • 2. 解决方案
    • 2.1 暴力匹配算法
      • 2.1.1 算法步骤
      • 2.1.2 代码实现
    • 2.2 KMP算法
      • 2.2.1 算法步骤
      • 2.2.2 next数组计算
      • 2.2.2 代码实现
  • 3. 真题
    • 3.1 力扣 28. 找出字符串中第一个匹配项的下标
    • 3.2 力扣 459. 重复的子字符串
    • 3.3 NC149 kmp算法
    • 3.4 KMP算法

1. 字符串匹配问题

  • 给定字符串S,和字符串T,查询T在S中出现的位置即为字符串匹配问题;
    在这里插入图片描述

2. 解决方案

2.1 暴力匹配算法

2.1.1 算法步骤

  • 使用双指针,指针i指向S串进行遍历,指针j指向T进行遍历;
  • 一旦某时刻S[I]!=T[j],则说明当前子串不匹配,需要重新开始匹配,即i=i-j+1(S中匹配操作新的起始位置),j=0(T从头开始匹配);
  • 当j=T.length时说明匹配成功;
    在这里插入图片描述

2.1.2 代码实现

package com.northsmile.string;

/**
 * @author NorthSmile
 * @version 1.0
 * @date 2023/8/22&1:20
 * 暴力匹配算法解决字符串匹配问题
 */
public class StrMatch {
    public static void main(String[] args) {
        String s="abdbcabcdef";
        String t="abc";
        System.out.println(match(s,t));
    }

    public static int match(String s,String t){
        if (t.length()>s.length()){
            return -1;
        }
        if (s.equals(t)){
            return 0;
        }
        int i=0,j=0;
        while (i<s.length()&&j<t.length()){
            if (s.charAt(i)==t.charAt(j)){
                i++;
                j++;
            }else{
                i=i-j+1;
                j=0;
            }
        }
        return j==t.length()?i-j:-1;
    }
}

2.2 KMP算法

  • 暴力匹配算法缺点:匹配过程中,一旦匹配失败,文本串要将当前匹配起点+1作为新的起点,在文本串和模式串长度较大时,性能比较低下;
  • 使用KMP算法进行字符串匹配,通过利用字符串的前缀和后缀的最长公共子串,降低不必要的无效匹配操作,可提升匹配速度;
    在这里插入图片描述

2.2.1 算法步骤

在这里插入图片描述

2.2.2 next数组计算

  • 计算每个位置对应的前缀与后缀最大公共长度,得到最大公共长度表;
  • 将最大长度均右移一位,用-1填充第一个位置(如果第一个位置要求0开头,则将此事的next数组元素均+1即可);
    在这里插入图片描述
    在这里插入图片描述

2.2.2 代码实现

package com.northsmile.string;

import java.util.Arrays;

/**
 * @author NorthSmile
 * @version 1.0
 * @date 2023/8/22&1:20
 * KMP算法
 * 目的:i不回退,j回退到特定的位置
 */
public class KMP{
    public static void main(String[] args) {
        String str="abdbcabcdef";
        String pattern="abc";
//        String pattern="abcababcabc";
//        String str="BBC ABCDAB ABCDABCDABDE";
//        String pattern="ABCDABD";
//        String pattern="AAAB";
        System.out.println(Arrays.toString(calNext(pattern)));
        System.out.println(match(str,pattern,0));
    }

    /**
     * 从str的pos位置查找pattern
     * @param str
     * @param pattern
     * @param pos
     * @return
     */
    public static int match(String str,String pattern,int pos){
        if (str==null||pattern==null){
            return -1;
        }
        if (pattern.length()>str.length()){
            return -1;
        }
        if (pos<0||pos>=pattern.length()){
            return -1;
        }
        if (str.equals(pattern)){
            return 0;
        }
        int[] next=calNext(pattern);
        // i指向文本串,j指向模式串
        int i=pos,j=0;
        while (i<str.length()&&j<pattern.length()){
        	// j=-1表示两个串第一个字符就不匹配
            if ((j==-1)||str.charAt(i)==pattern.charAt(j)){
                i++;
                j++;
            }else{
                // 回退j
                j = next[j];
            }
        }
        return j==pattern.length()?i-j:-1;
    }

    // 字符串对应next数组的计算
    public static int[] calNext(String str){
        int n=str.length();
        int[] next=new int[n];
        next[0]=-1;
        next[1]=0;
        for (int i=2,k=next[1];i<n;i++){
        	// k=-1表示前缀和后缀没有公共串
            if (k==-1||str.charAt(i-1)==str.charAt(k)){
                next[i]=k+1;
                k=next[i];
            }else{
                k=next[k];
                i--;
            }
        }
        return next;
    }
}



3. 真题

3.1 力扣 28. 找出字符串中第一个匹配项的下标

class Solution {
    public int strStr(String haystack, String needle) {
        return kmp(haystack,needle);
    }

    public int kmp(String str, String pattern){
        if(str==null||pattern==null){
            return -1;
        }
        if(str.length()==0||pattern.length()==0){
            return -1;
        }
        if(pattern.length()>str.length()){
            return -1;
        }
        if(pattern.equals(str)){
            return 0;
        }
        // 计算模式串的next数组
        int[] next=getNext(pattern);
        // 匹配查找
        int i=0,j=0;
        while(i<str.length()&&j<pattern.length()){
            if(j==-1||str.charAt(i)==pattern.charAt(j)){
                i++;
                j++;
            }else{
                // j回退
                j=next[j];
            }
        }
        return j==pattern.length()?i-j:-1;
    }

    public int[] getNext(String str){
        int n=str.length();
        if(n==1){
            return new int[]{-1};
        }
        if(n==2){
            return new int[]{-1,0};
        }
        int[] next=new int[n];
        next[0]=-1;
        next[1]=0;
        // k用于记录i-1位置需要回退的位置
        int i=2,k=0;
        while(i<n){
            if(k==-1||str.charAt(i-1)==str.charAt(k)){
                next[i]=k+1;
                k++;
                i++;
            }else{
                // k回退
                k=next[k];
            }
        }
        return next;
    }
}

3.2 力扣 459. 重复的子字符串

class Solution {
    // 字符串匹配
    public boolean repeatedSubstringPattern(String s) {
        return kmp(s+s,s,1)!=s.length();
    }

    public int kmp(String str, String pattern,int pos){
        if(str==null||pattern==null){
            return -1;
        }
        if(str.length()==0||pattern.length()==0){
            return -1;
        }
        if(pattern.length()>str.length()){
            return -1;
        }
        if(pattern.equals(str)){
            return 0;
        }
        // 计算模式串的next数组
        int[] next=getNext(pattern);
        // 匹配查找
        int i=pos,j=0;
        while(i<str.length()&&j<pattern.length()){
            if(j==-1||str.charAt(i)==pattern.charAt(j)){
                i++;
                j++;
            }else{
                // j回退
                j=next[j];
            }
        }
        return j==pattern.length()?i-j:-1;
    }

    public int[] getNext(String str){
        int n=str.length();
        if(n==1){
            return new int[]{-1};
        }
        if(n==2){
            return new int[]{-1,0};
        }
        int[] next=new int[n];
        next[0]=-1;
        next[1]=0;
        // k用于记录i-1位置需要回退的位置
        int i=2,k=0;
        while(i<n){
            if(k==-1||str.charAt(i-1)==str.charAt(k)){
                next[i]=k+1;
                k++;
                i++;
            }else{
                // k回退
                k=next[k];
            }
        }
        return next;
    }
}

3.3 NC149 kmp算法

import java.util.*;


public class Solution {
    /**
     * 代码中的类名、方法名、参数名已经指定,请勿修改,直接返回方法规定的值即可
     *
     * 计算模板串S在文本串T中出现了多少次
     * @param S string字符串 模板串
     * @param T string字符串 文本串
     * @return int整型
     */
    static int count=0;
    public int kmp (String S, String T) {
        kmp(T,S,0);
        return count;
    }
    
    public void kmp (String s, String p,int pos) {
        if(s==null||p==null){
            return;
        }
        if(s.length()==0||p.length()==0){
            return;
        }
        if(pos<0||pos>=s.length()){
            return;
        }
        int[] next=getNext(p);
        int i=pos,j=0;
        while(i<s.length()&&j<p.length()){
            if(j==-1||s.charAt(i)==p.charAt(j)){
                i++;
                j++;
            }else{
                j=next[j];
            }
            if(j==p.length()){
                count++;
                j=next[j];
            }
        }
    }

    public int[] getNext(String s){
        int n=s.length();
        if(n==1){
            return new int[]{-1};
        }
        if(n==2){
            return new int[]{-1,0};
        }
        int[] next=new int[n+1];
        next[0]=-1;
        next[1]=0;
        int i=2,k=0;
        while(i<=n){
            if(k==-1||s.charAt(i-1)==s.charAt(k)){
                next[i]=k+1;
                k++;
                i++;
            }else{
                k=next[k];
            }
        }
        return next;
    }
}

3.4 KMP算法

import java.util.*;

// 注意类名必须为 Main, 不要有任何 package xxx 信息
public class Main {
    public static void main(String[] args) {
        Scanner in = new Scanner(System.in);
        // 注意 hasNext 和 hasNextLine 的区别
        while (in.hasNextLine()) { // 注意 while 处理多个 case
            String str = in.nextLine();
            String match = in.nextLine();
            List<Integer> ans=kmp(str,match,0);
            if(ans.size()==0){
                System.out.println(-1);
                return;
            }
            for(int i=0;i<ans.size();i++){
                System.out.print(ans.get(i));
                if(i!=ans.size()-1){
                    System.out.print(" ");
                }else{
                    System.out.println();
                }
            }
        }
    }

    public static List<Integer> kmp (String s, String p,int pos) {
        List<Integer> ans=new ArrayList<>();
        if(s==null||p==null){
            return ans;
        }
        if(s.length()==0||p.length()==0){
            return ans;
        }
        if(pos<0||pos>=s.length()){
            return ans;
        }
        int[] next=getNext(p);
        int i=pos,j=0;
        while(i<s.length()&&j<p.length()){
            if(j==-1||s.charAt(i)==p.charAt(j)){
                i++;
                j++;
            }else{
                j=next[j];
            }
            if(j==p.length()){
                ans.add(i-j);
                j=next[j];
            }
        }
        return ans;
    }

    public static int[] getNext(String s){
        int n=s.length();
        if(n==1){
            return new int[]{-1,0};
        }
        int[] next=new int[n+1];
        next[0]=-1;
        next[1]=0;
        int i=2,k=0;
        while(i<=n){
            if(k==-1||s.charAt(i-1)==s.charAt(k)){
                next[i]=k+1;
                k++;
                i++;
            }else{
                k=next[k];
            }
        }
        return next;
    }
}

参考链接https://www.zhihu.com/question/21923021/answer/769606119,这位博主关于next数组计算讲的很清晰!

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

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

相关文章

opencv进阶18-基于opencv 决策树导论

1. 什么是决策树&#xff1f; 决策树是最早的机器学习算法之一&#xff0c;起源于对人类某些决策过程 的模仿&#xff0c;属于监督学习算法。 决策树的优点是易于理解&#xff0c;有些决策树既可以做分类&#xff0c;也可以做回归。在排名前十的数据挖掘算法中有两种是决策树[1…

火山引擎发布自研视频编解码芯片

2023年8月22日&#xff0c;火山引擎视频云宣布其自研的视频编解码芯片已成功出片。经验证&#xff0c;该芯片的视频压缩效率相比行业主流硬件编码器可提升30%以上&#xff0c;未来将服务于抖音、西瓜视频等视频业务&#xff0c;并将通过火山引擎视频云开放给企业客户。 火山引…

【附安装包】Tecplot 360 EX2022安装教程

软件下载 软件&#xff1a;Tecplot 360版本&#xff1a;2022语言&#xff1a;英文大小&#xff1a;388.97M安装环境&#xff1a;Win11/Win10/Win8/Win7硬件要求&#xff1a;CPU2.5GHz 内存4G(或更高&#xff09;下载通道①百度网盘丨64位下载链接&#xff1a;https://pan.baid…

SQL注入之报错注入

文章目录 报错注入是什么&#xff1f;报错注入获取cms账号密码成功登录 报错注入是什么&#xff1f; 在注入点的判断过程中&#xff0c;发现数据库中SQL 语句的报错信息&#xff0c;会显示在页面中&#xff0c;因此可以利用报错信息进行注入。 报错注入的原理&#xff0c;就是在…

uniapp 使用permission获取录音权限

使用前&#xff0c;需要先配置权限 android.permission.RECORD_AUDIO

wazhu配置以及漏洞复现

目录 1.wazhu配置 进入官网下载 部署wazhu 修改网络适配器 重启 本地开启apache wazhu案例复现 前端页面 执行 1.wazhu配置 进入官网下载 Virtual Machine (OVA) - Installation alternatives (wazuh.com) 部署wazhu 修改网络适配器 重启 service network restart 本地…

Datawhale AI夏令营 - 用户新增预测挑战赛 | 学习笔记

数据分析与可视化 为了拟合出更好的结果就要了解训练数据之间的相互关系&#xff0c;进行数据分析是必不可少的一步 导入必要的库 # 导入库 import pandas as pd import numpy as np import matplotlib.pyplot as plt import seaborn as sns pandas库是一个强大的分析结构化…

Python框架【自定义过滤器、自定义数据替换过滤器 、自定义时间过滤器、选择结构、选择练习、循环结构、循环练习、导入宏方式 】(三)

&#x1f44f;作者简介&#xff1a;大家好&#xff0c;我是爱敲代码的小王&#xff0c;CSDN博客博主,Python小白 &#x1f4d5;系列专栏&#xff1a;python入门到实战、Python爬虫开发、Python办公自动化、Python数据分析、Python前后端开发 &#x1f4e7;如果文章知识点有错误…

【OpenMp】openmp库的介绍及安装

目录 什么是OpenMPOpenMP的工作原理Amdahl 定律Gustafson 定律openmp的库在ubuntu下的编译 什么是OpenMP OpenMP&#xff08;Open Multi-Processing&#xff09;是一种并行编程框架&#xff0c;用于在共享内存系统中实现并行计算。它是GCC编译器的一部分&#xff0c;提供了一组…

向量检索:基于ResNet预训练模型构建以图搜图系统

1 项目背景介绍 以图搜图是一种向量检索技术&#xff0c;通过上传一张图像来搜索并找到与之相关的其他图像或相关信息。以图搜图技术提供了一种更直观、更高效的信息检索方式。这种技术应用场景和价值非常广泛&#xff0c;经常会用在商品检索及购物、动植物识别、食品识别、知…

leetcode48. 旋转图像(java)

旋转图像 题目描述旋转技巧上期经典算法 题目描述 难度 - 中等 原题链接 - 旋转图像 给定一个 n n 的二维矩阵 matrix 表示一个图像。请你将图像顺时针旋转 90 度。 你必须在 原地 旋转图像&#xff0c;这意味着你需要直接修改输入的二维矩阵。请不要 使用另一个矩阵来旋转图像…

StreamPark

1、StreamPark的标语 一个神奇的框架&#xff0c;让流处理更简单 2、StreamPark的前世今生 早期用名streamx&#xff0c;加入apache孵化器之后更名为StreamPark 3、StreamPark可以为你提供什么 降低学习成本、开发门槛&#xff0c;让开发者只用关心核心的业务 简单来说&#xf…

Pytorch学习:torchvison.transforms常用包(ToTensor、Resize、Compose和RandomCrop)

transforms常用包 1. torchvision.transforms.ToTensor2. torchvision.transforms.Resize3. torchvision.transforms.Compose4. torchvision.transforms.Normalize5. torchvision.transforms.RandomCrop 1. torchvision.transforms.ToTensor 将PIL Image或ndarray转换为张量并…

基于React实现无限滚动的日历详细教程,附源码【手写日历教程第二篇】

前言 最常见的日历大部分都是滚动去加载更多的月份&#xff0c;而不是让用户手动点击按钮切换日历月份。滚动加载的交互方式对于用户而言是更加丝滑和舒适的&#xff0c;没有明显的操作割裂感。 那么现在需要做一个这样的无限滚动的日历&#xff0c;前端开发者应该如何去思考…

【数据结构】实现栈和队列

目录 一、栈1.栈的概念及结构&#xff08;1&#xff09;栈的概念&#xff08;2&#xff09;栈的结构 2.栈的实现&#xff08;1&#xff09;类型和函数的声明&#xff08;2&#xff09;初始化栈&#xff08;3&#xff09;销毁&#xff08;4&#xff09;入栈&#xff08;5&#x…

在 Redis 中处理键值 | Navicat

Redis 是一个键值存储系统&#xff0c;允许我们将值与键相关联起来。与关系型数据库不同的是&#xff0c; 在Redis 中&#xff0c;不需要使用数据操作语言 &#xff08;DML&#xff09; 和查询语法&#xff0c;那么我们如何进行数据的写入、读取、更新和删除操作呢&#xff1f;…

shell 11(shell重定向输入输出)

一、标准输入输出 标准输入介绍 从键盘读取用户输入的数据&#xff0c;然后再把数据拿到Shell程序中使用; 标准输出介绍 Shell程序产生的数据&#xff0c;这些数据一般都是呈现到显示器上供用户浏览查看

go学习一之go的初体验

go语言学习笔记 一、golang初体验: 1.简单体验案例&#xff1a; package main{ //把这个test.go归属到main import "fmt" //引入一个包 func main(){//输出hellofmt.Println("hello world")} }2.从案例学到的知识点&#xff1a; (1) go文件的后缀是.…

【集合学习HashMap】HashMap集合详细分析

HashMap集合详细分析 一、HashMap简介 HashMap 主要用来存放键值对&#xff08;key-value的形式&#xff09;&#xff0c;它基于哈希表的 Map 接口实现&#xff0c;是常用的 Java 集合之一&#xff0c;是非线程安全的。 HashMap 可以存储 null 的 key 和 value&#xff0c;但 …

nginx基本介绍(安装、常用命令、反向代理)

文章目录 引言一、nginx是什么二、nginx的下载和安装1. 下载2. windows下安装3. 运行4. 外部服务器无法访问问题 三、nginx的常用命令四、nginx.config五、FileZilla1. 什么是FileZilla2. FileZilla的下载和安装 六、反向代理1. 什么是nginx的反向代理2. 反向代理工作流程3. 如…