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

news2024/12/23 5:09:04

目录

344. 反转字符串

541. 反转字符串II

剑指Offer 05.替换空格

151.翻转字符串里的单词

剑指Offer58-II.左旋转字符串

28.实现 strStr()

459.重复的子字符串

字符串总结


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.重复的子字符串

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/727979.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

JAVA jfreechart生成柱状图

JAVA jfreechart生成柱状图 在项目资源评估中&#xff0c;也就是生成word文档里需要根据数据生成柱状图&#xff0c;在网上找到了jfreechart工具包&#xff0c;来生成柱状图&#xff0c;当然他不仅仅只能生成柱状图&#xff0c;还支持折线图、饼状图等等… 过程 导入依赖 &l…

快速创建剪映草稿

实现原理 : JianYingPro 项目文件是 json 的形式存储的,只需要创建draft_content.json,draft_mate_info.json 打开软件后会自动补全。添加一个媒体到轨道顺序 草稿媒体库 -> 内容媒体库-> 轨道片段add_media_to_track 会识别媒体类型,加入到对应轨道。当没有视频轨道时…

哈希表 基础理论

什么是哈希表&#xff1f; 哈希表英文名hash table&#xff0c;国内有一些书籍也翻译为散列表。哈希表是根据关键码的值而直接进行访问的数据结构。 直白来讲&#xff0c;其实数组就是一张哈希表&#xff0c;哈希表中关键码就是数组的索引下标&#xff0c;然后通过下标直接访…

华为云编译构建CodeArts Build新手操作指南

华为云编译构建&#xff08;CodeArts Build&#xff09;基于云端大规模并发加速&#xff0c;为客户提供高速、低成本、配置简单的混合语言构建能力&#xff0c;帮助客户缩短构建时间&#xff0c;提升构建效率。 本文将给各位开发者带来华为云CodeArts Pipeline的手把手初级教学…

亚马逊买家账号被封的原因

亚马逊封号原因有很多种情况&#xff0c;以下是一些可能导致账号被封的常见原因&#xff1a; 1、违反亚马逊的服务条款&#xff1a;亚马逊有一系列的服务条款和规定&#xff0c;如果您违反了这些规定&#xff0c;比如多次提交虚假评价、涉及欺诈行为、滥用退货政策等&#xff…

【深度学习】日常笔记9

泛化误差&#xff08;generalization error&#xff09;是指&#xff0c;模型应⽤在同样从原始样本的分布中 抽取的⽆限多数据样本时&#xff0c;模型误差的期望。考虑对掷硬币的结果&#xff08;类别0&#xff1a;正⾯&#xff0c;类别1&#xff1a;反⾯&#xff09;进⾏分类的…

AIGC - Stable Diffusion 图像控制插件 ControlNet (OpenPose) 配置与使用

欢迎关注我的CSDN&#xff1a;https://spike.blog.csdn.net/ 本文地址&#xff1a;https://spike.blog.csdn.net/article/details/131591887 论文&#xff1a;Adding Conditional Control to Text-to-Image Diffusion Models ControlNet 是神经网络结构&#xff0c;用于控制预…

CentOS7安装详细安装

CentOS 7镜像下载 官网下载链接&#xff1a;http://isoredirect.centos.org/centos/7/isos/x86_64/ step1: 进入下载页&#xff0c;选择阿里云站点进行下载 Actual Country 国内资源 Nearby Countries 周边国家资源 阿里云站点&#xff1a;http://mirrors.aliyun.com/cento…

开源微服务框架是什么?看完这篇文章就知道了

随着低代码开发平台的快速发展&#xff0c;企业实现流程化管理的愿望指日可待。开源微服务框架是什么&#xff1f;都有哪些特点和优势&#xff1f;作为企业&#xff0c;想要提高办公协作效率&#xff0c;做好数据管理&#xff0c;应用专用的开发平台可以少走弯路&#xff0c;创…

【电子量产工具】6. 业务系统

文章目录 前言一、业务系统分析二、处理配置文件三、生成界面四、根据输入事件找到按钮五、业务系统总流程测试测试效果&#xff1a;总结 前言 最近看了 电子量产工具 这个项目&#xff0c;本专栏是对该项目的一个总结。 一、业务系统分析 前面实现了各个子系统&#xff1a;显…

