从 KMP算法到 Java的 String.indexOf(String str)方法

简介: 从 KMP算法到 Java的 String.indexOf(String str)方法

一、前言

从九月一开始日刷算法,每日三题稳定收获 LeetCode 21积分,在今天刷到 28. 实现 strStr()时, 最开始使用了暴力破解,双重循环(下面有具体介绍),但是在我看评论区的时候,发现这道题是 KMP的经典题目,但是我连什么是 KMP都不知道,接下来我会为和我一样的算法小白分享一下我理解的 KMP算法和相关题目的讲解

二、LeetCode 28. 实现 strStr()

题目描述

网络异常,图片无法展示
|

解题思路(暴力破解)

  • 根据文中所说,在字符串 haystack中找出 needle,找到了就返回第一个字符串出现位置的下标
  • 这个时候,我灵机一动,这不就是两层循环的事情吗,第一层循环遍历 haystack,然后第二次循环遍历 needle,判断字符串是否相等就可以了
  • 于是我开始对字符串下手了,先将两个字符串都转为数组
  • 第一层 for循环从下标 0开始,终止条件是 haystack的长度
  • 第二个 while循环的开始条件是当前外层循环的字符 == needle的第一个字符
  • 相等 i++,n++
  • 不相等 结束 while循环
  • 每次在去判断一下 n是否 == needle的长度
  • 相等则表明 needle在字符串 haystack中

假设目标字符串 haystack为 abcabde,needle字符串为 abd

下图为暴力破解的流程图

网络异常,图片无法展示
|

代码实现

public static int strStr(String haystack, String needle) {
    // 字符串转为 char数组
    char[] chars = haystack.toCharArray();
    char[] chars1 = needle.toCharArray();
    // 遍历字符串 haystack
    for (int i = 0; i < chars.length; i++) {
        // n表示第二层for循环的数组下标, m表示第一层for循环的数组下标
        int n = 0;
        int m = i;
        // 遍历 needle对应的 char数组,判断 chars[m] == chars1[n]
        while(m < chars.length && n < chars1.length && chars[m] == chars1[n]){
            m++;
            n++;
        }
        // 判断遍历的第二个字符串 needle的数组是否遍历完成
        // 只有 needle包含在 haystack中,才能使得 n == chars.length
        if (n == chars1.length && chars[m - 1] == chars1[n - 1]){
            return i;
        }
    }
    return -1;
}
复制代码

提交结果

网络异常,图片无法展示
|

小题总结

我知道自己的方法属于暴力破解,正确方式不应该这样做,所以我看了评论区和题解区发现有以下几种题解方式

  • 使用 String.substring(int beginIndex, int endIndex)方法截取字符串的
  • 内置函数(方法), 例如: String.indexOf(String str)
  • 像我一样两层循环
  • 双指针
  • KMP

看到 KMP的时候我真的是懵的,因为听都没听说过,在看下面的评论,差点吓退我,但是 内卷人是这么容易被吓退的吗?赶紧学起来

网络异常,图片无法展示
|

三、KMP算法

由来

外国人: Knuth,Morris和Pratt发明了这个算法,然后取它三个的首字母进行了命名。所以叫做KMP

用处

主要应用在 字符串匹配中

最长公共前后缀

将字符串拆分成以首字符开始的各个子串,然后依次判断,下标 n与下标 length - n - 1的元素是否相等,若有 m个连续相等的字符,则最长公共前缀为 m

例如字符串:AABAAF的公共前后缀长度如下图所示,红色代表不相同,蓝色代表元素相同

网络异常,图片无法展示
|

知道了公共前缀, 相应的可以利用我们获取到的公共前缀表来找到当字符不匹配的时候指针应该移动的位置,如下图所示:

下图来自于 代码随想录

网络异常,图片无法展示
|

核心:next数组

next数组就是用来存储公共前缀的个数的, 例如上面的例子,他的next数组结果就应该是

int[] next = {0,1,0,1,2,0}
复制代码

