刷题day8 28实现strStr()、459重复的子字符串、字符串总结、双指针回顾

一、28实现strStr()

先讲讲KMP,具体是在代码随想录中详细讲解

  • 什么是KMP
  • KMP有什么用
  • 什么是前缀表
  • 为什么一定要用前缀表
  • 如何计算前缀表
  • 前缀表与next数组
  • 使用next数组来匹配
  • 时间复杂度分析
  • 构造next数组
  • 使用next数组来做匹配
  • 前缀表统一减一 C++代码实现
  • 前缀表(不减一)C++实现
  • 总结 

找到前缀表010120,做出next表,这个next表就是没有任何的操作,就是等于前缀表,所以KMP的核心在于找到next数组,然后比较的时候,这里都是用j来比较 i 的,如果发现这一位不匹配,那么就是 j 要回退到前一位的next数组的值的那一位,即 j = next[j - 1],并且这里是做循环while的(这里我们定义的是 i 是后缀末尾,j 是前缀末尾)。然后这一位再进行比较,相等的话 j++,然后给next数组赋值 j ,即next[i] = j。然后根据这层逻辑求出来next数组,这里就是下边写到的getNext函数的定义,在主函数中直接传入初始化的next数组和模式串到getNext函数中,然后进行主串和模式串的比较

然后写代码,在代码中首先要判断一下如果模式串为0或者模式串的长度大于主串的长度,那么会返回-1

之后就是初始化创建一个next数组,然后带入上述已经写好的getNext函数中,得出next数组

然后进行主串和模式串的比较,先初始化 j = 0,然后进入for循环,从模式串的 j 位比较主串的 i 位,如果不相同,类似getNext写一个while循环,然后 j 移动到前一位的next位,即 j = next[j - 1]。然后如果相同的话,写if,就是 j 移动到下一位,即 j++。最后还有一个最后的if,要判断是否是比较完成了,比较完成根据题意返回主串匹配成功的开始位,这里因为比较完成之后j是++的,所以j是在模式串的最后一位+1的位置的,所以判断的时候直接比较 j == needle.length(),如果相等的话就是返回 return i - needle.length() +1,这个就是匹配成功的开始位。代码如下:

class Solution {
    public int strStr(String haystack, String needle) {
        if(needle.length() == 0)
            return -1;
        if(haystack.length() < needle.length())
            return -1;
        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;
    }

    public 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;
        }
    }
}

二、459重复的子字符串

首先这道题先要清楚是求什么,求的是这一串给定字符串是不是由重复的子字符串组成的,如果是就返回true,所以定义的函数类型是boolean型的,之后开始下面的代码

首先要写一个判断 s 是不是空串即使用一个equals函数:s.equals(""),之后s的长度赋值给len,再往下将s转换为字符串数组:利用char[] chars = s.toCharArray()来转换,其实这一步和 s.charAt(i) 这个效果是一样的。之后创建一个next数组,要和自己串的长度相等,之前上一题不和主串长度相等是因为这道题这个串自己就是子串。

之后就是求next数组的时候了,其实步骤和之前的上一题求next数组方法是一样的,只不过在进入for循环的时候要先初始化 j = 0;next[0] = 0;之后就是求next数组的固定过程了,就是while循环是为了可以让 j 回退,然后 i 从1的位置开始,和j位置的值进行比较,如果不相等的话,j 就要回退到前一位的next数组的值的那一位,以此循环,如果发现相等,也就是下一步就要判断的,j要++

这里注意要直接跳出if循环 ,然后给next数组赋值,而且if条件不能加j>0了,因为j > 0 的目的是确保 j 不会超出数组 next 的索引范围。但是,j 的值是可以为0的,特别是在循环的初始阶段。如果在 j 的值为0时不允许执行 j++,那么在 next[0] = 0; 之后就永远无法使 j 的值增加,导致进入死循环。

 之后就是最关键的一步了,要判断这个字符串是不是重复字符串,就是要判断字符串的长度减去next数组的最后一位的值,也就是最长相等前后缀的长度,然后看字符串的长度是否能整除,能整除的话就是重复字符串,并且这里还要加一个next[len - 1] >0的条件,原因如下:

KMP 算法的 next[len - 1] 表示了字符串中最长的相同前缀后缀的长度。如果这个值大于 0,说明存在相同的前缀后缀,即字符串中存在重复的模式,因为你找的时候不就是从前面找到后面的,后面都是基于前面的,如果最后一位都没找着,那之前的那几位都没有的,很简单

 如果 都没找到最后返回一个false,代码如下:

