数据结构与算法 -- 字符串匹配常用的Sunday算法

1 前言

本文首发于掘金,有意向转发的同行可私信,想在Android领域有所进步的伙伴,可微信搜索个人的公众号「Android技术集中营」或扫码添加,有不定时福利等大家来拿。

2 Sunday算法

如果各位伙伴们在刷题的时候,碰到关于字符串匹配的问题,在不考虑时间复杂度的情况下,使用暴力匹配这种方式可以搞定,但是一旦在时间复杂度上有了硬性的要求,就需要考虑使用一些魔法了,常见的魔法有KMP算法、Sunday算法,如果网络上搜索KMP算法,大部分文章显得晦涩难懂,一旦出现这种症状,一定是记住了马上忘记, 所以本着打不过"不加入"的原则,我们介绍一下Sunday算法,这个算法效率同样很高,而且比KMP算法能够更加易懂。

题目

给你两个字符串 haystackneedle ,请你在 haystack 字符串中找出 needle 字符串的第一个匹配项的下标(下标从 0 开始)。如果 needle 不是 haystack 的一部分,则返回 -1

示例 1:

ini 复制代码
输入: haystack = "sadbutsad", needle = "sad"
输出: 0
解释: "sad" 在下标 0 和 6 处匹配。
第一个匹配项的下标是 0 ,所以返回 0 。

示例 2:

arduino 复制代码
输入: haystack = "leetcode", needle = "leeto"
输出: -1
解释: "leeto" 没有在 "leetcode" 中出现,所以返回 -1 。

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

这道题,没有时间复杂度的要求,属于1🌟难度的问题,显然我们可以使用暴力匹配的方式来完成。

思路

needle属于模版,需要在haystack中找到与其匹配的第一个匹配项的下标,那么我们可以定义一个成员变量index,默认值为-1,如果没有找到匹配项,那么直接返回index;如果找到了匹配项,那么就将第一个匹配项的下标赋值给index

怎么找呢?从haystack第一个字符开始跟needle第一个字符开始匹配,如果匹配上了,记下当前位置index,开始往后比,如果完全匹配,那么返回index;如果没有匹配上,那么就从index的下一个位置重新匹配。

java 复制代码
public int strStr(String haystack, String needle) {
        if(haystack.length() < needle.length()){
            //一定匹配不上,直接返回-1;
            return -1;
        }

        //这个值记录开始匹配字符串时的下标
        int index = -1;
        int haystackLength = haystack.length();
        int needleLength = needle.length();

        //这个参数记录匹配到了字符串的哪个位置
        int strPos = 0;
        //记录匹配模版的位置
        int needleIndex = 0;

        while(strPos < haystackLength){

            if(index == -1){
                if(haystack.charAt(strPos) == needle.charAt(0)){
                    //与needle第一个字符串匹配上了,意味着这个点,可能会是最终结果
                    index = strPos;
                    //即便是匹配上了,但是如果剩余的长度不足以支持needle整体长度判断,也是-1
                    if(haystackLength - strPos  < needleLength){
                        index = -1;
                        break;
                    }
                }else{
                    //如果一直都没匹配上,继续往下走
                    strPos++;
                }
            }else{

                //正常开始匹配
                if(needleIndex == needleLength){
                    //此时整个needle都匹配完成了,说明找到了
                    break;
                }

                if(haystack.charAt(strPos) != needle.charAt(needleIndex)){
                    //没匹配上,重新计数
                    strPos = index+1;
                    needleIndex = 0;
                    index = -1;
                }else{
                    //如果与模版一致,就继续往下比较
                    strPos++;
                    needleIndex++;
                }
            }

        }

        return index;
    }

这里是通过双指针strPosneedleIndex分别记录比较字符串时所处于字符串的位置,而且在遍历字符串时,需要回到字符串的某个位置,因此采用了while循环的方式代替for循环,在匹配的过程中,index是一个风向标,一旦匹配失败,就需要回到haystackindex + 1的位置,继续往下匹配。

这个方法中有一些特殊的情况,需要处理,如果有疑问的伙伴可以评论区留言。

2.2 Sunday算法

前面我们在2.1 小结中,使用了暴力算法实现了字符串的匹配,其中时间复杂度O(n)=m*n,空间复杂度为O(1)。

如果对时间复杂度有了硬性要求,那么这个方法即便是能拿到最终的结果,也是不合理的,这时就需要Sunday算法来实现了。

2.2.1 暴力匹配存在的问题

在2.1小节中,我们通过暴力算法的整个流程是这样的:

此时在strPos位置时,发现t和u没有匹配上,此时需要将index移到a的位置,继续匹配。