思路分析

  • 前置条件, 字符串 s,前缀数组 int[] next
  • 设置一个整数 j代表最长公共前后缀的个数
  • 首先是初始化,我们的 next数组第一个元素肯定是 0
  • 然后去 for循环我们的字符串,这里需要注意的是我们的for循环是从下标 1开始的
  • 判断 j必须大于 0不然的话在回溯过程中会发生 越界的情况,还要判断元素是否相等
  • 若不相等则 j回溯到上一个 next数组下标
  • 这里需要注意的是要用 while循环,因为可能会一直进行回溯操作
  • 当 s.charAt(j) == s.charAt(i)时,代表最长前后缀元素个数 +1,所以 j++
  • 最后将 j的值赋给数组 next[i]

代码展示

public static int[] getNext(String s,int[] next){
    int j = 0;
    // 初始化下标 0
    next[0] = 0;
    for (int i = 1; i < s.length(); i++) {
        while(j > 0 && s.charAt(j) != s.charAt(i)){
            j = next[j - 1];
        }
        if (s.charAt(j) == s.charAt(i)){
            j++;
        }
        next[i] = j;
    }
    return next;
}
复制代码

四、LeetCode 28. 实现 strStr()

现在我们重新根据 KMP算法来做一遍 LeetCode 28题

思路分析

  • 判断 needle长度是否等于 0,如果等于 0则返回 0
  • 判断 needle长度是否大于 haystack,如果大于则返回 -1
  • 创建 next数组
  • 执行我们的 getNext方法
  • 设置 next数组的指针 j = 0
  • for循环遍历 haystack
  • next指针大于 0时判断字符串 haystack下标 i位置的元素是否等于字符串 needle下标 j的位置
  • 不相等则 j进行回溯操作
  • 一样的, 回溯可能多次,所以使用 while循环
  • 如果相等
  • j++
  • 如果 j == needle的长度,则代表 haystack包含字符串 needle
  • 返回 i - j + 1;

代码展示

public static int strStr2(String haystack, String needle) {
        // 如果 needle为 “” 则返回0
        if (needle.length() == 0){
            return 0;
        }
        // 如果 needle的长度大于 haystack则返回 -1
        if (needle.length() > haystack.length()){
            return -1;
        }
        // 生成 next数组
        int[] next = new int[needle.length()];
        getNext(needle,next);
        // 标记指针在 needle回退后的位置
        int j = 0;
        for (int i = 0; i < haystack.length(); i++) {
            // 判断当前位置
            while(j > 0  && haystack.charAt(i) != needle.charAt(j)){
                j = next[j - 1];
            }
            if (haystack.charAt(i) == needle.charAt(j)){
                j++;
            }
            if (j == needle.length()){
                return i - j + 1;
            }
        }
        return -1;
    }
    public static int[] getNext(String s,int[] next){
        int j = 0;
        // 初始化下标 0
        next[0] = 0;
        for (int i = 1; i < s.length(); i++) {
            while(j > 0 && s.charAt(j) != s.charAt(i)){
                j = next[j - 1];
            }
            if (s.charAt(j) == s.charAt(i)){
                j++;
            }
            next[i] = j;
        }
        return next;
    }
复制代码

提交结果

虽然但是,没有暴力破解的速度快,但是确实也学到了一种算法

网络异常,图片无法展示
|

五、String.index(String str)

在 LeetCode评论区有人直接使用了编程语言封装好的方法或者函数,我以前也只是使用过,接下来我们一起看一下源代码

我使用的测试代码如下所示

String haystack = "mississippi", needle = "issip";
int i1 = haystack.indexOf(needle);
复制代码

对应的源码方法重载跳转如下所示:

  • 先进入 indexOf(String str)方法
  • 跳转进入 indexOf(String str, int fromIndex)
  • 这个时候 fromIndex == 0
  • 跳转进入 inexOf(char[] source, int sourceOffset, int sourceCount,            char[] target, int targetOffset, int targetCount,            int fromIndex)
  • value表示 haystack元素的 char数组
  • value.length表示 haystack的长度
  • str.value表示 needle元素的 char数组
  • str.value.length表示 needle的长度
  • 接下来我们看 indexOf方法的具体实现

网络异常,图片无法展示
|

网络异常,图片无法展示
|

String.indexOf()方法详解

因为我没有下载 java8源码,所以直接在这里进行注释标注了

