leetcode极速复习版-第四章字符串

344. 反转字符串

编写一个函数,其作用是将输入的字符串反转过来。输入字符串以字符数组 char[] 的形式给出。

不要给另外的数组分配额外的空间,你必须原地修改输入数组、使用 O(1) 的额外空间解决这一问题。

你可以假设数组中的所有字符都是 ASCII 码表中的可打印字符。

示例 1:

输入:["h","e","l","l","o"]

输出:["o","l","l","e","h"]示例 2:

输入:["H","a","n","n","a","h"]

输出:["h","a","n","n","a","H"]

class Solution:
    def reverseString(self, s: List[str]) -> None:
        """
        Do not return anything, modify s in-place instead.
        """
        n = 0
        left = 0
        right = len(s) - 1
        while n < len(s)/2:
            temp = s[left]
            s[left] = s[right]
            s[right] = temp
 
            n += 1
            left += 1
            right -= 1
        return s
541. 反转字符串II

给定一个字符串 s 和一个整数 k,从字符串开头算起, 每计数至 2k 个字符,就反转这 2k 个字符中的前 k 个字符。

如果剩余字符少于 k 个,则将剩余字符全部反转。

如果剩余字符小于 2k 但大于或等于 k 个,则反转前 k 个字符,其余字符保持原样。

示例:

输入: s = "abcdefg", k = 2

输出: "bacdfeg"

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

其实在遍历字符串的过程中,只要让 i += (2 * k),i 每次移动 2 * k 就可以了,然后判断是否需要有反转的区间。

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

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

