代码随想录算法训练营第九天 | LeetCode 8. 找出字符串中第一个匹配项的下标、LeetCode 459. 重复的子字符串

简介: 代码随想录算法训练营第九天 | LeetCode 8. 找出字符串中第一个匹配项的下标、LeetCode 459. 重复的子字符串

1. LeetCode 8. 找出字符串中第一个匹配项的下标

1.1 思路

  1. 前缀表的由来:我们遍历到模式串f时发现不匹配,跳到了b位置,为什么呢?因为前面遍历了“aabaa”这个子串,后缀“aa”,前缀“aa”,f不匹配了,就找到与这个子串的后缀“aa”相等的前缀“aa”的后面“b”开始匹配,就是找到这个子串最长的相等前后缀
  2. 前缀与后缀:前缀是包含首字母,不包含尾字母的所有子串,上述子串只要不是“aabaaf”的都是前缀,只要包括了首字母,不包括尾字母,比如a,aa,aab,aaba,aabaa;后缀是只包含尾字母,不包含首字母的所有子串,上述子串只要不是“aabaaf”的都是后缀,只要包含了尾字母,不包含首字母,比如abaaf,baaf,aaf,af,f
  3. 最长相等前后缀:a,最长相等前后缀为0,aa,最长相等前后缀为1,aab,最长相等前后缀为0,aaba,最长相等前后缀为1,aabaa,最长相等前后缀为2,aabaaf,最长相等前后缀为0,这些数字就是前缀表,取最长的

1.2 实现步骤

  1. next数组(前缀表)实现步骤:初始化、处理前后缀不相同的情况、处理前后缀相同的情况、更新
  2. next数组就是以各个字符串为结尾的这个子串的一个最长相等前后缀的集合
  3. 初始化:指针i、指针j,指针j指向前缀末尾,i指向后缀末尾,j其实还包括了子串的最长相等前后缀的长度。j初始化为0,因为是从最开始的位置开始,next[0]=0,这个表示当只有一个字符的子串,最大相等前后缀的长度是0,i初始化是进入for循环中,i从1开始到arr.length跟j比较
  4. 前后缀不相同的情况:s(模式串),s[i]!=s[j]时,j就回退,看j前一位的值对应的下标,就是要回到的位置,j=next[j-1]。为什么看j的前一位的值呢?因为求这个前缀表时遇见冲突也是看前一位的值。由于回退时是连续回退,所以用while
  5. 前后缀相同的情况:s[i]==s[j],那么j++
  6. 更新next数组的值:next[i]==j

1.3 代码

class Solution {
    //前缀表(不减一)Java实现
    public int strStr(String haystack, String needle) {
        if (needle.length() == 0) return 0;
        int[] next = new int[needle.length()];
        getNext(next, needle);
        int j = 0;
        for (int i = 0; i < haystack.length(); i++) {
            while (j > 0 && needle.charAt(j) != haystack.charAt(i)) 
                j = next[j - 1];
            if (needle.charAt(j) == haystack.charAt(i)) 
                j++;
            if (j == needle.length()) 
                return i - needle.length() + 1;
        }
        return -1;
    }
    private void getNext(int[] next, String s) {
        int j = 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; 
        }
    }
}

2. LeetCode 459. 重复的子字符串

2.1 结论

       如果这个字符串是由重复子串组成的,那么重复子串的最小单位就是这个字符串的最长相等前后缀所不包含的那一个子串组成的

2.2 代码

class Solution {
    public boolean repeatedSubstringPattern(String s) {
        if (s.equals("")) return false;
        int len = s.length();
        // 原串加个空格(哨兵),使下标从1开始,这样j从0开始,也不用初始化了
        s = " " + s;
        char[] chars = s.toCharArray();
        int[] next = new int[len + 1];
        // 构造 next 数组过程,j从0开始(空格),i从2开始
        for (int i = 2, j = 0; i <= len; i++) {
            // 匹配不成功,j回到前一位置 next 数组所对应的值
            while (j > 0 && chars[i] != chars[j + 1]) j = next[j];
            // 匹配成功,j往后移
            if (chars[i] == chars[j + 1]) j++;
            // 更新 next 数组的值
            next[i] = j;
        }
        // 最后判断是否是重复的子字符串,这里 next[len] 即代表next数组末尾的值
        if (next[len] > 0 && len % (len - next[len]) == 0) {
            return true;
        }
        return false;
    }
}