// source:       当前字符串元素
// sourceOffset: 偏移量
// sourceCount: 当前字符串长度
// target:      目标字符串
// targetOffset:目标字符串偏移量
// targetCount: 目标字符串长度
// fromIndex:   索引位置
static int indexOf(char[] source, int sourceOffset, int sourceCount,
        char[] target, int targetOffset, int targetCount,
        int fromIndex) {
    if (fromIndex >= sourceCount) {
        return (targetCount == 0 ? sourceCount : -1);
    }
    if (fromIndex < 0) {
        fromIndex = 0;
    }
    // 如果目标字符串的长度为 0则直接返回 索引位置 (我们这里因为之前方法重载 fromIndex为 0 所以返回 0)
    if (targetCount == 0) {
        return fromIndex;
    }
    // 获取目标 首字符
    char first = target[targetOffset];
    // 我们这里就是 源字符串长度 - 目标字符串长度, sourceOffset == 0
    int max = sourceOffset + (sourceCount - targetCount);
    // 循环,i == 0
    // 加入循环到了 max的索引位置还没有找到和 目标字符串首字符相等的元素,则源字符串中肯定不包含目标字符串
    for (int i = sourceOffset + fromIndex; i <= max; i++) {
        // 搜索首字符所在位置
        if (source[i] != first) {
            while (++i <= max && source[i] != first);
        }
        // 找到了首字符,判断其余字符位置是否相等
        if (i <= max) {
            int j = i + 1;
            // 根据目标字符串的长度截取源字符串从下标 i开始相同长度的字符串
            int end = j + targetCount - 1;
            // 遍历判断
            for (int k = targetOffset + 1; j < end && source[j]
                    == target[k]; j++, k++);
            // 如果到最后一个元素都相同,则代表源字符串内包含目标字符串
            if (j == end) {
                // 这里就直接返回了 i 因为源字符串偏移量为 0
                return i - sourceOffset;
            }
        }
    }
    return -1;
}
复制代码

使用该源码方式做 LeetCode 28题

public static int strStr(String haystack, String needle) {
    // 如果 needle为 “” 则返回0
    if (needle.length() == 0){
        return 0;
    }
    // 如果 needle的长度大于 haystack则返回 -1
    if (needle.length() > haystack.length()){
        return -1;
    }
    int max = haystack.length() - needle.length();
    char first = needle.charAt(0);
    for (int i = 0; i <= max; i++) {
        if (haystack.charAt(i) != first){
            while(++i <= max && haystack.charAt(i) != first);
        }
        if (i <= max) {
            int j = i + 1;
            int end = j + needle.length() - 1;
            for (int k = 1; j < end && haystack.charAt(j)
                    == needle.charAt(k); j++, k++);
            if (j == end) {
                return i ;
            }
        }
    }
    return -1;
}
复制代码

提交结果

网络异常,图片无法展示
|

六、后续

KMP真的很难理解,建议多看几遍 B站代码随想录,文章也的再好,我感觉也没有视频来的直观,更何况我还是一个初级作者(脸上贴金ing)

接下来,还有一道题 LeetCode 459. 重复的子字符串也可以使用 KMP算法进行解答,感兴趣的可以关注一下我的专栏每日算法

由于算法方面特别影响推荐,我还准备去记录一下自己的刷题,所以每周更新一次算法刷题进度和思路分析,每天三道没有做过的题目,欢迎大家关注



