一、基础知识
1. 字符串与字符变量:字符串每一个位置上的元素都是一个字符变量。Python 使用 str
对象来代表字符串,为一种不可变类型对象。在定义之后无法更改字符串的长度,也无法改变或删除字符串中的字符。
2. 字符串的比较:
1)依次比较对应位置上的字符编码大小,可以用ord()函数来返回对应字符的ascii码来实现
2)若前几位字符都相同,则比较长度(误区:不能先比较长度!)
3. 存储方法
1)顺序存储:根据下标访问 s[i]
2)链式存储:字符串的链节点包含一个用于存放字符的 data
变量,和指向下一个链节点的指针变量 next
通常情况下,链节点的字符长度为 1
或者 4
,这是为了避免浪费空间。当链节点的字符长度为 4
时,由于字符串的长度不一定是 4
的倍数,因此字符串所占用的链节点中最后那个链节点的 data
变量可能没有占满,我们可以用 #
或其他不属于字符集的特殊字符将其补全。
代码实现
1. 如果在将所有大写字符转换为小写字符、并移除所有非字母数字字符之后,短语正着读和反着读都一样。则可以认为该短语是一个 回文串 。
class Solution:
def isPalindrome(self, s: str) -> bool:
i=0
j=len(s)-1
while (i<len(s))&(j>=0):
if not s[i].isalnum():
i+=1
continue
if not s[j].isalnum():
j-=1
continue
if (s[i].lower()==s[j].lower()):
i+=1
j-=1
else:
return False
2.
给定一个字符串 s,
将字符串中每个单词的字符顺序进行反转,同时仍保留空格和单词的初始顺序。
lass Solution:
def reverseWords(self, s: str) -> str:
i=0
r=[]
while i <len(s):
j=i
print(j)
while s[j]!=' ':
j+=1
if j == len(s):
break
for k in range(i,j):
r.append(s[i+j-k-1])
if j != len(s):
r.append(" ")
i=j+1
return ''.join(r)
二、查找算法——字符串匹配问题
1. 单模式串匹配:从文本串T找出特定模式串 p 的所有出现位置。
1)基于前缀搜索方法:从前往后遍历文本串T,在搜索窗口内从前向后逐个读入字符,找窗口中文本和模式串的最长公共前缀:KMP
算法、 Shift-Or
算法
Brute Force 算法:BF 算法、暴力匹配算法、朴素匹配算法。逐个比较字符,每次向后移动一位。
最坏情况:总的比较次数为
m * (n - m + 1)
。时间复杂度为 O(m∗n)。
def BF(T: str, p: str) -> int:
n, m =len(T), len(p)
i, j= 0, 0 # 双指针,分别表示在文本串中的位置和在模式串中的位置
while i < n and j < n:
if T[i] == p[j]:
i+=1
j+=1
else:
i=i-j+1
j=0
if j == m:
return i-j
else:
return -1
KMP 算法:主串的某个子串等于模式串的某个前缀。若该前缀的前k个字符恰好等于后k个字符,则不用将i回退到搜索窗口第一个字符的位置,而是让它直接对准该模式串前缀的第k+1个字符,继续进行比对。
- 用next数组来确定k:
next[j]
表示的含义是:记录下标 j 之前(包括 j)的模式串p
中,最长相等前后缀的长度。ABCAB的next[3]=1, next[4]=2。- 在构造前缀表阶段的时间复杂度为 O(m);在匹配阶段的时间复杂度是O(n),
next数组生成(真的挺难懂的,特别是left=next[left-1])
def generateNext(p:str):
m=len(p)
next=[0 for _ in range(m)]
left=0
for right in range(1,m):
while left>0 and p[left]!=p[right]:
left=next[left-1]
if p[left]==p[right]:
left+=1
next[right]=left
return next
KMP查找:
def kmp(s:str, p:str):
n,m =len(s), len(p)
next=generateNext(p)
j=0
for i in range(n):
# 这里一定要用while不能用if,因为要不断前推直至为0或相等
while j >0 and s[i]!=p[j]:
j=next[j-1]
if s[i]==p[j]:
j+=1
if j==m:
return i-m+1
return -1
2)基于后缀搜索方法:同样从前往后遍历文本串T,但在搜索窗口内从后向前逐个读入字符,找窗口中文本和模式串的最长公共后缀。使用这种搜索算法可以跳过一些文本字符,从而具有亚线性的平均时间复杂度。eg:
3)基于子串搜索方法:同样从前往后遍历文本串T,但在搜索窗口内从后向前逐个读入字符,找窗口中满足「既是窗口中文本的后缀,也是模式串的子串」的最长字符串。同样具有亚线性的平均时间复杂度:Rabin-Karp
算法使用了基于散列的子串搜索算法。
Rabin Karp 算法:比较T 所有长度为m的子串的哈希值与p是否相等,若相等再逐位比较(避免哈希冲突的出现)。优点还在于可以根据上一个子串的哈希值,快速计算相邻子串的哈希值,从而使得每次计算子串哈希值的时间复杂度降为了 O(1)——滚动哈希算法:
- 假设给定的字符串的字符集中只包含
d
种字符,那么我们就可以用一个d
进制数表示子串的哈希值。- 则右移一位的新子串的哈希值为
- 为了避免哈希值过大溢出,最后还需要对其取一个较大质数q的模,这样可以尽量减少哈希冲突。
def rk(T: str, p: str, d, q):
m = len(p)
n = len(T)
if n < m :
return -1
# 计算模式串和文本串第一个子串的哈希值
hash_p, hash_sub_T = 0, 0
for j in range(m):
hash_p=( ord(p[j])+hash_p*d ) % q
hash_sub_T=( ord(T[j])+hash_sub_T *d ) % q
i, k=0, 0
Do:
if hash_p == hash_sub_T:
match = True
for j in range(m):
if T[i+j] != p[j]:
match = False
break
if match:
return i
i+=1
hash_sub_T = (hash_sub_T - power * ord(T[i-1])) % q # 移除字符 T[i-1]
hash_sub_T = (hash_sub_T * d + ord(T[i - 1 + m])) % q # 增加字符 T[i - 1 + m]
hash_sub_T = (hash_sub_T + q) % q # 确保 hash_sub_T >= 0
Loop While i+k < n
return -1
2. 多模式串匹配问题:从文本串T找出一组模式串P= p1,p2,... 的所有出现位置。
查找算法代码实现
1. 给定一个非空的字符串 s
,检查是否可以通过由它的一个子串重复多次构成。
KMP做法:
我的做法:
class Solution:
def repeatedSubstringPattern(self, str: str) -> bool:
l=len(str)
for s in range(2, l+1):
if l % s ==0:
p=l//s
sub=str[0:p]
for k in range(1,s):
pp=p*k
match=True
for i in range(p):
sub_match= True
if str[pp+i]!=sub[i]:
sub_match=False
break
if sub_match is False:
match=False
break
if match:
return True
return False
(未完待续)