3. 字符串篇

3.1 双指针法

       在344.反转字符串 ,我们使用双指针法实现了反转字符串的操作,双指针法在数组,链表和字符串中很常用。

       接着在字符串:替换空格,同样还是使用双指针法在时间复杂度O(n)的情况下完成替换空格。

       其实很多数组填充类的问题,都可以先预先给数组扩容带填充后的大小,然后在从后向前进行操作。

       那么针对数组删除操作的问题,其实在27. 移除元素中就已经提到了使用双指针法进行移除操作。

       同样的道理在151.翻转字符串里的单词 中我们使用O(n)的时间复杂度,完成了删除冗余空格。

       一些同学会使用for循环里调用库函数erase来移除元素,这其实是O(n^2)的操作,因为erase就是O(n)的操作,所以这也是典型的不知道库函数的时间复杂度,上来就用的案例了。

3.2 反转系列

       在反转上还可以在加一些玩法,其实考察的是对代码的掌控能力。

       541. 反转字符串II中,一些同学可能为了处理逻辑:每隔2k个字符的前k的字符,写了一堆逻辑代码或者再搞一个计数器,来统计2k,再统计前k个字符。

       其实当需要固定规律一段一段去处理字符串的时候,要想想在在for循环的表达式上做做文章

       只要让 i += (2 * k),i 每次移动 2 * k 就可以了,然后判断是否需要有反转的区间。

       因为要找的也就是每2 * k 区间的起点,这样写程序会高效很多。

       在151.翻转字符串里的单词中要求翻转字符串里的单词,这道题目可以说是综合考察了字符串的多种操作。是考察字符串的好题。

       这道题目通过 先整体反转再局部反转,实现了反转字符串里的单词。

       后来发现反转字符串还有一个牛逼的用处,就是达到左旋的效果。

       在字符串:反转个字符串还有这个用处?中,我们通过先局部反转再整体反转达到了左旋的效果。

3.3 KMP

       KMP的主要思想是当出现字符串不匹配时,可以知道一部分之前已经匹配的文本内容,可以利用这些信息避免从头再去做匹配了。

       KMP的精髓所在就是前缀表,在KMP精讲中提到了,什么是KMP,什么是前缀表,以及为什么要用前缀表。

       前缀表:起始位置到下标i之前(包括i)的子串中,有多大长度的相同前缀后缀。

       那么使用KMP可以解决两类经典问题:

  1. 匹配问题:28. 实现 strStr()
  2. 重复子串问题:459.重复的子字符串

       再一次强调了什么是前缀,什么是后缀,什么又是最长相等前后缀。

       前缀:指不包含最后一个字符的所有以第一个字符开头的连续子串。

       后缀:指不包含第一个字符的所有以最后一个字符结尾的连续子串。

       然后针对前缀表到底要不要减一,这其实是不同KMP实现的方式,我们在KMP精讲中针对之前两个问题,分别给出了两个不同版本的的KMP实现。

       其中主要理解j=next[x]这一步最为关键!

4. 双指针篇

4.1 数组篇

       在数组:就移除个元素很难么?中,原地移除数组上的元素,我们说到了数组上的元素,不能真正的删除,只能覆盖。通过两个指针在一个for循环下完成两个for循环的工作

4.2 字符串篇

       在字符串:这道题目,使用库函数一行代码搞定 中讲解了反转字符串,注意这里强调要原地反转,要不然就失去了题目的意义。

       使用双指针法,定义两个指针(也可以说是索引下标),一个从字符串前面,一个从字符串后面,两个指针同时向中间移动,并交换元素。时间复杂度是O(n)。

       在替换空格 中介绍使用双指针填充字符串的方法,如果想把这道题目做到极致,就不要只用额外的辅助空间了!

       思路就是首先扩充数组到每个空格替换成"%20"之后的大小。然后双指针从后向前替换空格。

       有同学问了,为什么要从后向前填充,从前向后填充不行么?

       从前向后填充就是O(n^2)的算法了,因为每次添加元素都要将添加元素之后的所有元素向后移动。

       其实很多数组(字符串)填充类的问题,都可以先预先给数组扩容带填充后的大小,然后在从后向前进行操作。

       那么在字符串:花式反转还不够!中,我们使用双指针法,用O(n)的时间复杂度完成字符串删除类的操作,因为题目要删除冗余空格。

       在删除冗余空格的过程中,如果不注意代码效率,很容易写成了O(n^2)的时间复杂度。其实使用双指针法O(n)就可以搞定。

       主要还是大家用erase用的比较随意,一定要注意for循环下用erase的情况,一般可以用双指针写效率更高!