目录
相关文章
|
6天前
|
存储 算法 安全
探究‘公司禁用 U 盘’背后的哈希表算法与 Java 实现
在数字化办公时代,信息安全至关重要。许多公司采取“禁用U盘”策略,利用哈希表算法高效管理外接设备的接入权限。哈希表通过哈希函数将设备标识映射到数组索引,快速判断U盘是否授权。例如,公司预先将允许的U盘标识存入哈希表,新设备接入时迅速验证,未授权则禁止传输并报警。这有效防止恶意软件和数据泄露,保障企业信息安全。 代码示例展示了如何用Java实现简单的哈希表,模拟公司U盘管控场景。哈希表不仅用于设备管理,还在文件索引、用户权限等多方面助力信息安全防线的构建,为企业数字化进程保驾护航。
|
14天前
|
监控 算法 网络协议
Java 实现局域网电脑屏幕监控算法揭秘
在数字化办公环境中,局域网电脑屏幕监控至关重要。本文介绍用Java实现这一功能的算法,涵盖图像采集、数据传输和监控端显示三个关键环节。通过Java的AWT/Swing库和Robot类抓取屏幕图像,使用Socket进行TCP/IP通信传输图像数据,并利用ImageIO类在监控端展示图像。整个过程确保高效、实时和准确,为提升数字化管理提供了技术基础。
50 15
|
3月前
|
存储 人工智能 算法
数据结构与算法细节篇之最短路径问题:Dijkstra和Floyd算法详细描述,java语言实现。
这篇文章详细介绍了Dijkstra和Floyd算法,这两种算法分别用于解决单源和多源最短路径问题,并且提供了Java语言的实现代码。
109 3
数据结构与算法细节篇之最短路径问题:Dijkstra和Floyd算法详细描述,java语言实现。
|
6天前
|
运维 监控 算法
企业局域网监控软件中 Java 优先队列算法的核心优势
企业局域网监控软件是数字化时代企业网络安全与高效运营的基石,犹如一位洞察秋毫的卫士。通过Java实现的优先队列算法,它能依据事件优先级排序,确保关键网络事件如异常流量、数据泄露等被优先处理,保障系统稳定与安全。代码示例展示了如何定义网络事件类并使用PriorityQueue处理高优先级事件,尤其在面对疑似风险时迅速启动应急措施。这一核心技术助力企业在复杂网络环境中稳健前行,护航业务腾飞。
50 32
|
5天前
|
存储 监控 算法
探秘局域网桌面监控:深入剖析 Java 语言核心算法
在数字化办公时代,局域网桌面监控如同企业的“智慧鹰眼”,确保工作效率与数据安全。本文以Java为载体,揭示哈希表在监控中的关键应用。通过高效的数据结构和算法,哈希表能快速索引设备连接信息,大幅提升监控的时效性和响应速度。代码示例展示了如何用Java实现设备网络连接监控,结合未来技术如AI、大数据,展望更智能的监控体系,助力企业在数字化浪潮中稳健前行。
|
20天前
|
缓存 算法 搜索推荐
Java中的算法优化与复杂度分析
在Java开发中,理解和优化算法的时间复杂度和空间复杂度是提升程序性能的关键。通过合理选择数据结构、避免重复计算、应用分治法等策略,可以显著提高算法效率。在实际开发中,应该根据具体需求和场景,选择合适的优化方法,从而编写出高效、可靠的代码。
29 6
|
3月前
|
Java 测试技术 开发者
Java零基础-indexOf(String str)详解!
【10月更文挑战第14天】Java零基础教学篇,手把手实践教学!
135 65
|
3月前
|
Java
【编程基础知识】(讲解+示例实战)方法参数的传递机制(值传递及地址传递)以及String类的对象的不可变性
本文深入探讨了Java中方法参数的传递机制,包括值传递和引用传递的区别,以及String类对象的不可变性。通过详细讲解和示例代码,帮助读者理解参数传递的内部原理,并掌握在实际编程中正确处理参数传递的方法。关键词:Java, 方法参数传递, 值传递, 引用传递, String不可变性。
77 1
【编程基础知识】(讲解+示例实战)方法参数的传递机制(值传递及地址传递)以及String类的对象的不可变性
|
3月前
|
Java 测试技术 开发者
Java零基础-indexOf(String str)详解!
【10月更文挑战第13天】Java零基础教学篇,手把手实践教学!
71 1
|
3月前
|
算法 搜索推荐 Java
java 后端 使用 Graphics2D 制作海报,画echarts图,带工具类,各种细节:如头像切割成圆形,文字换行算法(完美实验success),解决画上文字、图片后不清晰问题
这篇文章介绍了如何使用Java后端技术,结合Graphics2D和Echarts等工具,生成包含个性化信息和图表的海报,并提供了详细的代码实现和GitHub项目链接。
176 0
java 后端 使用 Graphics2D 制作海报,画echarts图,带工具类,各种细节:如头像切割成圆形,文字换行算法(完美实验success),解决画上文字、图片后不清晰问题