代码随想录算法训练营第八天(字符串篇)|Leetcode151翻转字符串中的单词,卡码网右旋转字符串,Leetcode28实现Strstr(),Leetcode459重复的子字符串
Leetcode 151 翻转字符串中的单词
题目链接: 翻转字符串中的单词
给定一个字符串,如 " The sky is blue",其行首,行尾和单词之间可能存在一个或者多个空格。你需要删去多余空格后,将字符串中单词进行反转,如此处反转后的结果为 "blue is sky The"
思路: 整体而言,实现分为两步:一是先删去多余空格,确保行首,行尾处无空格,且单词之间的空格数量为一个。二是反转单词,先将字符串整体反转,再逐个单词进行反转即可达成目的。
具体代码实现:
# 不使用库函数
class Solution:
def reverseSingleWord(self, result: List, left: int, right: int):
while left < right:
result[left], result[right] = result[right], result[left]
left += 1
right -= 1
def removeExtraSpaces(self, s: str) -> str:
result = ''
idx = 0
while idx < len(s):
if s[idx] != ' ':
if len(result) != 0: # 手动为单词之间添加一个空格
result += ' '
while idx < len(s) and s[idx] != ' ':
result += s[idx]
idx += 1
else:
idx += 1
return result
def reverseWords(self, s: str) -> str:
s = self.removeExtraSpaces(s)
s = s[::-1]
result = list(s) # 将不可变的字符串转换为可变的链表,方便直接修改元素
slow, fast = 0, 0
while fast < len(s):
if s[fast] == ' ':
self.reverseSingleWord(result, slow, fast-1)
slow = fast + 1
fast += 1
else:
fast += 1
self.reverseSingleWord(result, slow, fast-1) # 反转最后一个单词
return ''.join(result)
# 使用库函数
class Solution:
def reverseWords(self, s: str) -> str:
s = s.strip()
words = s[::-1].split()
for i in range(len(words)):
words[i] = words[i][::-1]
return ' '.join(words)
右旋转字符串
题目链接: 右旋转字符串
字符串的右旋转操作是把字符串尾部的若干个字符转移到字符串的前面。给定一个字符串 s
和一个正整数 k
,请编写一个函数,将字符串中的后面 k
个字符移到字符串的前面,实现字符串的右旋转操作。
例如,对于输入字符串 "abcdefg" 和整数 2,函数应该将其转换为 "fgabcde"。
思路: 我们可以通过先反转字符串整体,再反转相应部分达成目的。如对于 "abcdefg" 右旋2位的情况,反转整体后得到 "gfedcba" ,随后再分别反转前2位和剩余部分,得到 "fgabcde" ,即目标结果。通过归纳总结我们可以知道,先反转字符串整体,再将字符串前k位和剩余部分分别进行反转即可得到结果
具体代码实现:
import sys
def right_spin_str(s: str, n: int) -> str:
s = s[::-1]
s = s[:n][::-1] + s[n:][::-1]
return s
def main():
input = sys.stdin.readline().strip()
right_spin_str(input, 3)
Leetcode 28 实现Strstr()
题目链接: 实现Strstr()
给定两个字符串,"haystack" 和 "needle",实现strStr()函数,找出 "needle" 在 "haystack" 中的第一个匹配项的下标。
如果 "needle" 不存在于 "haystack" 中,返回 -1。
思路: 若直接通过暴力解法,通过双层for循环进行遍历,则时间复杂度为 O(m*n)
。
本题的重点在于理解并运用KMP算法,实现 O(n)
时间复杂度内的快速匹配。
KMP算法的重点在于前缀表,对于我们待搜寻的子串,我们会为其构建一个相应的前缀表。前缀表 prefix
是一个数组,其中元素的值 prefix[j]
的含义为: 子串中下标0到j部分处,最长相等前后缀的长度。
最长相等前后缀是什么呢?顾名思义,前缀即从字符串首开始,不断向后扩展的子串(不包括字符串尾元素),都可以算作前缀,后缀同理。
举例来说:aabaa
的前缀包括 a
, aa
, aab
, aaba
;后缀包括a
, aa
, baa
, abaa
。因此,最长相等前后缀为 aa
,最长相等前后缀的长度为 2
给定一个子串,我们演示求出其前缀表的过程。如对于子串 aabaaf
:
prefix[0]
= 0,因为 a
无前缀,也无后缀,最长相等前后缀的长度为0,因此 prefix[0] = 0
。
prefix[1]
= 1,aa
最长相等前后缀为 a
,最长相等前后缀的长度为1
prefix[2]
= 0, aab
不存在相等前后缀,最长相等前后缀的长度为0
prefix[3]
= 1, aaba
最长相等前后缀为 a
, 最长相等前后缀的长度为1
prefix[4]
= 2, aabaa
最长相等前后缀为 aa
, 最长相等前后缀的长度为2
prefix[5]
= 0, aabaaf
不存在相等前后缀, 最长相等前后缀的长度为0
有了前缀表,我们可以构造 next
数组。什么是 next
数组?可以理解为在进行子串匹配时,若目标串 "haystack" 和子串 "needle" 相应元素不匹配时,next
数组中记录了跳转到子串 "needle" 对应元素处的下标,通过对应元素继续尝试与目标串 "haystack" 当前元素进行匹配,而不是从子串头部重新开始匹配。这样就减少了重复匹配。
next
数组的构造和KMP算法的具体实现有多种方式,此处给出 next
数组中元素统一保持原样的实现方式,相应 next
数组构造代码如下:
def getNext(s: str):
next = [0] * len(s)
# i 为当前前缀末尾指针, j 为当前后缀末尾指针
i = 0
for j in range(1, len(s)):
while i>0 and s[j] != s[i]: # 当前前缀末尾元素与后缀末尾元素不匹配,不断尝试回退
i = next[i-1]
if s[j] == s[i]: # 当前元素相同,最长相等前后缀的长度加1
i += 1
next[j] = i
return next
有了 next
数组,我们可以据此进行字符串匹配。
具体代码实现
class Solution:
def getNext(self, s: str) -> List[int]:
next = [0] * len(s)
i = 0
for j in range(1, len(s)):
while i>0 and s[i] != s[j]:
i = next[i-1]
if s[i] == s[j]:
i += 1
next[j] = i
return next
def strStr(self, haystack: str, needle: str):
if len(needle) == 0:
return 0
next = self.getNext(needle)
j = 0
for i in range(len(haystack)):
while j>0 and haystack[i] != needle[j]:
j = next[j-1]
if haystack[i] == needle[j]:
j += 1
if j == len(needle):
return i-j+1
return -1
时间复杂度: O(m+n)
Leetcode 459 重复的字符串
题目链接: 重复的字符串
给定一个字符串 s
,判断该字符串是否由重复的子串构成。
如 abcabcabc
由子串 abc
构成
思路: 有两种思路,此处一并给出。
第一种思路是: 对于字符串 s
, 构造一个字符串 s+s
,随后再去掉新字符串中头元素和尾元素,判断新字符串中剩余部分里是否仍然存在字符串 s
,若存在,则说明字符串 s
有重复的子串构成。
第二种思路是:对于字符串 s
,求其前缀表,找出其最长相等前后缀的长度。若字符串本身的长度是字符串减去最长相等前后缀以后剩余部分的长度的整数倍,则说明字符串由重复子串构成
具体代码实现
# 思路一
class Solution:
def repeatedSubstringPattern(self, s: str) -> bool:
newS = s+s
cut = newS[1:len(newS)-1]
if s in cut:
return True
return False
# 思路二
class Solution:
def getNext(self, s: str) -> List[int]:
next = [0] * len(s)
i = 0
for j in range(len(s)):
while i>0 and s[j] != s[i]:
i = next[i-1]
if s[j] == s[i]:
i += 1
next[j] = i
return next
def repeatedSubstringPattern(self, s: str) -> bool:
next = self.getNext(s)
if next[-1] != 0 and len(s) % (len(s)-next[-1]) == 0:
return True
return False