4.3 链表篇

       翻转链表是现场面试,白纸写代码的好题,考察了候选者对链表以及指针的熟悉程度,而且代码也不长,适合在白纸上写。

       在链表:听说过两天反转链表又写不出来了?中,讲如何使用双指针法来翻转链表,只需要改变链表的next指针的指向,直接将链表反转 ,而不用重新定义一个新的链表。

       思路还是很简单的,代码也不长,但是想在白纸上一次性写出bugfree的代码,并不是容易的事情。

       在链表中求环,应该是双指针在链表里最经典的应用,在链表:环找到了,那入口呢?中讲解了如何通过双指针判断是否有环,而且还要找到环的入口。

       使用快慢指针(双指针法),分别定义 fast 和 slow指针,从头结点出发,fast指针每次移动两个节点,slow指针每次移动一个节点,如果 fast 和 slow指针在途中相遇 ,说明这个链表有环。

       那么找到环的入口,其实需要点简单的数学推理,我在文章中把找环的入口清清楚楚的推理的一遍,如果对找环入口不够清楚的同学建议自己看一看链表:环找到了,那入口呢?

4.4 N数之和篇

       在哈希表:解决了两数之和,那么能解决三数之和么?中,讲到使用哈希法可以解决1.两数之和的问题

       其实使用双指针也可以解决1.两数之和的问题,只不过1.两数之和求的是两个元素的下标,没法用双指针,如果改成求具体两个元素的数值就可以了,大家可以尝试用双指针做一个leetcode上两数之和的题目,就可以体会到我说的意思了。

       使用了哈希法解决了两数之和,但是哈希法并不使用于三数之和!

       使用哈希法的过程中要把符合条件的三元组放进vector中,然后在去去重,这样是非常费时的,很容易超时,也是三数之和通过率如此之低的根源所在。

       去重的过程不好处理,有很多小细节,如果在面试中很难想到位。

       时间复杂度可以做到O(n^2),但还是比较费时的,因为不好做剪枝操作。

       所以这道题目使用双指针法才是最为合适的,用双指针做这道题目才能就能真正体会到,通过前后两个指针不算向中间逼近,在一个for循环下完成两个for循环的工作。

       只用双指针法时间复杂度为O(n^2),但比哈希法的O(n^2)效率高得多,哈希法在使用两层for循环的时候,能做的剪枝操作很有限。

       在双指针法:一样的道理,能解决四数之和中,讲到了四数之和,其实思路是一样的,在三数之和的基础上再套一层for循环,依然是使用双指针法。

       对于三数之和使用双指针法就是将原本暴力O(n^3)的解法,降为O(n^2)的解法,四数之和的双指针解法就是将原本暴力O(n^4)的解法,降为O(n^3)的解法。

       同样的道理,五数之和,n数之和都是在这个基础上累加。