那么既然遍历到t的时候,前面有什么元素其实已经知道了,能不能不退回去再重新匹配呢? 所以Sunday算法的出现,解决了此问题,只通过一次遍历便可以拿到匹配到的字符串。

2.2.2 Sunday算法的精髓

  • 与暴力匹配一致,从头开始遍历,直到找到与子串首字母匹配的位置,开始逐个匹配,如果出现不匹配的情况,

例如t和u不匹配,那么就看主串不匹配字符t的下一个字符y,判断y是否在子串中,显然不在,那么直接跳过y,将指针strPos移动到y的下一个位置a。

  • 由于移动之后,第一个字符串就不匹配,相当于匹配失败,依然按照上一步原则,看主串a的下一个字符u,因为u是在子串中的倒数第一的位置 ,因此指针向右移动1位。 此时匹配成功了。

注意这里的规则,是要看字符在子串中倒数的位置,如果子串中有重复出现的字符,那么就需要获取到最后出现这个字符的位置。

java 复制代码
public int strStr(String haystack, String needle) {
        if(haystack.length() < needle.length()){
            //一定匹配不上,直接返回-1;
            return -1;
        }

        //这个值记录开始匹配字符串时的下标
        int index = -1;
        int haystackLength = haystack.length();
        int needleLength = needle.length();

        //这个参数记录匹配到了字符串的哪个位置
        int strPos = 0;
        //记录匹配模版的位置
        int needleIndex = 0;

        //使用Sunday算法
        while(strPos < haystackLength){

            if(index == -1){
                //此时没有匹配成功
                if(haystack.charAt(strPos) == needle.charAt(0)){
                    //匹配到了,会进入到else逻辑
                    index = strPos;

                    //即便是匹配到了,但是长度已经不足以后续的匹配
                    if(haystackLength - strPos < needleLength){
                        index=-1;
                        break;
                    }
                }else{
                    strPos++;
                }

            }else{

                if(needleIndex == needleLength){
                    break;
                }

                //正在匹配中
                if(haystack.charAt(strPos) != needle.charAt(needleIndex)){

                    if(index + needleLength >= haystackLength){
                        //说明此时肯定不会匹配了
                        index = -1;
                        break;
                    }

                    char nextVal = haystack.charAt(index+needleLength);
                    if(needle.contains(String.valueOf(nextVal))){
                        //如果存在,查出来这个字符在子串中的位置
                        //正向的位置,如果子串中存在重复的元素,拿到最后一个元素
                        int position = indexOfChar(needle,nextVal);

                        index = index + needleLength - position;
                        strPos = index;
                        index = -1;
                        needleIndex = 0;
                    }else{
                        //如果不存在这个字符,直接跳过
                        index = index + needleLength + 1;
                        strPos = index;
                        index = -1;
                        needleIndex = 0;
                    }
                }else{
                    strPos++;
                    needleIndex++;
                }
            }

        }
         return index;
    }

    public int indexOfChar(String str,char ch){
        char[] chars = str.toCharArray();
        int result = 0;
        for(int i = chars.length-1;i>=0;i--){
            if(chars[i] == ch){
                result = i;
                break;
            }
        }
        return result;
    }

其实唯一逻辑的变动,就是在使用暴力算法的时候,如果在匹配的过程中,发现无法匹配,那么就会将主串的strPos进行回溯,回退到index的下一个坐标位置 , 而使用Sunday算法,则是在匹配失败之后,根据规则判断可以跳过几个字符,而不需要回溯,仅需要一次遍历即可。

当然这个算法中存在的不足之处在于,需要通过计算某个字符在字符串中的位置,这个是需要优化的点。

相关推荐
sp_fyf_202418 分钟前
计算机前沿技术-人工智能算法-大语言模型-最新研究进展-2024-11-01
人工智能·深度学习·神经网络·算法·机器学习·语言模型·数据挖掘
香菜大丸38 分钟前
链表的归并排序
数据结构·算法·链表
jrrz082839 分钟前
LeetCode 热题100(七)【链表】(1)
数据结构·c++·算法·leetcode·链表
oliveira-time1 小时前
golang学习2
算法
南宫生2 小时前
贪心算法习题其四【力扣】【算法学习day.21】
学习·算法·leetcode·链表·贪心算法
懒惰才能让科技进步2 小时前
从零学习大模型(十二)-----基于梯度的重要性剪枝(Gradient-based Pruning)
人工智能·深度学习·学习·算法·chatgpt·transformer·剪枝
Ni-Guvara3 小时前
函数对象笔记
c++·算法
泉崎3 小时前
11.7比赛总结
数据结构·算法
你好helloworld3 小时前
滑动窗口最大值
数据结构·算法·leetcode
AI街潜水的八角4 小时前
基于C++的决策树C4.5机器学习算法(不调包)
c++·算法·决策树·机器学习