1.字符串
写在前面
本系列笔记主要作为笔者刷题的题解,所用的语言为Python3
,若于您有助,不胜荣幸。
字符串类题目的思路在算法性的思路上和数组类题目的思路类似,具体需要注意的就是,字符串在不同编程语言中的特性,合理利用在不同语言中的特性有效完成题目的要求是十分重要的。
1.1 反转字符串
编写一个函数,其作用是将输入的字符串反转过来。输入字符串以字符数组 s
的形式给出。
不要给另外的数组分配额外的空间,你必须**原地修改输入数组**、使用 O(1) 的额外空间解决这一问题。
解法一:双指针法
class Solution:
def reverseString(self, s: List[str]) -> None:
"""
Do not return anything, modify s in-place instead.
"""
left: int = 0
right: int = len(s) - 1
while left < right:
s[left], s[right] = s[right], s[left] # python的特性,使得我们可以不需要中间变量temp就可以交换两个元素的值
left += 1
right -= 1
解法二:使用栈
class Solution:
def reverseString(self, s: List[str]) -> None:
"""
Do not return anything, modify s in-place instead.
"""
stack: List = []
for char in s:
stack.append(char)
for i in range(len(s)):
s[i] = stack.pop()
1.2 反转字符串II
给定一个字符串 s
和一个整数 k
,从字符串开头算起,每计数至 2k
个字符,就反转这 2k
字符中的前 k
个字符。
- 如果剩余字符少于
k
个,则将剩余字符全部反转。 - 如果剩余字符小于
2k
但大于或等于k
个,则反转前k
个字符,其余字符保持原样。
双指针法
class Solution:
def reverseChar(self, s: List[str]) -> List[str]:
left: int = 0
right: int = len(s) - 1
while left < right:
s[left], s[right] = s[right], s[left]
left += 1
right -= 1
return s
def reverseStr(self, s: str, k: int) -> str:
res: List[str] = list(s)
for cur in range(0, len(s), 2*k):
res[cur:cur+k] = self.reverseChar(res[cur:cur+k]) # python中切片长度超过可迭代对象的长度,会默认将切片的长度限制到可迭代对象的长度内
return ''.join(res) # 使用''作为连接符,连接res中的所有元素
1.3 替换数字
给定一个字符串 s,它包含小写字母和数字字符,请编写一个函数,将字符串中的字母字符保持不变,而将每个数字字符替换为number。 例如,对于输入字符串 “a1b2c3”,函数应该将其转换为 “anumberbnumbercnumber”。
class Solution:
def replaceNumber(self,s:str)->str:
res: List[str] = list(s)
for i in range(len(res)):
if res[i] in ['0', '1', '2', '3', '4', '5', '6', '7', '8', '9']:
res[i] = 'number'
return ''.join(res)
class Solution:
def replaceNumber(self,s:str)->str:
res: List[str] = list(s)
for i in range(len(res)):
if res[i].isdigit(): # str的isdigit()方法可以判断字符串是否为数字
res[i] = 'number'
return ''.join(res)
1.4反转字符串中的单词
解法一:双指针
class Solution:
def reverseWords(self, s: str) -> str:
res: List[str] = [word.strip() for word in s.split()] # 分割单词且除去多余空格
left: int = 0
right: int = len(res) - 1
while left < right:
res[left], res[right] = res[right], res[left]
left += 1
right -= 1
return ' '.join(res)
解法二:pythonic
def reverseWords(self, s: str) -> str:
res: List[str] = [word.strip() for word in s.split()] # 分割单词且除去多余空格
return ' '.join(res[::-1])
解法三:使用快慢指针手搓removeSpaces
class Solution:
def removeSpaces(self, s: str) -> List[str]:
res: List[str] = []
slowIndex: int = 0
fastIndex: int = 0
while fastIndex < len(s) and s[fastIndex] == " ": # 移除开头的空格
fastIndex += 1
while fastIndex < len(s):
if s[fastIndex] != ' ':
if slowIndex != 0 and res[slowIndex - 1] == ' ':
res.append('') # 在单词之间添加空格
slowIndex += 1
wordStart: int = fastIndex
while fastIndex < len(s) and s[fastIndex] != ' ': # 寻找单词结束位置
fastIndex += 1
res.append(s[wordStart:fastIndex]) # 保存单词
slowIndex += 1
else:
fastIndex += 1
return res
def reverseWords(self, s: str) -> str:
res: List[str] = self.removeSpaces(s)
left: int = 0
right: int = len(res) - 1
while left < right:
res[left], res[right] = res[right], res[left]
left += 1
right -= 1
return ' '.join(res)
1.5右旋字符串
字符串的右旋转操作是把字符串尾部的若干个字符转移到字符串的前面。给定一个字符串 s 和一个正整数 k,请编写一个函数,将字符串中的后面 k 个字符移到字符串的前面,实现字符串的右旋转操作。
例如,对于输入字符串 “abcdefg” 和整数 2,函数应该将其转换为 “fgabcde”。
class Solution:
def rotateStr(self,s:str,n:int)->str:
res: List[str] = list(s)
for _ in range(n):
res.insert(0, res.pop())
return ''.join(res)
1.6KMP算法
KMP算法中关键的点是前缀表,前缀表的作用是用来回退的,它记录了在模式串和文本串不匹配的时候,模式串应该从哪里开始重新匹配。
还有一个重要的概念是最长公共前后缀,字符串的前缀指的是不包含最后一个字符的所有以第一个字符开头的连续子串。后缀指的是不包含第一个字符的所有以最后一个字符结尾的连续子串。我们通常用next
数组来表示前缀表。
给你两个字符串 haystack
和 needle
,请你在 haystack
字符串中找出 needle
字符串的第一个匹配项的下标(下标从 0 开始)。如果 needle
不是 haystack
的一部分,则返回 -1
。
解法一:KMP法
在KMP解法中,我们主要包含以下的步骤
- 初始化
- 生成前缀表
next
- 遍历字符串,并且使用前缀表
next
来进行回溯
我们需要先生成一个前缀表
用来回溯,那如何生成前缀表呢?我们需要一个前缀末尾指针j
和一个后缀末尾指针i
来帮助我们完成前缀表的生成,我们通过对比前缀末尾指针j
和后缀末尾指针i
是否相同来获得整个前缀表
class Solution:
def getNext(self, s:str) -> List[int]:
"""
获取前缀表
j: 前缀末尾
i: 后缀末尾
"""
j: int = 0
next: List[int] = [0] * len(s) # 初始化前缀表
for i in range(1, len(s)):
while j>0 and s[i] != s[j]: # 对前缀末尾j进行回溯
j = next[j-1]
if s[i] == s[j]:
j += 1
next[i] = j
return next
def strStr(self, haystack: str, needle: str) -> int:
j: int = 0
next: List[int] = self.getNext(needle)
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 - len(needle) + 1
return -1
解法二:暴力法
class Solution:
def strStr(self, haystack: str, needle: str) -> int:
m, n = len(haystack), len(needle)
for i in range(m):
if haystack[i:i+n] == needle:
return i
return -1
1.7重复的子字符串
给定一个非空的字符串 s
,检查是否可以通过由它的一个子串重复多次构成。
解法一:暴力法
class Solution:
def repeatedSubstringPattern(self, s: str) -> bool:
n: int = len(s)
if n <= 1:
return False
for i in range(1, n//2+1): # 索引只需要遍历一半
if n % i == 0:
substr = s[:i]
if substr * (n//i) == s:
return True
return False
解法二:KMP法
class Solution:
def getNext(self, s: str) -> List[int]:
j: int = 0 # 前缀指针
next: List[int] = [0] * len(s)
for i in range(1, len(s)): # 后缀指针
while j>0 and s[i] != s[j]: # 使用前缀表进行回溯
j = next[j-1]
if s[i] == s[j]:
j += 1
next[i] = j
return next
def repeatedSubstringPattern(self, s: str) -> bool:
next: List[int] = self.getNext(s)
if next[-1] != 0 and len(s) % (len(s) - next[-1]) == 0:
return True
else:
return False