相关文章
|
1月前
|
存储 算法 程序员
C 语言递归算法:以简洁代码驾驭复杂逻辑
C语言递归算法简介:通过简洁的代码实现复杂的逻辑处理,递归函数自我调用解决分层问题,高效而优雅。适用于树形结构遍历、数学计算等领域。
|
2月前
|
并行计算 算法 测试技术
C语言因高效灵活被广泛应用于软件开发。本文探讨了优化C语言程序性能的策略,涵盖算法优化、代码结构优化、内存管理优化、编译器优化、数据结构优化、并行计算优化及性能测试与分析七个方面
C语言因高效灵活被广泛应用于软件开发。本文探讨了优化C语言程序性能的策略,涵盖算法优化、代码结构优化、内存管理优化、编译器优化、数据结构优化、并行计算优化及性能测试与分析七个方面,旨在通过综合策略提升程序性能,满足实际需求。
79 1
|
2月前
|
存储 缓存 算法
通过优化算法和代码结构来提升易语言程序的执行效率
通过优化算法和代码结构来提升易语言程序的执行效率
|
2月前
|
算法
分享一些提高二叉树遍历算法效率的代码示例
这只是简单的示例代码,实际应用中可能还需要根据具体需求进行更多的优化和处理。你可以根据自己的需求对代码进行修改和扩展。
|
9天前
|
算法 数据安全/隐私保护
室内障碍物射线追踪算法matlab模拟仿真
### 简介 本项目展示了室内障碍物射线追踪算法在无线通信中的应用。通过Matlab 2022a实现,包含完整程序运行效果(无水印),支持增加发射点和室内墙壁设置。核心代码配有详细中文注释及操作视频。该算法基于几何光学原理,模拟信号在复杂室内环境中的传播路径与强度,涵盖场景建模、射线发射、传播及接收点场强计算等步骤,为无线网络规划提供重要依据。
|
22天前
|
机器学习/深度学习 算法
基于改进遗传优化的BP神经网络金融序列预测算法matlab仿真
本项目基于改进遗传优化的BP神经网络进行金融序列预测,使用MATLAB2022A实现。通过对比BP神经网络、遗传优化BP神经网络及改进遗传优化BP神经网络,展示了三者的误差和预测曲线差异。核心程序结合遗传算法(GA)与BP神经网络,利用GA优化BP网络的初始权重和阈值,提高预测精度。GA通过选择、交叉、变异操作迭代优化,防止局部收敛,增强模型对金融市场复杂性和不确定性的适应能力。
158 80
|
10天前
|
机器学习/深度学习 数据采集 算法
基于GA遗传优化的CNN-GRU-SAM网络时间序列回归预测算法matlab仿真
本项目基于MATLAB2022a实现时间序列预测,采用CNN-GRU-SAM网络结构。卷积层提取局部特征,GRU层处理长期依赖,自注意力机制捕捉全局特征。完整代码含中文注释和操作视频,运行效果无水印展示。算法通过数据归一化、种群初始化、适应度计算、个体更新等步骤优化网络参数,最终输出预测结果。适用于金融市场、气象预报等领域。
基于GA遗传优化的CNN-GRU-SAM网络时间序列回归预测算法matlab仿真
|
10天前
|
算法
基于龙格库塔算法的锅炉单相受热管建模与matlab数值仿真
本设计基于龙格库塔算法对锅炉单相受热管进行建模与MATLAB数值仿真,简化为喷水减温器和末级过热器组合,考虑均匀传热及静态烟气处理。使用MATLAB2022A版本运行,展示自编与内置四阶龙格库塔法的精度对比及误差分析。模型涉及热传递和流体动力学原理,适用于优化锅炉效率。
|
8天前
|
移动开发 算法 计算机视觉
基于分块贝叶斯非局部均值优化(OBNLM)的图像去噪算法matlab仿真
本项目基于分块贝叶斯非局部均值优化(OBNLM)算法实现图像去噪,使用MATLAB2022A进行仿真。通过调整块大小和窗口大小等参数,研究其对去噪效果的影响。OBNLM结合了经典NLM算法与贝叶斯统计理论,利用块匹配和概率模型优化相似块的加权融合,提高去噪效率和保真度。实验展示了不同参数设置下的去噪结果,验证了算法的有效性。
|
7天前
|
算法 决策智能
基于SA模拟退火优化算法的TSP问题求解matlab仿真,并对比ACO蚁群优化算法
本项目基于MATLAB2022A,使用模拟退火(SA)和蚁群优化(ACO)算法求解旅行商问题(TSP),对比两者的仿真时间、收敛曲线及最短路径长度。SA源于金属退火过程,允许暂时接受较差解以跳出局部最优;ACO模仿蚂蚁信息素机制,通过正反馈发现最优路径。结果显示SA全局探索能力强,ACO在路径优化类问题中表现优异。