class Solution {
    public boolean repeatedSubstringPattern(String s) {
        if(s.equals(""))
            return false;
        
        int len = s.length();
        char[] chars = s.toCharArray();
        int[] next = new int[len];

        int j=0;
        next[0] = 0;
        for(int i=1;i<len;i++)
        {
            while(j>0&&chars[i] != chars[j])
            {
                j = next[j-1];
            }

            if(chars[i] == chars[j])
                j++;
            
            next[i] = j;
            
        }  

        if(next[len - 1] > 0 && len % (len - next[len - 1]) == 0)
        {
            return true;
        }

        return false;
         
    }
}

三、字符串总结

 主要是双指针法,以及KMP算法的地方

双指针法在数组、链表以及字符串中很常用

KMP算法主要是理解最长相等前后缀概念是什么,怎么拿笔求出来的,之后又怎么用next数组求出来的,在匹配字符串的时候很实用

字符串类类型的题目,往往想法比较简单,但是实现起来并不容易,复杂的字符串题目非常考验对代码的掌控能力。

双指针法是字符串处理的常客。

KMP算法是字符串查找最重要的算法,但彻底理解KMP并不容易,我们已经写了五篇KMP的文章,不断总结和完善,最终才把KMP讲清楚。(摘自代码随想录)

四、双指针回顾

  • 27移除元素 详情见 day1的移除元素:快慢指针一起动,快指针for循环;判断条件后不相等边的值向前覆盖变相删除 之后慢指针++ 下一轮循环
  • 344反转字符串 详情见 day7的反转字符串:头尾指针  头指针++  尾指针--  异或法
  • 替换数字 详情见 day7的替换数字:这个Java代码使用的是开辟新空间进行填充,返回新数组
  • 151反转字符串里的单词  详情见 day7的翻转字符串里的单词:三部分 ①去首尾空格中间多余空格用首尾双指针 ②翻转整个字符串用首尾双指针 ③翻转空格之间的字符串用首双指针,一个是首位一个是下一位,尾指针循环+1,一直判断到是否有空格停止,再套用第二步定义的反转字符串函数
  • 206反转链表  详情见 day3反转链表:一个pre节点在cur节点前面,一个temp节点在cur节点后面,然后开始移动,顺序是temp先移动到cur.next,然后打断链表,cur.next指向pre,之后pre指向cur,cur再指向temp,之后进入下一轮循环,一直到cur指向temp指向null
  • 24两两交换链表中的节点  详情见 day4两两交换链表中的节点:这道题设置了四个节点指针,首先是创建了一个虚拟头节点,让他指向头节点,之后四个指针节点分别指向虚拟头节点,第一二三节点,移动顺序是先将cur指向second,这是定头,再将second指向first,之后再将first指向temp也就是第三个节点
  • 19删除链表的倒数第N个节点  详情见 day4删除链表的倒数第N个节点:这道题首先定义了一个虚拟头节点指向head,双指针:两个快慢指针都指向虚拟头节点,之后让快指针移动n+1个位置,保证和慢指针之间的距离保持n,两个指针同时向后移动,直到快指针指向null,那么慢指针指向的就是倒数第n个位置,然后做删除操作即可
  • 链表相交  详情见  day4面试题-链表相交:这道题定义了两个链表的头指针nodea和nodeb,然后将两链表的尾端对齐,将链表a的头节点指针移动lena-lenb的距离,也就是和b链表的头节点指针对齐开始比较,如果相等就直接返回相等的节点即可,如果不相等那就继续循环比较
  • 142环形链表II  详情见  day4环形链表II:首先要定义快慢指针都从头节点开始,每次快指针向后移动两个单位,慢指针向后移动一个单位,如果有环的话他们一定会相遇的,这是一个定理;之后在相遇的时候又定义了一个头节点开始的指针,一个相遇点的指针,然后两个一起向后移动,如果能相遇的话,说明这是环的入口,这个也是一个定理,是推导出来的,不管那个相遇点转几圈,他都会和从头开始的指针在环的入口相见
  • 15三数之和  详情见 day6三数之和:这道题是想要找到给定数组里的和0的不重复三元组,那么这道题先对数组进行了从小到大排序,之后去掉重复的a,此时位置是在i,然后定义了i+1的位置的指针向后移动,定义了最后位置的指针(即b、c)向前移动进行相加统计和,和如果为0就添加到三元组,之后去掉b、c的下一位重复值,再移动去找和为0的元组,最后返回
  • 18四数之和   详情见 day6四数之和:四数之和可以参考三数之和,因为他只多了一个for循环 j = i+1的循环操作,相当于就是i做循环,然后再从i的下一位做循环,再判断去重,其他过程都是和之前三数之和是一样的,只是多了一个i下一位的指针

以上这些最好画个图去理解,画一下指针的动态变化,还有链表打断重新指向的过程

  • 23
    点赞
  • 23
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值