【Java项目】Vue+ElementUI+Ceph实现多类型文件上传功能并实现文件预览功能

文章目录 效果演示前端后端Java 效果演示 先说一下我们的需求&#xff0c;我们的需求就是文件上传&#xff0c;之前的接口是只支持上传图片的&#xff0c;之后需求是需要支持上传pdf&#xff0c;所以我就得换接口&#xff0c;把原先图片上传的接口换为后端ceph&#xff0c;但是…

MV-Map论文研读

MV-Map MV-Map: Offboard HD-Map Generation with Multi-view Consistency 论文&#xff1a;https://arxiv.org/pdf/2305.08851.pdf code&#xff1a;https://github.com/ZiYang-xie/MV-Map 代码未开源 总体网络结构 简述 论文首次提出以非车载的方式产生高精度地图。可以…

基于QT使用7z压缩与解压总结

1. 概述 本文主要讲述使用7z第三方工具对文件或文件夹进行加密压缩和解密解压相关方法。7z的全称7-Zip&#xff0c;是一款开源软件。&#xff08;资源主页&#xff1a;https://7-zip.org/&#xff09;2. 设计原理 本文主要使用7z.exe通过命令行来实现压缩与解压功能&…

数据库之MySQL字符集与数据库操作

目录 字符集 CHRARCTER SET 与COLLATION的关联 CHRARCTER SET 定义 基础操作 查看当前MySQL Server支持的 CHARACTER SET 查看特定字符集信息&#xff08;主要包含默认的COLLATION 与 MAXLEN&#xff09; COLLATION 定义 COLLATION后缀 基础操作 查看MySQL Server支持的…

C++教程(一)开发环境visual studio的安装——图文详细

一、visual studio下载地址&#xff1a; 1、百度网盘 链接&#xff1a;https://pan.baidu.com/s/1QJosSoAT7EumuvyjtC_1Iw?pwdwuqz 提取码&#xff1a;wuqz 2、官网下载 Visual Studio: 面向软件开发人员和 Teams 的 IDE 和代码编辑器 (microsoft.com)https://visualstudio.…

【Linux】vi编辑器的使用,要求能新建、编辑、保存一个文本文件。

&#xff08;1&#xff09;点击”应用程序”→ “附件”→“终端”&#xff0c;打开终端&#xff0c;在终端输入命令&#xff1a; [rootlocalhost root]#vi kk.c按 i 键&#xff0c;进入插入状态。 &#xff08;2&#xff09;输入以下C程序 #include<stdio.h>int main( …

【CEEMDAN-WOA-LSTM】完备集合经验模态分解-鲸鱼优化-长短时记忆神经网络研究(Python代码实现)

&#x1f4a5;&#x1f4a5;&#x1f49e;&#x1f49e;欢迎来到本博客❤️❤️&#x1f4a5;&#x1f4a5; &#x1f3c6;博主优势&#xff1a;&#x1f31e;&#x1f31e;&#x1f31e;博客内容尽量做到思维缜密&#xff0c;逻辑清晰&#xff0c;为了方便读者。 ⛳️座右铭&a…

Java用native修饰的方法

今天看JDK ServerSocket源代码的时候&#xff0c;通过层层调用&#xff0c;到了用native修饰的方法&#xff0c;然后再也跟不下去了。 例如sun.nio.ch.Net类中下面的方法&#xff1a; Java方法如果用native修饰&#xff0c;就表示这个方法的实现不是用java实现的&#xff0c…

Element Ui Tree组件实现增、删、改、查、拖拽节点 的树形结构

介绍&#xff1a;首先组件 | Element官网某些功能都具备了&#xff0c;这里我就把这些功能结合在一起更完美的使用&#xff0c;其次编辑节点官网是没有实例&#xff0c;所以这里搞了一套较完整的功能&#xff0c;其次编辑和添加&#xff0c;这里直接使用了弹窗&#xff08;顾及…

单位列表单列出来,假(封装)组件

效果图&#xff1a; 因为每个页面都用到这个单位&#xff0c;所以把单位列表单列出来&#xff0c;假装是个封装的组件&#xff0c;在其他页面直接用。 源码&#xff1a; <template><div style"height: 48rem;overflow-y: scroll"><h4>单位列表<…