数组-链表-哈希表-字符串
- 数组章节
- 二分查找
- 搜索插入位置
- 在排序数组中查找元素的第一个和最后一个位置
- x 的平方根
- 有效的完全平方数
- 移除元素
- 删除有序数组中的重复项
- 移动零
- 比较含退格的字符串
- 有序数组的平方
- 长度最小的子数组
- 水果成篮
- 最小覆盖子串
- 不会不会不会,好难的题。本题的关键在于想明白:如何表示字符串?怎么知道 s 的子串,是否包含 t ? 统计 t 中每一个字母的个数,用字典或者哈希表!因为已经说明,t 只由英文字母组成,所以可以哈希表。字典要学会用defaultdict,方便太多。另外,注意子串是有“顺序”在里面的,比如“ACAB”是“ABC”的最短子串,当 s 串移动到B时,加入了B,此时向左的循环可以移除一个“A”,然后指向C,此时C的值是0,那么如果左指针还能继续移动,就要在后边再找一个C,比如“CABABC”,前面的AB虽然也是 t 的字符,但是都不可作为退出寻找的条件,只能在字典中先行记录,当找到下一个C时,才可以左移。为了达成上述目的,就要在"CAB"时,给C的值+1,然后left+1 , 让 left 指向A 。这样目前字典里只有一个值大于0,就是C,所以才会只有当向右找到一个C时,再进入移动左指针的流程。
- 我妄图把0的逻辑加入到 if 判断里,就是当前字符值为0,说明这个字符是 t 中字符,不像上面的代码那么处理,因为左移后的最后两行代码确实不好想,就让值从0的基础上继续减一,但是我发现,如果是这种逻辑,会伴随着其他非常多的Bug,其中某些逻辑存在冲突,写不出来。
- 螺旋矩阵II
- 螺旋矩阵
- 剑指Offer 29.顺时针打印矩阵
- 移除链表元素
- 构造链表
- 写的真是磕磕绊绊啊,主要是我不知道怎么给链表初始化合适,然后添加头,添加尾的操作,都要考虑到:如果当前链表是一个只初始化后的链表,就是当前的头结点是假的头结点,那么不管是加入头结点操作,还是加入尾结点操作,本质上都是:对链表头结点的初始化。
- 看了卡哥的解答,我只能说,这也行?因为题目只要求实现上述操作,所以不用管,当前的链表,按照self.head一直遍历得到的结果数组是什么样的!比如,加入头,那么只要头结点的值是正确的就可以了,不管尾结点是什么值!其他操作同理,同时又因为题目给定,所有节点的值大于0,所以搞一个值是0的“头结点”无所谓(当然随着后续操作,这个“头结点”就不知道跑到什么位置上去了)。还有一个值得学习的地方,在init的地方,多定义一个链表长度的变量。
- 有点意思,卡哥的代码中,假如现在有一个虚拟头,再执行加入头结点操作,按照其代码,链表现在为:虚拟头->加入头,如果内核想建言是否正确,是去调用get(0) ? 在 get 方法中,是先让 cur = dummy_head.next 。
- 我的代码这么复杂,是我保证了每次操作后,按照self.head进行遍历,得到的链表就是正确的链表。但其实我还是觉得我这么写是更好的。
- 反转链表
- 两两交换链表中的节点
- 删除链表的倒数第 N 个结点
- 链表相交
- 环形链表II
- 有效的字母异位词
- 赎金信
- 字母异位词分组
- 找到字符串中所有字母异位词
- 两个数组的交集
- 两个数组的交集 II
- 快乐数
- 两数之和
- 四数相加 II
- 三数之和
- 四数之和
- 反转字符串
- 反转字符串II
- 替换空格
- 反转字符串中的单词
- 左旋转字符串
- 接下来这两道,是KMP算法的经典题目了,但是我现在已经忘了KMP是什么了。
- 什么是前缀表(即:next数组):记录下标 i 之前 (包括 i ) 的字符串中,有多大长度的相同前缀后缀。
- 注意一定要理解,前缀和后缀的定义,定义明确是正确计算前缀表的关键。
- 为什么当字符不匹配时,找到next数组的前一位,然后回退该数值就可以?这本质上就是一个最长相等前后缀的问题!假设已经匹配到了下标 index ,那么在母串中,当前index字符的前面某些字符,和模式串的前面字符一定是相等的!那么现在让模式串的指针回退,意义就是:模式串的前缀=母串中index前面子串的后缀!由于next数组的定义,所以一定相等。
- next数组是前缀表-1的版本,我不喜欢,就按照,前缀表=next数组,来记忆和学习。
- 定义两个指针 i 和 j ,j 指向前缀末尾位置,i 指向后缀末尾位置。next[i] 表示 i(包括 i)之前最长相等的前后缀长度(其实就是 j )。这里一定要明白什么叫前缀,前缀的起始一定是从0开始的,后缀的末尾一定是最后一个元素!“abc”,前缀有“a”,“ab”。
- 在求next数组的过程中,一定要抓住我们的循环不变量,在KMP的算法定义中,next数组的用法时:当前字符不匹配时,取当前index的前一个的位置的next数组的值,将指针下标移动到该值对应的下标处。那么在求next数组,如果出现前缀末尾和后缀末尾不匹配的情况,也要这样回退。
- 找出字符串中第一个匹配项的下标
数组章节
二分查找
二分查找,现在就确定适合我自己的风格,写左闭右闭区间的!
class Solution:
def search(self, nums: List[int], target: int) -> int:
n = len(nums)
left = 0
right = n-1
while left <= right :
middle = left + (right-left)//2
if nums[middle] > target :
right = middle - 1
elif nums[middle] < target :
left = middle + 1
else :
return middle
return -1
搜索插入位置
无重复元素,升序数组,当跳出循环时,left 一定大于 right ,所以返回 left , 这就是插入位置。
class Solution:
def searchInsert(self, nums: List[int], target: int) -> int:
n = len(nums)
left = 0
right = n-1
while left <= right :
middle = left + (right-left)//2
if nums[middle] > target :
right = middle - 1
elif nums[middle] < target :
left = middle + 1
else :
return middle
return left
在排序数组中查找元素的第一个和最后一个位置
有重复元素,这道题会了,才算是掌握了二分法。
搜索右边界,相等时移动左指针;搜索左边界,相等时移动右指针;本题我的解法和代码随想录的解法稍有不同,在我的解法中,right_edge和left_edge一定都会被赋值,如果left_edge > right_edge,说明没有找到该值(这个可以自己拿没有找到的例子试试),如果找到了,范围就是对的,和代码随想录的还是不太一样。
class Solution:
def searchRange(self, nums: List[int], target: int) -> List[int]:
n = len(nums)
left_edge = -2
right_edge = -2
left = 0
right = n-1
# 搜索左边界
while left <= right :
middle = left + (right-left)//2
if nums[middle] < target :
left = middle + 1
elif nums[middle] > target :
right = middle - 1
else :
right = middle - 1 # 相等时,移动右指针
left_edge = right + 1
left = 0
right = n-1
# 搜索右边界
while left <= right :
middle = left + (right-left)//2
if nums[middle] < target :
left = middle + 1
elif nums[middle] > target :
right = middle - 1
else :
left = middle + 1 # 相等时,移动左指针
right_edge = left - 1
if right_edge < left_edge :
return [-1,-1]
else :
return [left_edge,right_edge]
卡哥的代码
class Solution:
def searchRange(self, nums: List[int], target: int) -> List[int]:
def getRightBorder(nums:List[int], target:int) -> int:
left, right = 0, len(nums)-1
rightBoder = -2 # 记录一下rightBorder没有被赋值的情况
while left <= right:
middle = left + (right-left) // 2
if nums[middle] > target:
right = middle - 1
else: # 寻找右边界,nums[middle] == target的时候更新left
left = middle + 1
rightBoder = left
return rightBoder
def getLeftBorder(nums:List[int], target:int) -> int:
left, right = 0, len(nums)-1
leftBoder = -2 # 记录一下leftBorder没有被赋值的情况
while left <= right:
middle = left + (right-left) // 2
if nums[middle] >= target: # 寻找左边界,nums[middle] == target的时候更新right
right = middle - 1
leftBoder = right
else:
left = middle + 1
return leftBoder
leftBoder = getLeftBorder(nums, target)
rightBoder = getRightBorder(nums, target)
# 情况一
if leftBoder == -2 or rightBoder == -2: return [-1, -1]
# 情况三
if rightBoder -leftBoder >1: return [leftBoder + 1, rightBoder - 1]
# 情况二
return [-1, -1]
x 的平方根
如果找不到,return right 是本题的关键
class Solution:
def mySqrt(self, x: int) -> int:
left = 1
right = x
while left <= right :
middle = left + (right-left)//2
if middle*middle > x :
right = middle - 1
elif middle*middle < x :
left = middle + 1
else :
return middle
return right
有效的完全平方数
只需要改变上一题代码的返回值!
class Solution:
def isPerfectSquare(self, num: int) -> bool:
left = 1
right = num
while left <= right :
middle = left + (right-left)//2
if middle*middle > num :
right = middle - 1
elif middle*middle < num :
left = middle + 1
else :
return True
return False
移除元素
双指针法的代表,一定要想到用双指针法。避免双重循环
class Solution:
def removeElement(self, nums: List[int], val: int) -> int:
n = len(nums)
slow = 0
fast = 0
while slow < n and fast < n :
if nums[fast] != val :
nums[slow]=nums[fast]
slow += 1
fast += 1
else :
fast += 1
return slow
本题还有一种,最少移动元素次数的双向双指针方法
写下面代码的核心就是:抓住区间不变量,左闭右闭。
自己写的代码:
class Solution:
def removeElement(self, nums: List[int], val: int) -> int:
n = len(nums)
left = 0
right = n-1
while left <= right :
while left <= right and nums[left]!=val : # 从左边找等于 val 的元素
left += 1
while left <= right and nums[right] == val : # 从右边找不等于 val 的元素
right -= 1
# 这里按理说,应该是小于,而不是小于等于
# 因为不可能出现等于的情况,一个位置的元素不可能又等于val又不等于val
# if left <= right :
if left < right :
nums[left] = nums[right]
left += 1
right -= 1
return left
删除有序数组中的重复项
class Solution:
def removeDuplicates(self, nums: List[int]) -> int:
slow = 0
fast = 1
n = len(nums)
if n <= 1 :
return n
while fast < n :
while fast < n and nums[slow] == nums[fast] :
fast += 1
if fast >= n :
break
else :
slow += 1
nums[slow] = nums[fast]
fast += 1
return slow + 1
移动零
就是删除0
class Solution:
def moveZeroes(self, nums: List[int]) -> None:
"""
Do not return anything, modify nums in-place instead.
"""
n = len(nums)
slow = 0
fast = 0
while fast < n :
if nums[fast] != 0 :
nums[slow] = nums[fast]
slow += 1
fast += 1
else :
fast += 1
while slow < n :
nums[slow] = 0
slow += 1
比较含退格的字符串
双指针,好指
class Solution:
def backspaceCompare(self, s: str, t: str) -> bool:
slow = 0
fast = 0
s = list(s)
t = list(t)
ns = len(s)
nt = len(t)
while fast < ns :
if s[fast] != '#':
s[slow] = s[fast]
slow += 1
fast += 1
else :
fast += 1
slow -= 1
# 当 slow 退到 0 时,不能再减小,不能吃掉未来的字符
if slow < 0 :
slow = 0
s_idx = slow
slow = 0
fast = 0
while fast < nt :
if t[fast] != '#':
t[slow] = t[fast]
slow += 1
fast += 1
else :
fast += 1
slow -= 1
# 当 slow 退到 0 时,不能再减小,不能吃掉未来的字符
if slow < 0 :
slow = 0
t_idx = slow
if s_idx != t_idx :
return False
for i in range(s_idx):
if s[i] != t[i] :
return False
return True
本题还可以使用栈,是比较直观的,入栈出栈就可以了,需要注意的是,在对栈做 pop 操作时,要判断栈是否为空。
有序数组的平方
通过编写本题的代码,有几个需要注意的点。一是:当 start==end 时,也需要比较一下,只不过一定是等于的情况,加等于号是为了结果的完整,不然还要在循环外,再处理一下。这里有一个细节要注意一下,每一个条件判断前面都要加 if start <= end,避免后序操作导致溢出,光一个while循环在外面控制是不够的,其次就是,一定要把这个条件放在前面,因为python语言的逻辑判断特性。对于 and 操作符,当第一个条件是False时,不会进行第二个的判断,这样也避免了后面的数组索引可能超限的bug,这个小技巧在后面的编程中也经常用到。二是:当二者相等时,只能选择一个下标进行移动,而不是移动两个,移动两个会漏掉一个。
class Solution:
def sortedSquares(self, nums: List[int]) -> List[int]:
n = len(nums)
res = [0]*n
start = 0
end = n-1
index = n-1
while start <= end : # 当 start==end 时,也需要比较一下,只不过一定是等于的情况
# 加等于号是为了结果的完整,不然还要在循环外,再处理一下
# 这里有一个细节要注意一下,每一个条件判断前面都要加 if start <= end,避免后序操作导致溢出
# 光一个while循环在外面控制是不够的,其次就是,一定要把这个条件放在前面,因为python语言的逻辑判断特性
# 对于 and 操作符,当第一个条件是False时,不会进行第二个的判断,这样也避免了后面的数组索引可能超限的bug
# 这个小技巧在后面的编程中也经常用到
if start <= end and nums[start] + nums[end] > 0 :
res[index] = nums[end]*nums[end]
end -= 1
index -= 1
if start <= end and nums[start] + nums[end] < 0 :
res[index] = nums[start]*nums[start]
index -= 1
start += 1
if start <= end and nums[start] + nums[end] == 0 :
res[index] = nums[start]*nums[start]
index -= 1
# 当二者相等时,只能选择一个下标进行移动,而不是移动两个
# 移动两个会漏掉一个
start += 1
return res
长度最小的子数组
自己写的代码,反正AC了,所有注意的细节,写在下面的注释中了,去学习下滑动窗口的思路。
class Solution:
def minSubArrayLen(self, target: int, nums: List[int]) -> int:
left = 0
right = 0
nsum = 0
n = len(nums)
# 先初始化 right, 找到一个可行解,不然放在循环里不好写
while right < n and nsum < target :
nsum += nums[right]
right += 1
if nsum < target :
return 0
mincount = right-left
'''
if right == n and nsum >= target :
while nsum >= target :
nsum -= nums[left]
left += 1
left -= 1
nsum += nums[left]
mincount = min(mincount,right-left)
return mincount
'''
while right < n :
nsum += nums[right]
right += 1
# 这里就直接减过去,后面再加回来就好,搞一堆 if 判断,逻辑根本写不明白
while nsum >= target :
nsum -= nums[left]
left += 1
# 这里就是再加回来的步骤,上面的while出来后,nsum一定小于target,但是又保证了
# 再加回来一个值,一定是大于等于target的当前最短长度
left -= 1
nsum += nums[left]
mincount = min(mincount,right-left)
# 这里要再加一个左边的判断,为的就是,加入数组最后一个数后,没有进行左边移动的操作
# 其实这个操作放在开头也行,我已经做了注释,就是为了避免,在初始化的时候,right直接走到
# 数组末尾的情况,这种情况下,不会进入左移动的while循环
if right == n and nsum >= target :
while nsum >= target :
nsum -= nums[left]
left += 1
left -= 1
nsum += nums[left]
mincount = min(mincount,right-left)
return mincount
滑动窗口
思路还蛮直观的,卡哥的代码能这么短的原因是,题目只需要求出最短长度即可,不需要给出其对应的子数组,所以不需要像我一样去维护一个合理的区间[left,right],只需要每次移动都记录最短距离就好了。我是维护了区间[left,right],再根据区间,计算出的最短距离。
(版本一)滑动窗口法
class Solution:
def minSubArrayLen(self, s: int, nums: List[int]) -> int:
l = len(nums)
left = 0
right = 0
min_len = float('inf')
cur_sum = 0 #当前的累加值
while right < l:
cur_sum += nums[right]
while cur_sum >= s: # 当前累加值大于目标值
min_len = min(min_len, right - left + 1)
cur_sum -= nums[left]
left += 1
right += 1
return min_len if min_len != float('inf') else 0
水果成篮
不会写,错误代码如下:
class Solution:
def totalFruit(self, fruits: List[int]) -> int:
bags = []
maxcount = 0
n = len(fruits)
index = 0
count = [0]*2
for index in range(n):
if fruits[index] not in bags:
if bags == [] :
bags.append(fruits[index])
count[0] = 1
maxcount = max(maxcount,sum(count))
elif len(bags) == 1:
bags.append(fruits[index])
count[1] = 1
maxcount = max(maxcount,sum(count))
else :
bags.append(fruits[index])
bags = bags[1:]
count[0] = count[1]
count[1] = 1
maxcount = max(maxcount,sum(count))
else :
idx = bags.index(fruits[index])
count[idx] += 1
maxcount = max(maxcount,sum(count))
return maxcount
错误样例:
本题的重点在于:要去维护两个变量,“连续的当前水果数量,注意,这里的连续很重要” , “当前水果种类”,这两个很难想,想到这两个就比较好做了。
class Solution:
def totalFruit(self, fruits: List[int]) -> int:
bags = []
maxcount = 1
n = len(fruits)
num = 0 # 连续的当前水果数量,注意,这里的连续很重要
name = -1 # 当前水果种类
count = 0
for index in range(n):
if fruits[index] not in bags:
if len(bags) < 2:
bags.append(fruits[index])
name = fruits[index]
num = 1
count += 1
else :
if fruits[index] == name :
num += 1
count += 1
else :
bags = [name,fruits[index]]
name = fruits[index]
count = num+1
num = 1
else :
if fruits[index] == name :
num += 1
count += 1
else :
name = fruits[index]
count += 1
num = 1
maxcount = max(maxcount,count)
return maxcount
另一版看上去,更符合滑动窗口的代码:
class Solution:
def totalFruit(self, fruits: List[int]) -> int:
maxcount = 1
n = len(fruits)
if n <= 1 :
return n
left = 0
right = 1
count = 1
fruit_left = fruits[left]
fruit_right = fruits[right]
# 双指针法,不需要去保存当前水果种类,以及当前连续最大水果数
while right < n :
if fruits[right]==fruit_left or fruits[right]==fruit_right :
count += 1
right += 1
else :
fruit_right = fruits[right]
fruit_left = fruits[right-1]
# 当当前水果是新水果时,直接让left走到当前right的前一个,然后向前循环,直到不再相同
left = right-1
while left > 0 and fruits[left] == fruits[left-1] :
left -= 1
# 计算当前水果数
count = right-left+1
right += 1
maxcount = max(maxcount,count)
return maxcount
最小覆盖子串
不会不会不会,好难的题。本题的关键在于想明白:如何表示字符串?怎么知道 s 的子串,是否包含 t ? 统计 t 中每一个字母的个数,用字典或者哈希表!因为已经说明,t 只由英文字母组成,所以可以哈希表。字典要学会用defaultdict,方便太多。另外,注意子串是有“顺序”在里面的,比如“ACAB”是“ABC”的最短子串,当 s 串移动到B时,加入了B,此时向左的循环可以移除一个“A”,然后指向C,此时C的值是0,那么如果左指针还能继续移动,就要在后边再找一个C,比如“CABABC”,前面的AB虽然也是 t 的字符,但是都不可作为退出寻找的条件,只能在字典中先行记录,当找到下一个C时,才可以左移。为了达成上述目的,就要在"CAB"时,给C的值+1,然后left+1 , 让 left 指向A 。这样目前字典里只有一个值大于0,就是C,所以才会只有当向右找到一个C时,再进入移动左指针的流程。
需要着重注意的地方,我都写在注释里了
from collections import defaultdict
class Solution:
def minWindow(self, s: str, t: str) -> str:
char_t = defaultdict(int)
nt = len(t)
ns = len(s)
for i in range(nt):
char_t[t[i]] += 1
leftmin = 0
rightmin = 2*ns
left = 0
right = 0
while right < ns :
# 这里必须是大于0,因为对于不存在的元素,defaultdict的返回值也是0
if char_t[s[right]] > 0 :
char_t[s[right]] -= 1
nt -= 1
else :
char_t[s[right]] -= 1 # 不是t的值也要记录
if nt == 0 :
# 这里必须是小于0,因为对于不存在的元素,defaultdict的返回值也是0
while char_t[s[left]] < 0 :
char_t[s[left]] += 1
left += 1
if rightmin - leftmin > right-left :
rightmin , leftmin = right , left
nt = 1
# 要理解下面两行代码所代表的逻辑,如果进入判断nt==0,那么一定是在前面,新加入了一个
# 属于t的字符,并且t中所有字符目前的值是0,而上面的while循环,只能寻找小于0的字符,
# 当while循环终止时,找到的是最左边的为0的字符,那么此时,如果我们还想让left移动,
# 那么就要在right的右边,找一个和该最左边字符相等的字符,所以要让char_t[s[left]] += 1
# 同时也记得让left移动一格,这样在后面找到该字符时,向左移动的while循环是合法的
char_t[s[left]] += 1
left += 1
right += 1
if rightmin == 2* ns :
return ''
else :
return s[leftmin:rightmin+1]
我妄图把0的逻辑加入到 if 判断里,就是当前字符值为0,说明这个字符是 t 中字符,不像上面的代码那么处理,因为左移后的最后两行代码确实不好想,就让值从0的基础上继续减一,但是我发现,如果是这种逻辑,会伴随着其他非常多的Bug,其中某些逻辑存在冲突,写不出来。
下面是错误代码:
class Solution:
def minWindow(self, s: str, t: str) -> str:
char_t = {}
nt = len(t)
ns = len(s)
for i in range(nt):
if char_t.get(t[i],None) == None :
char_t[t[i]] = 1
else :
char_t[t[i]] += 1
leftmin = 0
rightmin = 2*ns
left = 0
right = 0
while right < ns :
if char_t.get(s[right],None) == None :
char_t[s[right]] = -1
else :
if char_t[s[right]] > 0 :
char_t[s[right]] -= 1
nt -= 1
else :
char_t[s[right]] -= 1 # 不是t的值也要记录
if nt <= -1 :
while char_t[s[left]] < 0 :
char_t[s[left]] += 1
left += 1
if rightmin - leftmin > right-left :
rightmin , leftmin = right , left
nt += 1
right += 1
if rightmin == 2* ns :
return ''
else :
return s[leftmin:rightmin+1]
螺旋矩阵II
要写好本题,最关键的就是处理好各个区间的定义,我是按照代码随想录对这道题的解答来的,本题很关键的一点是:领悟 n-1-i 的含义!(这道题可以多看看,多理解)
class Solution:
def generateMatrix(self, n: int) -> List[List[int]]:
matrix = [[0]*n for _ in range(n)]
times = n // 2
number = 1
for i in range(times):
# 从左到右
for j in range(i,n-1-i):
matrix[i][j] = number
number += 1
for j in range(i,n-1-i):
matrix[j][n-i-1] = number
number += 1
for j in range(n-1-i,i,-1):
matrix[n-i-1][j] = number
number += 1
for j in range(n-1-i,i,-1):
matrix[j][i] = number
number += 1
print(matrix)
if n % 2 == 1 :
matrix[times][times] = number
return matrix
螺旋矩阵
我觉得下面这样的写法,算得上是,和上面螺旋矩阵的题目是一个模板,一刷的时候的写法,以及一些录友的解答,都是用while循环,来进行模拟的。
class Solution:
def spiralOrder(self, matrix: List[List[int]]) -> List[int]:
row = len(matrix)
col = len(matrix[0])
total = row*col
res = [0]*total
idx = 0
if row == 1 :
for i in range(total):
res[i] = matrix[0][i]
return res
if col == 1 :
for i in range(total):
res[i] = matrix[i][0]
return res
# 在我这种写法中,只能是用min
temp = min(row,col)//2
for i in range(temp):
for j in range(i,col-1-i):
res[idx] = matrix[i][j]
idx += 1
for j in range(i,row-i-1):
res[idx] = matrix[j][col-i-1]
idx += 1
for j in range(col-i-1,i,-1):
res[idx] = matrix[row-i-1][j]
idx += 1
for j in range(row-i-1,i,-1):
res[idx] = matrix[j][i]
idx += 1
if idx != total :
if row > col :
i = temp
for j in range(i,row-i):
res[idx] = matrix[j][col-i-1]
idx += 1
elif row < col :
i = temp
for j in range(i,col-i):
res[idx] = matrix[i][j]
idx += 1
else :
res[idx] = matrix[temp][temp]
return res
while 循环的写法,确定周围四个边界,每次移动完都要判断是否进行break,由于每次循环完某一行或某一列后,都对其对应的边界进行收缩操作,所以在当次 for 循环中,可以直接使用左闭右闭区间,直接一行(一列)元素拉满,全部放入结果数组中!这种编写思路,逻辑倒是非常清晰,不需要去考虑太多的边界判断问题。
class Solution:
def spiralOrder(self, matrix: List[List[int]]) -> List[int]:
row = len(matrix)
col = len(matrix[0])
total = row*col
res = [0]*total
idx = 0
left = 0
right = col-1
up = 0
down = row-1
while idx < total :
for j in range(left,right+1):
res[idx] = matrix[up][j]
idx += 1
up += 1
if up > down :
break
for j in range(up,down+1):
res[idx] = matrix[j][right]
idx += 1
right -= 1
if right < left :
break
for j in range(right,left-1,-1):
res[idx] = matrix[down][j]
idx += 1
down -= 1
if up > down :
break
for j in range(down,up-1,-1):
res[idx] = matrix[j][left]
idx += 1
left += 1
if right < left :
break
return res
剑指Offer 29.顺时针打印矩阵
同上一题。
移除链表元素
和解答写的逻辑一致,本质需要注意的地方是,因为我对 virtual 进行了操作,所以要在前面先用 res 来保存一下 virtual , 方便在结果时返回: virtual.next
class ListNode:
def __init__(self,val=0,next=None):
self.val = val
self.next = next
class Solution:
def removeElements(self, head: Optional[ListNode], val: int) -> Optional[ListNode]:
virtual = ListNode(0,head)
res = virtual
while virtual.next :
if virtual.next.val == val :
virtual.next = virtual.next.next
else :
virtual = virtual.next
return res.next
构造链表
写的真是磕磕绊绊啊,主要是我不知道怎么给链表初始化合适,然后添加头,添加尾的操作,都要考虑到:如果当前链表是一个只初始化后的链表,就是当前的头结点是假的头结点,那么不管是加入头结点操作,还是加入尾结点操作,本质上都是:对链表头结点的初始化。
class ListNode:
def __init__(self, val=0, next=None):
self.val = val
self.next = next
class MyLinkedList:
def __init__(self):
self.head = ListNode(-1)
def get(self, index: int) -> int:
count = 0
temp = self.head
while temp :
print(temp.val)
if index == count :
return temp.val
else :
temp = temp.next
count += 1
return -1
def addAtHead(self, val: int) -> None:
node = ListNode(val,None)
if self.head.val == -1 :
self.head = node
else :
node.next = self.head
self.head = node
def addAtTail(self, val: int) -> None:
node = ListNode(val,None)
if self.head.val == -1 :
self.head = node
else :
temp = self.head
while temp.next :
temp = temp.next
temp.next = node
def addAtIndex(self, index: int, val: int) -> None:
count = 0
node = ListNode(val,None)
if index == 0 :
node.next = self.head
self.head = node
else :
temp = self.head
while temp :
if index-1 == count :
a = temp.next
temp.next = node
node.next = a
return
else :
temp = temp.next
count += 1
def deleteAtIndex(self, index: int) -> None:
count = 0
if index == 0 :
self.head = self.head.next
else :
temp = self.head
while temp.next :
if index-1 == count :
temp.next = temp.next.next
return
else :
temp = temp.next
count += 1
看了卡哥的解答,我只能说,这也行?因为题目只要求实现上述操作,所以不用管,当前的链表,按照self.head一直遍历得到的结果数组是什么样的!比如,加入头,那么只要头结点的值是正确的就可以了,不管尾结点是什么值!其他操作同理,同时又因为题目给定,所有节点的值大于0,所以搞一个值是0的“头结点”无所谓(当然随着后续操作,这个“头结点”就不知道跑到什么位置上去了)。还有一个值得学习的地方,在init的地方,多定义一个链表长度的变量。
有点意思,卡哥的代码中,假如现在有一个虚拟头,再执行加入头结点操作,按照其代码,链表现在为:虚拟头->加入头,如果内核想建言是否正确,是去调用get(0) ? 在 get 方法中,是先让 cur = dummy_head.next 。
我的代码这么复杂,是我保证了每次操作后,按照self.head进行遍历,得到的链表就是正确的链表。但其实我还是觉得我这么写是更好的。
反转链表
下面的代码有些冗余。不知道怎么的,感觉最近脑子像坏掉了一样,这题之前不是做过吗?怎么还会有些卡壳?
class Solution:
def reverseList(self, head: Optional[ListNode]) -> Optional[ListNode]:
if head == None :
return head
if head.next == None :
return head
virtual = ListNode(0,head)
pre = virtual
cur = virtual.next
while cur.next :
temp = cur.next
cur.next = pre
pre = cur
cur = temp
cur.next = pre
head.next = None
return cur
看了解答后的改进版:
本题不需要虚拟头结点,代码编写下来,确实会发现用不到,只需要一个pre和一个cur,pre先赋值为None,链表的移动,也只需要一次移动一步,后面两两交换,是一次移动两步。也不需要处理边界情况,因为代码逻辑中已经包含了边界的情况。
class Solution:
def reverseList(self, head: Optional[ListNode]) -> Optional[ListNode]:
pre = None
cur = head
while cur :
temp = cur.next
cur.next = pre
pre = cur
cur = temp
return pre
两两交换链表中的节点
本题主要就是两点,一是while条件的判断,到底是while cur and cur.next还是while cur.next and cur.next.next。二是,本题需要用到两个变量来暂存赋值过程中的中间变量,同时要注意,本题的节点要一次移动两格。
class ListNode:
def __init__(self,val=0,next=None):
self.val=val
self.next = next
class Solution:
def swapPairs(self, head: Optional[ListNode]) -> Optional[ListNode]:
if head == None or head.next == None :
return head
virtual = ListNode(0,head)
pre = virtual
cur = head
# 注意这里的判断,不能是:while cur.next and cur.next.next
# 这样最后两个节点不会交换,注意到 cur.next.next是None是可以的,但是cur不能是None
# cur是None的话,cur.next首先就会报错
while cur and cur.next :
temp = cur.next
a = temp.next
pre.next = temp
temp.next = cur
cur.next = a
pre = cur
cur = a
return virtual.next
删除链表的倒数第 N 个结点
让 fast 先走N步,然后一起走,fast走到尾结点时,slow就到了要删除节点的前一个节点。
class Solution:
def removeNthFromEnd(self, head: Optional[ListNode], n: int) -> Optional[ListNode]:
virtual = ListNode(0,head)
slow = virtual
fast = virtual
while n > 0 :
fast = fast.next
n -= 1
while fast.next :
fast = fast.next
slow = slow.next
slow.next = slow.next.next
return virtual.next
链表相交
在写过两边之后,这次是第三遍写这个题,可以从容地用 if 判断+交换,来避免代码重复冗余的情况了,不需要去写countA和countB的两种大小情况,只写一种即可。
class ListNode:
def __init__(self,val=0,next=None):
self.val = val
self.next = next
class Solution:
def getIntersectionNode(self, headA: ListNode, headB: ListNode) -> ListNode:
curA = headA
curB = headB
countA = 0
countB = 0
while curA :
curA = curA.next
countA += 1
while curB :
curB = curB.next
countB += 1
# 在下面这个判断之后,只需要考虑 countA >= countB 的情况了
if countA < countB :
headA,headB = headB,headA
countA,countB = countB,countA
diff = countA - countB
while diff > 0 :
headA = headA.next
diff -= 1
while headA :
if headA == headB :
return headA
headA = headA.next
headB = headB.next
return None
环形链表II
基本上自主编写,但有两个疑问。一是要处理两个边界情况,二是要用一个变量,来强制让代码第一次进入循环。
class Solution:
def detectCycle(self, head: Optional[ListNode]) -> Optional[ListNode]:
# 处理两种特殊情况
if head == None :
return None
if head.next == None :
return None
slow = head
fast = head
# 要用一个变量,让程序可以进入移动循环,因为在我的初始化中
# 一开始就有:fast == slow
flag = 0
while fast != slow or flag == 0:
if fast.next == None or fast.next.next == None :
return None
fast = fast.next.next
slow = slow.next
flag = 1
# print(slow.val)
cur = head
while cur != slow :
cur = cur.next
slow = slow.next
return cur
卡哥的代码
哦,原来是这样,只要改一下 while 循环的判断条件就行了。
class Solution:
def detectCycle(self, head: ListNode) -> ListNode:
slow = head
fast = head
while fast and fast.next:
slow = slow.next
fast = fast.next.next
# If there is a cycle, the slow and fast pointers will eventually meet
if slow == fast:
# Move one of the pointers back to the start of the list
slow = head
while slow != fast:
slow = slow.next
fast = fast.next
return slow
# If there is no cycle, return None
return None
本题的重要知识点补充
如果,从相遇点走了N圈,才和从头结点开始走的指针相遇呢?会相遇吗?根据公式:有 2(x+y) = x+y+n(y+z) ,变换有: x=(n-1)(y+z)+z ,(n-1)圈,还会再多一个z ! 所以,不管是走一圈还是走N圈,我们令指针1从头节点走,指针2从相遇节点走,相遇的地方,一定是环的入口!
有效的字母异位词
简单。
class Solution:
def isAnagram(self, s: str, t: str) -> bool:
num = [0]*26
for i in s :
num[ord(i)-ord('a')] += 1
for i in t :
num[ord(i)-ord('a')] -= 1
for i in range(26):
if num[i]!=0:
return False
return True
赎金信
和上一题的差别只在一个条件判断。
class Solution:
def canConstruct(self, ransomNote: str, magazine: str) -> bool:
record = [0]*26
for i in magazine :
record[ord(i)-ord('a')] += 1
for i in ransomNote :
record[ord(i)-ord('a')] -= 1
for i in range(26):
if record[i] < 0:
return False
return True
字母异位词分组
自己写的代码,逻辑是没问题的,但是超出时间限制。
class Solution:
def groupAnagrams(self, strs: List[str]) -> List[List[str]]:
if strs == [""] :
return [[""]]
n = len(strs)
used = [0]*n
haxi = [[0]*26 for _ in range(n)]
for i in range(n):
string = strs[i]
for j in string :
haxi[i][ord(j)-ord('a')]+=1
res = []
for i in range(n):
if used[i] == 0 :
level = []
for t in range(i+1,n):
if used[i] == 0 :
flag = True
for r in range(26):
if haxi[i][r] != haxi[t][r] :
flag = False
break
if flag == True :
level.append(strs[t])
used[t]=1
if level != [] :
level.append(strs[i])
used[i]=1
res.append(level)
for i in range(n):
if used[i] == 0 :
res.append([strs[i]])
return res
看了解答,用了内置排序函数sort,好吧,这样写确实更快,怎么就没想到用排序!这样只需要比较排序后,是不是相等,就知道是不是异位词。
class Solution:
def groupAnagrams(self, strs: List[str]) -> List[List[str]]:
res = []
dic = {}
for s in strs:
keys = "".join(sorted(s))
if keys not in dic:
dic[keys] = [s]
else:
dic[keys].append(s)
return list(dic.values())
用质数表示26个字母,把字符串的各个字母相乘,这样可保证字母异位词的乘积必定是相等的。其余步骤就是用map存储。这个思路真牛逼。
class Solution:
def groupAnagrams(self, strs: List[str]) -> List[List[str]]:
map = {
'a':2,'b':3,'c':5,'d':7,'e':11,'f':13,'g':17,'h':19,'i':23,'j':29,'k':31,'l':37,'m':41,
'n':43,'o':47,'p':53,'q':59,'r':61,'s':67,'t':71,'u':73,'v':79,'w':83,'x':89,'y':97,'z':101
}
resmap={}
reslist = []
for str in strs:
m = 1
for i in range(len(str)):
m*=map[str[i]]
if not m in resmap:
resmap[m]=[]
resmap[m].append(str)
print(resmap.values())
return [j for j in resmap.values()]
所以我的方法超时的原因是:我是利用26长度的哈希数组来判断是否是异位词的,是这个原因导致的超时。在双重for循环找字符串时,我用了used数组,所以复杂度还是O(n),而不是O(n^2)
找到字符串中所有字母异位词
排序,排序,还TM的是排序。自己写的代码
class Solution:
def findAnagrams(self, s: str, p: str) -> List[int]:
slen = len(s)
plen = len(p)
p = sorted(p)
res = []
if slen < plen :
return res
else :
diff = slen - plen
for i in range(diff+1):
temp = s[i:i+plen]
if sorted(temp) == p :
res.append(i)
return res
力扣的示例代码,滑动窗口+哈希的思想,本题简化的关键在于:字符串p的长度是知道的,且求子串就意味着连续,所以就可以模拟一个窗口在s上滑动;用哈希数组,来记录滑动窗口和p是否相等。
class Solution:
def findAnagrams(self, s: str, p: str) -> List[int]:
s_len, p_len = len(s), len(p)
if s_len < p_len:
return []
ans = []
s_count = [0] * 26
p_count = [0] * 26
for i in range(p_len):
s_count[ord(s[i]) - 97] += 1
p_count[ord(p[i]) - 97] += 1
if s_count == p_count:
ans.append(0)
for i in range(s_len - p_len):
s_count[ord(s[i]) - 97] -= 1
s_count[ord(s[i + p_len]) - 97] += 1
if s_count == p_count:
ans.append(i + 1)
return ans
两个数组的交集
数组哈希,竟然不超时,我以为会超时。
class Solution:
def intersection(self, nums1: List[int], nums2: List[int]) -> List[int]:
list1 = [0]*1001
list2 = [0]*1001
res = []
for i in nums1 :
list1[i] += 1
for i in nums2 :
list2[i] += 1
for i in range(1001) :
if list1[i] > 0 and list2[i] > 0 :
res.append(i)
return res
但是这道题的本意并不是想让我用数组来做的,而是想让我用set,但我现在只会数组哈希,我是废物fw。本题能用set的关键点(或者说,set的特性):不需要对数据进行排序,不要让数据重复。
卡哥的代码,字典+集合+删除操作
class Solution:
def intersection(self, nums1: List[int], nums2: List[int]) -> List[int]:
# 使用哈希表存储一个数组中的所有元素
table = {}
for num in nums1:
table[num] = table.get(num, 0) + 1
# 使用集合存储结果
res = set()
for num in nums2:
if num in table:
res.add(num)
del table[num]
return list(res)
两个数组的交集 II
在上一题的基础上写出此题。不删除元素版本
class Solution:
def intersect(self, nums1: List[int], nums2: List[int]) -> List[int]:
table = {}
for i in nums1 :
table[i] = table.get(i,0)+1
# 注意这里,思路一定要灵活,上一道题不能重复,所以储存结果的数据结构用set
# 本题要有重复元素,所以数据结果就应该用列表
res = []
for j in nums2 :
if table.get(j,-1) > 0 :
res.append(j)
table[j]-=1
return res
删除元素版本
class Solution:
def intersect(self, nums1: List[int], nums2: List[int]) -> List[int]:
table = {}
for num in nums1:
table[num] = table.get(num, 0) + 1
# 使用集合存储结果
res = []
for num in nums2:
# 判断元素是否在数据结构中,用in
if num in table:
res.append(num)
table[num] -= 1
if table[num] == 0:
del table[num]
return res
快乐数
我只能想到循环法,目前。
class Solution:
def isHappy(self, n: int) -> bool:
res = []
while True :
if n == 1 :
return True
if n in res :
return False
res.append(n)
n = self.compute(n)
def compute(self,n):
res = 0
while n > 0 :
temp = n % 10
res += temp*temp
n = n // 10
return res
这道题的基本思路,和上面我写的一致,能简化代码的地方是:可以先将n变成str,然后就可以利用索引去取每一位上的数字了。
两数之和
本题的暴力法,就是两层for循环,哈希法就是用一个集合或字典,存储之前已经遍历过的元素,这样在遍历当前元素时,用差值去快速索引(因为本题明确说明:答案如果存在,只可能存在一个)。用双指针解决多数之和,是这种题目的普遍解法,我这里写下双指针的写法吧。
双指针法,代码思路很直观,就是要处理,有相同值的情况,因为存在索引 index 操作,值相同会导致返回同一下标。
class Solution:
def twoSum(self, nums: List[int], target: int) -> List[int]:
temp = sorted(nums)
n = len(nums)
left = 0
right = n-1
while left < right :
if temp[left] + temp[right] > target :
right -= 1
elif temp[left] + temp[right] < target :
left += 1
else :
res1 = nums.index(temp[left])
# 这里必须要有一个如果数组中存在相同值的判断逻辑
# 因为index索引函数,会索引到相同的下标
# 另一种处理方法是:
# res2 = nums[res1+1:].index(temp[right])+res1+1
# 我就直接在res1的位置上赋值为一个不可能正确的值
res2 = nums.index(temp[right])
if res1 == res2 :
nums[res1] = temp[left]-1
res2 = nums.index(temp[right])
'''
if res1 == res2 :
res2 = nums[res1+1:].index(temp[right])+res1+1
'''
return [res1,res2]
四数相加 II
不会做不会做,想不到好的方法。因为是四个数,从中间砍开啊,类似于二分法的思想,这样就只需要去统计前两个数组的和,后两个数组的和了!
有道理有道理,学习了,二分法思想的灵活运用!
class Solution:
def fourSumCount(self, nums1: List[int], nums2: List[int], nums3: List[int], nums4: List[int]) -> int:
res = 0
table = {}
for i in nums1 :
for j in nums2 :
table[i+j] = table.get(i+j,0)+1
for i in nums3:
for j in nums4 :
target = -(i+j)
if table.get(target,0) != 0 :
res = res + table[target]
return res
三数之和
主要思想就是双指针,先遍历起始位置 i , 然后right固定为末尾开始,left为 i+1 开始,去重逻辑就是,相同值就跳过(当然,因为需要去重,所以要先排序)。去重逻辑我就直接凭直觉就过了,当时第一次写的时候,去重逻辑好像很难啊。
class Solution:
def threeSum(self, nums: List[int]) -> List[List[int]]:
nums.sort()
n = len(nums)
res = []
for i in range(0,n-2):
if i > 0 and nums[i]==nums[i-1]:
continue
left = i+1
right = n-1
target = -nums[i]
while left < right :
if nums[left] + nums[right] > target :
right -= 1
elif nums[left] + nums[right] < target :
left += 1
else :
path = [nums[i],nums[left],nums[right]]
res.append(path)
while left < right and nums[left]==nums[left+1]:
left += 1
while left < right and nums[right]==nums[right-1]:
right -= 1
left += 1
right -= 1
return res
卡哥的代码,哈哈哈,和我的代码几乎一模一样,去重逻辑就是这样的!里面的利用target为0的特性进行剪枝,是我忽略的。
class Solution:
def threeSum(self, nums: List[int]) -> List[List[int]]:
result = []
nums.sort()
for i in range(len(nums)):
# 如果第一个元素已经大于0,不需要进一步检查
if nums[i] > 0:
return result
# 跳过相同的元素以避免重复
if i > 0 and nums[i] == nums[i - 1]:
continue
left = i + 1
right = len(nums) - 1
while right > left:
sum_ = nums[i] + nums[left] + nums[right]
if sum_ < 0:
left += 1
elif sum_ > 0:
right -= 1
else:
result.append([nums[i], nums[left], nums[right]])
# 跳过相同的元素以避免重复
while right > left and nums[right] == nums[right - 1]:
right -= 1
while right > left and nums[left] == nums[left + 1]:
left += 1
right -= 1
left += 1
return result
四数之和
独立完成,需要注意的是剪枝的部分。另外:剪枝操作,建议以后都写break吧,我不知道为什么我这道题习惯写了return,在第二层循环那里,会漏掉很多情况,循环中的剪枝,return是没什么道理的,统一用break。
class Solution:
def fourSum(self, nums: List[int], target: int) -> List[List[int]]:
nums = sorted(nums)
n = len(nums)
res = []
for i in range(0,n-3):
if i > 0 and nums[i]==nums[i-1]:
continue
# 剪枝细节,一定注意,要考虑target是负数的情况
if nums[i] > 0 and nums[i] > target:
# 这里因为是最外层循环,所以break还是return 无所谓
break
for j in range(i+1,n-2):
if j > i+1 and nums[j]==nums[j-1]:
continue
# 剪枝细节,一定注意,要考虑target是负数的情况,同时要留意到:
# 多个负数相加是更小的负数,所以(nums[i]+nums[j]) > 0是必须的
# 比如 target=-8,解是[-1,-2,-2,-3],前两个的加和是大于target的!
if (nums[i]+nums[j]) > 0 and (nums[i]+nums[j]) > target:
# 注意这里,不能是return ,而是break
break
left = j+1
right = n-1
temp = target - (nums[i]+nums[j])
while left < right :
if nums[left]+nums[right] > temp :
right -= 1
elif nums[left]+nums[right] < temp :
left += 1
else :
path = [nums[i],nums[j],nums[left],nums[right]]
res.append(path)
while left < right and nums[left]==nums[left+1]:
left += 1
while left < right and nums[right]==nums[right-1]:
right -= 1
left += 1
right -= 1
return res
反转字符串
class Solution:
def reverseString(self, s: List[str]) -> None:
"""
Do not return anything, modify s in-place instead.
"""
left = 0
n = len(s)
right = n-1
while left < right :
s[left],s[right] = s[right],s[left]
left += 1
right -= 1
反转字符串II
自己的代码,不够简洁
class Solution:
def reverseStr(self, s: str, k: int) -> str:
s = list(s)
n = len(s)
count = n // (2*k)
for i in range(count):
temp = s[i*2*k:i*2*k+k]
s[i*2*k:i*2*k+k] = temp[::-1]
index = count*2*k
if n-index < k :
temp = s[index:]
s[index:] = temp[::-1]
elif n-index >= k and n-index < 2*k :
temp = s[index:index+k]
s[index:index+k] = temp[::-1]
return ''.join(s)
卡哥的代码,能这么写的原因是因为:利用了Python中,字符串或者列表,当切片切出范围后,返回的都是空,而不会报错。例如: s = ‘aab’ , s[100]会报错,但是 s[10:100]会返回: “” 。同样的,s = [1,2,3] , s[100]会报错,但是但是 s[10:100]会返回: [ ] 。这样就可以直接循环操作了,不需要考虑越界的问题。
class Solution:
def reverseStr(self, s: str, k: int) -> str:
"""
1. 使用range(start, end, step)来确定需要调换的初始位置
2. 对于字符串s = 'abc',如果使用s[0:999] ===> 'abc'。字符串末尾如果超过最大长度,则会返回至字符串最后一个值,这个特性可以避免一些边界条件的处理。
3. 用切片整体替换,而不是一个个替换.
"""
def reverse_substring(text):
left, right = 0, len(text) - 1
while left < right:
text[left], text[right] = text[right], text[left]
left += 1
right -= 1
return text
res = list(s)
for cur in range(0, len(s), 2 * k):
res[cur: cur + k] = reverse_substring(res[cur: cur + k])
return ''.join(res)
class Solution:
def reverseStr(self, s: str, k: int) -> str:
# Two pointers. Another is inside the loop.
p = 0
while p < len(s):
p2 = p + k
# Written in this could be more pythonic.
s = s[:p] + s[p: p2][::-1] + s[p2:]
p = p + 2 * k
return s
替换空格
利用Python语言的性质,字符串可以直接相加,这道题很简单。但是这道题想考的应该是双指针。
class Solution:
def replaceSpace(self, s: str) -> str:
res = ''
for i in s:
if i != ' ':
res += i
else :
res += '%20'
return res
定量扩容+双指针
class Solution:
def replaceSpace(self, s: str) -> str:
s = list(s)
n = len(s)
count = 0
for i in range(n):
if s[i]==' ' :
count += 1
extend = [0]*2*count
s = s + extend
left = n-1
right = n + 2*count - 1
while left > -1 :
if s[left] != ' ':
s[right]=s[left]
right -= 1
left -= 1
else :
s[right-2:right+1] = '%20'
right -= 3
left -= 1
return ''.join(s)
反转字符串中的单词
自己写的代码,有点长,但主题思想还是,先整体翻转,再分别翻转单词。
class Solution:
def reverseWords(self, s: str) -> str:
s = list(s)
n = len(s)
slow = 0
fast = 0
while fast < n :
if slow == 0 and s[fast]==' ':
fast += 1
elif slow > 0 and s[fast]==' ' and s[slow-1] != ' ':
s[slow] = s[fast]
slow += 1
fast += 1
elif slow > 0 and s[fast]==' ' and s[slow-1] == ' ':
fast += 1
# 上面对 fast 做了加一操作,下面这里的 if 是和上面独立的
# 所以在进行一切操作之前,一定要加上最外层while循环的判断条件 fast < n
if fast < n and s[fast] != ' ':
s[slow] = s[fast]
slow += 1
fast += 1
# 最后一个字符是否是空格,是的话,就去掉
# 我上面的逻辑就会导致,最后一位可能是空格
if s[slow-1] == ' ':
s = s[:slow-1]
length = slow-1
else :
s = s[:slow]
lenght = slow
s = s[::-1]
left = 0
right = 0
while right < slow :
if s[right] != ' ':
right += 1
else :
temp = s[left:right]
s[left:right] = temp[::-1]
right += 1
left = right
# 这里也要注意,要单独处理 right 走到最后一个位置的情况,由于前面已经去掉了空格
# 所以要单独处理
if right == slow-1 :
temp = s[left:]
s[left:] = temp[::-1]
right += 1
left = right
return ''.join(s)
卡哥的解答,咔咔用内置函数,用split,用strip,无语
class Solution:
def reverseWords(self, s: str) -> str:
# 删除前后空白
s = s.strip()
# 反转整个字符串
s = s[::-1]
# 将字符串拆分为单词,并反转每个单词
s = ' '.join(word[::-1] for word in s.split())
return s
class Solution:
def reverseWords(self, s: str) -> str:
# 将字符串拆分为单词,即转换成列表类型
words = s.split()
# 反转单词
left, right = 0, len(words) - 1
while left < right:
words[left], words[right] = words[right], words[left]
left += 1
right -= 1
# 将列表转换成字符串
return " ".join(words)
Python内置的 split() 默认参数:就是基于空格进行分割!那用了 split() 函数,这道题想要考察的,如何按要求去除空格,不就被跳过了吗。
左旋转字符串
投机方法:一行代码
class Solution:
def reverseLeftWords(self, s: str, n: int) -> str:
return s[n:]+s[:n]
翻转的写法:翻转前n个字符的子串–翻转第n+1到最末位的子串–翻转整个字符串。
class Solution:
def reverseLeftWords(self, s: str, n: int) -> str:
s = list(s)
temp = s[:n]
s[:n] = temp[::-1]
temp = s[n:]
s[n:] = temp[::-1]
s = s[::-1]
return ''.join(s)
接下来这两道,是KMP算法的经典题目了,但是我现在已经忘了KMP是什么了。
KMP算法:
KMP主要应用在字符串匹配上。
KMP的主要思想是当出现字符串不匹配时,可以知道一部分之前已经匹配的文本内容,可以利用这些信息避免从头再去做匹配了。
所以如何记录已经匹配的文本内容,是KMP的重点,也是next数组肩负的重任。
什么是前缀表(即:next数组):记录下标 i 之前 (包括 i ) 的字符串中,有多大长度的相同前缀后缀。
“KMP最长公共前后缀”,不准确,应该是,“最长相等前后缀”,因为前缀表求得是,相同前后缀的长度。
注意一定要理解,前缀和后缀的定义,定义明确是正确计算前缀表的关键。
长度为前1个字符的子串a,最长相同前后缀的长度为0。(注意字符串的前缀是指不包含最后一个字符的所有以第一个字符开头的连续子串;后缀是指不包含第一个字符的所有以最后一个字符结尾的连续子串。)
这里要结合例子好好理解!比如 “aaba”, 那么后缀有:【“a” , “ba” , “aba”】,前缀有:【“a” , “aa” , “aab”】。所以最长相等前后缀就是 1 。
为什么当字符不匹配时,找到next数组的前一位,然后回退该数值就可以?这本质上就是一个最长相等前后缀的问题!假设已经匹配到了下标 index ,那么在母串中,当前index字符的前面某些字符,和模式串的前面字符一定是相等的!那么现在让模式串的指针回退,意义就是:模式串的前缀=母串中index前面子串的后缀!由于next数组的定义,所以一定相等。
next数组是前缀表-1的版本,我不喜欢,就按照,前缀表=next数组,来记忆和学习。
那么 next 数据 , 如何构造?初始化很重要,定义很重要。
定义两个指针 i 和 j ,j 指向前缀末尾位置,i 指向后缀末尾位置。next[i] 表示 i(包括 i)之前最长相等的前后缀长度(其实就是 j )。这里一定要明白什么叫前缀,前缀的起始一定是从0开始的,后缀的末尾一定是最后一个元素!“abc”,前缀有“a”,“ab”。
在求next数组的过程中,一定要抓住我们的循环不变量,在KMP的算法定义中,next数组的用法时:当前字符不匹配时,取当前index的前一个的位置的next数组的值,将指针下标移动到该值对应的下标处。那么在求next数组,如果出现前缀末尾和后缀末尾不匹配的情况,也要这样回退。
一个自己写的求 next 数组的示例:但是有很多说不通的地方
s = 'aabaaf'
n = len(s)
nums = [0]*n
j = 0
for i in range(1,n):
while j > 0 and s[j]!=s[i] :
# 这里一定要好好理解,这里看做是[0,j]和[0,i]进行匹配
j = nums[j-1]
if s[j]==s[i] :
# 这里别的代码,都写的是:nums[i] = j+1
nums[i] = nums[j]+1 # 这么写说不通吧
j += 1
else :
nums[i]=0
KMP还是没掌握,晕了,明天再来看看。