class Solution:
    # def reverseStr(self, s: str, k: int) -> str:
    #     def reverse(s):
    #         for i in range(len(s)//2):
    #             temp = s[i]
    #             s[i] = s[len(s) - 1 - i]
    #             s[len(s) - 1 - i] = temp
    #         return s
 
    #     s = list(s)
    #     n = len(s)//k # 注意不是/是//,表示取整数结果
    #     kn = 0
    #     for i in range(0, len(s), k): 
    #         kn += 1
    #         if kn % 2 == 1:
    #             if i + k > len(s):#这里判断的是如果余下不足k个
    #                 s[i:len(s)] = reverse(s[i:len(s)]) # 注意 s[len(s)]不存在但s[i:len(s)]是存在的。因为取不到右区间。
    #                 s = ''.join(s)
    #                 return s # 这里不return的话就会继续运行下面一行的reverse直接又变正序了
    #             # s[i:i+k] = reverse(s[i:i+k])  
    #             # return s[i:i+k], reverse(s[i:i+k])  
    #             # #tmd见鬼了 输入"abcdefg"k=8 输出是[["a","b","c","d","e","f","g"],["g","f","e","d","c","b","a"]]
    #             # # s[i:i+k], reverse(s[i:i+k])俩值居然不一样,明明已经s[i:i+k] = reverse(s[i:i+k])赋值过的
    #             # 傻了,因为reverse没有在这个if内进行 是在if i + k > len(s)内进行的 进行完尾巴部分就应该直接return 
    #             s[i:i+k] = reverse(s[i:i+k])
 
    #         else:
    #             continue
            
    #     s = ''.join(s)
    #     return s
 
    def reverseStr(self, s: str, k: int) -> str:
        def reverse(s):
            for i in range(len(s)//2):
                temp = s[i]
                s[i] = s[len(s) - 1 - i]
                s[len(s) - 1 - i] = temp
            return s
        s = list(s)
        n = len(s)//# 注意不是/是//,表示取整数结果
        kn = 0
        for i in range(0, len(s), k): 
            kn += 1
            if kn % 2 == 1:
                # 其实根本不需要上面那么冗杂 python会自动处理尾巴
                s[i:i+k] = reverse(s[i:i+k])
            else:
                continue
            
        s = ''.join(s)
        return s
剑指Offer 05.替换空格

请实现一个函数,把字符串 s 中的每个空格替换成"%20"。

示例 1: 输入:s = "We are happy."

输出:"We%20are%20happy."

首先扩充数组到每个空格替换成"%20"之后的大小。

然后从后向前替换空格

为什么要从后向前填充,从前向后填充不行么?

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

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

class Solution:
    def replaceSpace(self, s: str) -> str:
        s = list(s)
        n = 0
        len1 = len(s)
        for i in range(len1):
            if s[i] == ' ':
                n += 1
        s = s + [0]*n*2
        len2 = len(s)
        left = len1 - 1
        right = len2 - 1
        while right >= 0:
            if s[left] != ' ':
                s[right] = s[left]
            else:
                s[right] = '0'
                right -= 1
                s[right] = '2'
                right -= 1
                s[right] = '%'
            left -= 1
            right -= 1
        s = ''.join(s)
        return s
151.翻转字符串里的单词

给定一个字符串,逐个翻转字符串中的每个单词。

示例 1:

输入: "the sky is blue"

输出: "blue is sky the"

示例 2:

输入: "  hello world!  "

输出: "world! hello"

解释: 输入字符串可以在前面或者后面包含多余的空格,但是反转后的字符不能包括。

示例 3:

输入: "a good   example"

输出: "example good a"

解释: 如果两个单词间有多余的空格,将反转后单词间的空格减少到只含一个。

解题思路如下:

  • 移除多余空格
  • 将整个字符串反转
  • 将每个单词反转

举个例子,源字符串为:"the sky is blue "

  • 移除多余空格 : "the sky is blue"
  • 字符串反转:"eulb si yks eht"
  • 单词反转:"blue is sky the"
class Solution:
    def reverseWords(self, s: str) -> str:
        def reverse(s):
            for i in range(len(s)//2):
                tmp = s[i]
                s[i] = s[len(s) - 1 - i]
                s[len(s) - 1 - i] = tmp
            return s
 
        left = 0 
        right = 0
        s = list(s)
        while right < len(s):
            if s[right] != ' ': #原列表元素非空格时
                if left != 0:#如果left非索引0,那就代表着此处有单词分隔,就需要赋一个空格
                    s[left] = ' '#
                    left += 1
 
                while right < len(s) and s[right] != ' ': #这里才开始赋值
                    s[left] = s[right]
                    left += 1
                    right += 1
            else: #原列表元素是空格时直接索引自增。这时也意味着到了单词分隔处,所以下一次遇到非空格的元素就意味着新的单词,所以上面的if中就是在考虑这种情况
                right += 1
        s = s[:left]
        
        s = reverse(s)
        tmp = 0
        for i in range(len(s)):
            if s[i] == ' ':
                s[tmp:i] = reverse(s[tmp:i])
                tmp = i + 1
        s[tmp:len(s)] = reverse(s[tmp:len(s)])
        s = ''.join(s)
        return s
剑指Offer58-II.左旋转字符串

字符串的左旋转操作是把字符串前面的若干个字符转移到字符串的尾部。请定义一个函数实现字符串左旋转操作的功能。比如,输入字符串"abcdefg"和数字2,该函数将返回左旋转两位得到的结果"cdefgab"。

示例 1:

输入: s = "abcdefg", k = 2

输出: "cdefgab"

示例 2:

输入: s = "lrloseumgh", k = 6

输出: "umghlrlose"

上一题目中讲过,使用整体反转+局部反转就可以实现反转单词顺序的目的。

具体步骤为:

  1. 反转区间为前n的子串
  2. 反转区间为n到末尾的子串
  3. 反转整个字符串

为了让本题更有意义,提升一下本题难度:不能申请额外空间,只能在本串上操作

class Solution:
    def reverseLeftWords(self, s: str, n: int) -> str:
        def reverse(s):
            for i in range(len(s)//2):
                tmp = s[i]
                s[i] = s[len(s) - 1 - i]
                s[len(s) - 1 - i] = tmp
            return s
        s = list(s)
        s[:n] = reverse(s[:n])
        s[n:] = reverse(s[n:])
        s = reverse(s)
        s = ''.join(s)
        return s

在代码随想录上还有其他几种答案,非常简洁

最简单的直接切片:

# 方法一:可以使用切片方法
class Solution:
    def reverseLeftWords(self, s: str, n: int) -> str:
        return s[n:] + s[0:n]

如果直接用库函数的话:((待反转数组)以及reverse()方法)

# 方法二:也可以使用上文描述的方法,有些面试中不允许使用切片,那就使用上文作者提到的方法
class Solution:
    def reverseLeftWords(self, s: str, n: int) -> str:
        s = list(s)
        s[0:n] = list(reversed(s[0:n]))
        s[n:] = list(reversed(s[n:]))
        s.reverse()
        
        return "".join(s)

如果手写:(lst[left], lst[right] = lst[right], lst[left]这行比自己写的更简洁直白)

# 方法三:如果连reversed也不让使用,那么自己手写一个
class Solution:
    def reverseLeftWords(self, s: str, n: int) -> str:
        def reverse_sub(lst, left, right):
            while left < right:
                lst[left], lst[right] = lst[right], lst[left]
                left += 1
                right -= 1
        
        res = list(s)
        end = len(res) - 1
        reverse_sub(res, 0, n - 1)
        reverse_sub(res, n, end)
        reverse_sub(res, 0, end)
        return ''.join(res)
 
# 同方法二
# 时间复杂度:O(n)
# 空间复杂度:O(n),python的string为不可变,需要开辟同样大小的list空间来修改

使用模的方法:(j=(i+n)%len(s) 若n=2则j从2开始,到n之后下一个从0继续)

#方法四:考虑不能用切片的情况下,利用模+下标实现
class Solution:
    def reverseLeftWords(self, s: str, n: int) -> str:
        new_s = ''
        for i in range(len(s)):
            j = (i+n)%len(s)
            new_s = new_s + s[j]
        return new_s
28.实现 strStr()

实现 strStr() 函数。

给定一个 haystack 字符串和一个 needle 字符串,在 haystack 字符串中找出 needle 字符串出现的第一个位置 (从0开始)。如果不存在,则返回  -1。

示例 1: 输入: haystack = "hello", needle = "ll" 输出: 2

示例 2: 输入: haystack = "aaaaa", needle = "bba" 输出: -1

说明: 当 needle 是空字符串时,我们应当返回什么值呢?这是一个在面试中很好的问题。 对于本题而言,当 needle 是空字符串时我们应当返回 0 。这与C语言的 strstr() 以及 Java的 indexOf() 定义相符。

KMP主要应用在字符串匹配上。

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

所以如何记录已经匹配的文本内容,是KMP的重点,也是next数组肩负的重任。

next数组就是一个前缀表(prefix table)。

前缀表是用来回退的,它记录了模式串与主串(文本串)不匹配的时候,模式串应该从哪里开始重新匹配。

例如,要在文本串:aabaabaafa 中查找是否出现过一个模式串:aabaaf。

文本串中第六个字符b 和 模式串的第六个字符f,不匹配了。如果暴力匹配,发现不匹配,此时就要从头匹配了。

但如果使用前缀表,就不会从头匹配,而是从上次已经匹配的内容开始匹配,找到了模式串中第三个字符b继续开始匹配。

首先要知道前缀表的任务是当前位置匹配失败,找到之前已经匹配上的位置,再重新匹配,此也意味着在某个字符失配时,前缀表会告诉你下一步匹配中,模式串应该跳到哪个位置。

那么什么是前缀表:记录下标i之前(包括i)的字符串中,有多大长度的相同前缀后缀。

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

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

所以字符串a的最长相等前后缀为0。 字符串aa的最长相等前后缀为1。 字符串aaa的最长相等前后缀为2。

下标5之前这部分的字符串(也就是字符串aabaa)的最长相等的前缀 和 后缀字符串是 子字符串aa ,因为找到了最长相等的前缀和后缀,匹配失败的位置是后缀子串的后面,那么我们找到与其相同的前缀子串的后面重新匹配就可以了。

长度为前4个字符的子串aaba,最长相同前后缀的长度为1。 长度为前5个字符的子串aabaa,最长相同前后缀的长度为2。 长度为前6个字符的子串aabaaf,最长相同前后缀的长度为0。

找到的不匹配的位置, 那么此时我们要看它的前一个字符的前缀表的数值是多少。

为什么要前一个字符的前缀表的数值呢,因为要找前面字符串的最长相同的前缀和后缀。

所以要看前一位的 前缀表的数值。

前一个字符的前缀表的数值是2, 所以把下标移动到下标2的位置继续比配。

其中n为文本串长度,m为模式串长度,因为在匹配的过程中,根据前缀表不断调整匹配的位置,可以看出匹配的过程是O(n),之前还要单独生成next数组,时间复杂度是O(m)。所以整个KMP算法的时间复杂度是O(n+m)的。

构造next数组

其实就是计算模式串s,前缀表的过程。

void getNext(int* next, const string& s)

  1. 初始化:

定义两个指针i和j,j指向前缀末尾位置,i指向后缀末尾位置。

然后还要对next数组进行初始化赋值,如下:

int j = -1;
next[0] = j;

j 为什么要初始化为 -1呢,因为之前说过 前缀表要统一减一的操作仅仅是其中的一种实现,我们这里选择j初始化为-1,下文我还会给出j不初始化为-1的实现代码。

next[i] 表示 i(包括i)之前最长相等的前后缀长度(其实就是j)【所以next[i]=j】

所以初始化next[0] = j 。

  1. 处理前后缀不相同的情况

因为j初始化为-1,那么i就从1开始,进行s[i] 与 s[j+1]的比较。【相当于i=0的情况已经考虑过了,i=0对应的next数组元素是-1】【为什么要进行的是s[i]和s[j+1]的比较呢?其实就是前缀和后缀的比较……因为j表示前缀末尾,i表示后缀末尾】

所以遍历模式串s的循环下标i 要从 1开始,代码如下:

for (int i = 1; i < s.size(); i++) {

如果 s[i] 与 s[j+1]不相同,也就是遇到 前后缀末尾不相同的情况,就要向前回退。

怎么回退呢?

next[j]就是记录着j(包括j)之前的子串的相同前后缀的长度。【再次强调j是前缀】

那么 s[i] 与 s[j+1] 不相同,就要找 j+1前一个元素在next数组里的值(就是next[j])。

【不妨区分一下:

next[i] 表示 i(包括i)之前最长相等的前后缀长度(其实就是j)【所以next[i]=j】

next[j] 记录着j(包括j)之前的子串的相同前后缀的长度。

s[i] 模式串的后缀末尾

s[j+1] 模式串的前缀末尾】

所以,处理前后缀不相同的情况代码如下:

while (j >= 0 && s[i] != s[j + 1]) { // 前后缀不相同了
    j = next[j]; // 向前回退 #调整前缀末尾索引
}

  1. 处理前后缀相同的情况

如果 s[i] 与 s[j + 1] 相同,那么就同时向后移动i 和j 说明找到了相同的前后缀,同时还要将j(前缀的长度)赋给next[i], 因为next[i]要记录相同前后缀的长度。

代码如下:

if (s[i] == s[+ 1]) { // 找到相同的前后缀
    j++;
}
next[i] = j;

最后整体构建next数组的函数代码如下:

void getNext(int* next, const string& s){
    int j = -1;
    next[0] = j;
    for(int i = 1; i < s.size(); i++) { // 注意i从1开始#j前缀末尾i后缀末尾
        while (j >= 0 && s[i] != s[j + 1]) { // 前后缀不相同了
            j = next[j]; // 向前回退 #只改变前缀末尾索引 按照之前保存的next的值去回退
        }
        if (s[i] == s[j + 1]) { // 找到相同的前后缀
            j++;
        }
        #以上的while和if都不改变i的值
        #只是为了调整j的值
        #对于每前进1元素(i每次+1)都算出来此时对应的j
        #然后填充next
        #然后根据next的值调整j
        #j只可能小于等于i-1
        #前后缀不相同 j就回退直到找到相同的前后缀或j回归-1
        #前后缀相同 j就自增
        next[i] = j; // 将j(前缀的长度)赋给next[i]#补充next里存的最长相等前后缀的值
    }
}

代码构造next数组的逻辑流程动画如下:

得到了next数组之后,就要用这个来做匹配了。

使用next数组来做匹配

在文本串s里 找是否出现过模式串t。

定义两个下标j 指向模式串起始位置,i指向文本串起始位置。

【s的索引和i有关

t的索引和j有关】

那么j初始值依然为-1,为什么呢? 依然因为next数组里记录的起始位置为-1。

i就从0开始,遍历文本串,代码如下:

for (int i = 0; i < s.size(); i++) 

接下来就是 s[i] 与 t[j + 1] (因为j从-1开始的) 进行比较。

如果 s[i] 与 t[j + 1] 不相同,j就要从next数组里寻找下一个匹配的位置。

【本来如果s[i]与模式串不匹配的话就需要从模式串的开头开始匹配,next数组就可以避免这一点,使得s[i]可以从模式串的中间位置开始匹配】

【j表示当前匹配到的位置,但当回退的时候就表示前缀末尾的位置】

代码如下:

while(j >= 0 && s[i] != t[j + 1]) {
    j = next[j];
}

如果 s[i] 与 t[j + 1] 相同,那么i 和 j 同时向后移动, 代码如下:

if (s[i] == t[j + 1]) {
    j++; // i的增加在for循环里
}

如何判断在文本串s里出现了模式串t呢,如果j指向了模式串t的末尾,那么就说明模式串t完全匹配文本串s里的某个子串了。

本题要在文本串字符串中找出模式串出现的第一个位置 (从0开始),所以返回当前在文本串匹配模式串的位置i 减去 模式串的长度,就是文本串字符串中出现模式串的第一个位置。

代码如下:

if (j == (t.size() - 1) ) {
    return (i - t.size() + 1);
}

那么使用next数组,用模式串匹配文本串的整体代码如下:

int j = -1; // 因为next数组里记录的起始位置为-1
for (int i = 0; i < s.size(); i++) { // 注意i就从0开始
    while(j >= 0 && s[i] != t[j + 1]) { // 不匹配
        j = next[j]; // j 寻找之前匹配的位置
    }
    if (s[i] == t[j + 1]) { // 匹配,j和i同时向后移动
        j++; // i的增加在for循环里
    }
    if (j == (t.size() - 1) ) { // 文本串s里出现了模式串t
        return (i - t.size() + 1);
    }
}

此时所有逻辑的代码都已经写出来了

class Solution:
    # # KMP
    # def getNext(self, s):
    #     nextt = [0] * len(s)
    #     j = 0
    #     for i in range(1, len(s)):【寻找前缀和后缀的最长相同的长度】
    #         if s[i] == s[j]:
    #             j += 1【每次找到相同,前缀索引增1并补充next数组】
    #             nextt[i] = j
    #         while j > 0 and s[i] != s[j]:
    #             j = nextt[j - 1]
    #     return nextt
    # 以上为错误版本
    # 以下为正确版本
    # 差就差在for循环内if和while的先后顺序
    # KMP
    def getNext(self, s):
        nextt = [0] * len(s)
        j = 0
        for i in range(1, len(s)):
            while j > 0 and s[i] != s[j]:
                    j = nextt[- 1]
            if s[i] == s[j]:
                j += 1
                nextt[i] = j
    # 必须先判断不等再判断相等。先while再if。否则 若needle是'abab',本来next应该是[0012],错误版本得到的是[0010]
    # 原因在于判断s[i] == s[j]时,判断到j0(a) i2(a)相等,下一步是j自增变j1(b),然后next[2]=1
    # 然后本应i自增变i3(b)然后进入j1(b)和i3(b)相等的if判断,但错误版本直接进入j1(b)和i2(a)不等的while判断了。
    【简而言之就是错误版本不适用于连续相等的环境。若前缀末尾和后缀末尾连续相同,则只会处理第一次相同的情况。】
        return nextt
        
    def strStr(self, haystack: str, needle: str) -> int:
        nextt = self.getNext(needle)
        i = 0
        j = 0【这里初始化为0而不是之前一直说的-1
        if len(haystack) < len(needle):
            return -1
        while i < len(haystack):
            while j > 0 and haystack[i] != needle[j]:
            # while j > 0 and needle[j]: # 这他妈反而能通过55/79 为何
                j = nextt[- 1]
            # 字母不同的时候就往回退 此时i不动,j一直退,直到j退到0跳出循环,或者遇到字母相同的情况
            【此while表示回退的情况】
            if haystack[i] == needle[j]:
                i += 1
                j += 1
            # 字母相同的时候就往前走 此时i和j同步往前走
            【这个if是对字符串元素和模式串元素判断 得到i和j值的变化】
            【此if表示匹配到相同元素的情况】
            if j == len(nextt):
                return i -len(nextt)
            # 当j走完时说明得到这样的字符串了,返回i在第一个匹配项的下标
            【此if表示匹配完成的情况】
            if j == 0 and i < len(haystack):
                i += 1
            【此if表示未开始匹配的情况】
            # 当j回退到0时说明本次匹配尝试失败,i自增直到遇到下一个匹配项或字符串结束
            # 匹配成功时j才自增,匹配失败时j回退,未找到第一个匹配项时j一直在0索引处
            【这两个if是对i个j的判断 得到return与否或i值的变化】
        return -1
459.重复的子字符串

给定一个非空的字符串,判断它是否可以由它的一个子串重复多次构成。给定的字符串只含有小写英文字母,并且长度不超过10000。

示例 1:

输入: "abab"

输出: True

解释: 可由子字符串 "ab" 重复两次构成。示例 2:

输入: "aba"

输出: False示例 3:

输入: "abcabcabcabc"

输出: True

解释: 可由子字符串 "abc" 重复四次构成。 (或者子字符串 "abcabc" 重复两次构成。)

暴力的解法, 就是一个for循环获取 子串的终止位置, 然后判断子串是否能重复构成字符串,又嵌套一个for循环,所以是O(n^2)的时间复杂度。

有的同学可以想,怎么一个for循环就可以获取子串吗? 至少得一个for获取子串起始位置,一个for获取子串结束位置吧。

其实我们只需要判断,以第一个字母为开始的子串就可以【因为一个字符串s若能由一个重复的子串rep_s组成,那么s的第一个字母必须出现在子串rep_s重复的第一次,也即,字符串的第一个字母必须是rep_s的第一个字母】,所以一个for循环获取子串的终止位置就行了。 而且遍历的时候 都不用遍历结束,只需要遍历到中间位置,因为子串结束位置大于中间位置的话,一定不能重复组成字符串。

当一个字符串s:abcabc,内部由重复的子串组成,那么这个字符串的结构一定是这样的:

也就是由前后相同的子串组成。

那么既然前面有相同的子串,后面有相同的子串,用 s + s,这样组成的字符串中,后面的子串做前串,前后的子串做后串,就一定还能组成一个s,如图:

所以判断字符串s是否由重复子串组成,只要两个s拼接在一起,里面还出现一个s的话,就说明s是由重复子串组成。

不过这种解法还有一个问题,就是 我们最终还是要判断 一个字符串(s + s)是否出现过 s 的过程,大家可能直接用contains,find 之类的库函数。 却忽略了实现这些函数的时间复杂度(暴力解法是m * n,一般库函数实现为 O(m + n))。

如果我们做过 题目的话,其实就知道,实现一个 高效的算法来判断 一个字符串中是否出现另一个字符串是很复杂的,这里就涉及到了KMP算法。为何?最长相同前后缀和重复子串的关系有什么关系?其实:

再看一下前缀和后缀的定义,回顾一下:

  • 前缀是指不包含最后一个字符的所有以第一个字符开头的连续子串;
  • 后缀是指不包含第一个字符的所有以最后一个字符结尾的连续子串

由重复子串组成的字符串中(不考虑无法由重复子串组成的字符串),最长相等前后缀不包含的子串就是最小重复子串,这里拿字符串s:abababab 来举例,ab就是最小重复单位,如图所示:

数组长度为:len。

s末尾对应的next的值:最长相等前后缀的长度:next[len - 1]+1

如果len % (len - (next[len - 1] + 1)) == 0 ,则说明数组的长度正好可以被 (数组长度-最长相等前后缀的长度) 整除 ,说明该字符串有重复的子字符串。

数组长度减去最长相同前后缀的长度相当于是第一个周期的长度。类似于上例中的第一个ab。

class Solution:
    def repeatedSubstringPattern(self, s: str) -> bool:
        s = list(s)
        ss = s[1:] + s[:len(s) - 1] # 写成ss = [1:2*len(s) - 1]不对。
        # 第28题是给出字符串h(haystack)还有待匹配的子串n(needle)判断n是否存在于h中
        # 本题 h即为字符串ss(s+s然后对新字符串掐头去尾得到),n即为字符串s
        # 原因参考代码随想录视频
 
        # 下面判断ss中是否有s
        # 第一步是求s作为子串 对应的next数组
        next = self.getnext(s)
        # 第二步开始遍历ss 尝试在其中找到s
        i = 0 
        j = 0
        while i < len(ss):
            while j > 0 and ss[i] != s[j]:
                j = next[- 1]
                # 其实next的作用仅仅是 在j需要回退的时候指定j回退的位置
            if ss[i] == s[j]:
                i += 1
                j += 1
 
            if j == 0:
                i += 1
 
            if j == len(next):
                return True
 
        return False
 
        # # 如果字符串是由重复的子串组成的话,可以用以下方法求出重复的基本单位。
        # next = self.getnext(s)
        # longest = 0
        # index = 0
        # for i in range(len(next)):
        #     if next[i] > longest:
        #         longest = next[i]
        #         index = i
        # needle = ss[:len(ss) - longest] # 这样把字符串重复的基本单位就求出来了
 
    def getnext(self, s):
        next = [0] * len(s)
        j = 0
        for i in range(1, len(s)):
            while j > 0 and s[i] != s[j]:
                j = next[- 1]
            if s[i] == s[j]:
                j += 1
                next[i] = j
        return next

之后又回看代码随想录,发现其实可以不用那么复杂在ss里找s,有更简单的方法判断:

if nxt[-1] != 0 and len(s) % (len(s) - nxt[-1]) == 0:
            return True
        return False

也就是说,上面主函数里判断ss中是否有s的操作完全是kmp那道题的写法,但作为这道题来说不需要进行那样复杂的while if判断,只需要判断是否len % (len - (next[len - 1] + 1)) == 0 ,若是,则说明数组的长度正好可以被 (数组长度-最长相等前后缀的长度) 整除 ,说明该字符串有重复的子字符串。

字符串总结

双指针法

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

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

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

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

反转系列

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

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

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

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

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

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

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

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

KMP

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

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

匹配问题:28. 实现 strStr()

重复子串问题:459.重复的子字符串

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值