- LeetCode中的替换空格 题目
可以预先给数组扩容到带填充后的大小,然后从后向前进行操作
上述做法的好处:1. 不用申请新的数组; - 从后向前填充元素,避免了从前向后填充元素要每次添加元素都要将添加元素之后的所有元素向后移动。
3. 其实很多数组填充类的问题,都可以先预先给数组扩容带填充后的大小,然后在从后向前进行操作。
需要注意的是:在python和Java中,字符串被设计为不可变的类型,也就是无法直接修改字符串的某一字符,需要新建一个字符串才能实现。
但是在C++当中,string被设计为可变的类型,因此可以在不新建字符串的情况下实现原地修改
class Solution:
def replaceSpace(self, str):
res = []
for c in str:
if c == ' ':
res.append("%20")
else:
res.append(c)
return "".join(res)
# 上面这一句是将列表转成字符串的方法,很多地方都会用到
复杂度分析:
时间复杂度 O(N) : 遍历使用 O(N) ,每轮添加(修改)字符操作使用 O(1) ;
空间复杂度 O(N) : Python 新建的 list 和 Java 新建的 StringBuilder 都使用了线性大小的额外空间。
但是在C++当中的空间复杂度为O(1),因为是原地扩展字符串的长度,所有使用O(1)的额外空间。
leetcode中的第155题,翻转字符串里的单词:
很多语言提供了split,reverse,join等针对字符串的方法,所以我们可以调用简单的内置的API来完成操作,但是我们也可以不使用这些语言中的API,而是自己编写对应的函数,在python中字符串不可变,而在cpp中字符串是可变的,这就导致了不同的语言实现API的不同方法。
对于字符串不可变的语言,首先需要把字符串转化为其它可变的数据结构。同时需要在转化的过程中去除空格
以下是我学习到的很巧妙地去除空格的方式:
def trim_space(self,s1):
left = 0
right = len(s1) - 1
# 去掉字符串开头的空白字符
while left <= right and s[left] == ' ':
left += 1
# 去掉字符串末尾的空白字符
while left <= right and s[right] == ' ':
right -= 1
# 经过上面的两个while循环,现在left和right已经都移动到了出现字符的位置上了
output = []
while left <= right:
if s[left] != ' ':
output.append(s[left])
elif output[-1] != ' ':
# 这句代码的意思是,中间只能保留一个空格,再多的空格就不能加进来了
output.append(s[left])
left += 1
很巧妙地反转字符串中一整单个word的方式:
def reverse_each_word(self, l: list) -> None:
n = len(l)
start = end = 0
while start < n:
# 循环至单词的末尾
while end < n and l[end] != ' ':
#判断最后的end是否为 “ ” ,
#是因为单词与单词之间以空格来划分,
#当l[end] != ' '时,说明已经遍历到了一个单词的末尾,可以开始对这个单词进行翻转了
end += 1
# 翻转单词
self.reverse(l, start, end - 1)
# 更新start,去找下一个单词
start = end + 1
end += 1
反转的常见写法:
def reverse(self,l,left,right):
while left < right:
l[left], l[right] = l[right], l[left]
left, right = left + 1, right - 1
list_s[i:(i + k)] = self.reverse(list_s[i:(i + k)])
#莫非跟这行代码有关系?反转之后要不要赋予给一个新的链表
结合206题中的链表反转:
def reverse(self,pre,cur):
if cur == None:
return pre
while (cur != None):
# 递归法,要学习递归在python中的写法
# cur = head
# pre = None
# temp = None
temp = cur.next
cur.next = pre
# pre = cur
# cur = temp
return self.reverse(pre,cur)
KMP
要知道在不匹配的那个位置的前面的字符串中,的最长的相等前后缀是多少,是多少的长度,就跳到模式串的那个下标的地方。
前缀表是怎么计算得到的
将字符串进行拆解,比如aabaaf这个字符串,a的最长相等前后缀为0,aa为1,aab为0,aaba为1,aabaa为2,aabaaf为0,那么这个0,1,0,1,2,0,就构成了与模式串对应的前缀表。那么这个前后缀的数字的意义也就出来了,它表示的是在字符串的这个位置相等前后缀的长度。
很多代码里会出现next数组和prefix,原理相同,只是具体的实现,比如把我们上述得到的前缀表整体减1,变成【-1,0,-1,0,1,-1】;或者是整体将前缀表右移,开头的地方赋值为-1,即变成【-1,0,1,0,1,2】.
**在一个串中查找是否出现过另一个串,这是KMP的看家本领,「前缀表里的数值代表着就是:当前位置之前的子串有多大长度相同的前缀后缀。」搞清楚上述这个字符串的next数组说明你的KMP算法就明白啦!
**
- 设查询串的的长度为 n,模式串的长度为 m,我们需要判断模式串是否为查询串的子串。那么使用 KMP 算法处理该问题时的时间复杂度是多少?在分析时间复杂度时使用了哪一种分析方法?
- 时间复杂度为 O(n+m),用到了均摊分析(摊还分析)的方法。
具体地,无论在预处理过程还是查询过程中,虽然匹配失败时,指针会不断地根据 next数组向左回退,看似时间复杂度会很高。但考虑匹配成功时,指针会向右移动一个位置,这一部分对应的时间复杂度为 O(n+m)O。又因为向左移动的次数不会超过向右移动的次数,因此总时间复杂度仍然为 O(n+m)O。
-
如果有多个查询串,平均长度为 n,数量为 k,那么总时间复杂度是多少?
-
时间复杂度为 O(nk+m)。模式串只需要预处理一次。
-
在 KMP 算法中,对于模式串,我们需要预处理出一个next数组。这个数组到底表示了什么?
-
next 等于满足下述要求的 x 的最大值:s[0:i] 具有长度为x+1 的完全相同的前缀和后缀。这也是 KMP 算法最重要的一部分。