395. 至少有K个重复字符的最长子串
滑动窗口:
每次都要重新遍历整个字符串
class Solution:
def longestSubstring(self, s: str, k: int) -> int:
max_num=len(set(s))
res=0
for i in range(1,max_num+1):
#当不同字符个数为i时,最长字符串的长度
res=max(res,self.helper(i,s,k))
return res
def helper(self,n,s,k):
hash=defaultdict(int)
l=0
maxl=0
windows,strs=0,0
for r in range(len(s)):
if hash[s[r]]==0:
#不同字符的个数
strs+=1
hash[s[r]]+=1
#大于等于k的字符个数
if hash[s[r]]==k:
windows+=1
#当字符串不同个数大于n时.左移窗口找到子字符串
while strs>n:
if hash[s[l]]==k:
windows-=1
hash[s[l]]-=1
if hash[s[l]]==0:
strs-=1
l+=1
if strs==windows:
maxl=max(r-l+1,maxl)
return maxl
递归分治:
递归条件
当前处理的字符出现的次数小于k,以当前字符为分隔符切割当前字符串,对切割好的子字符串依次调用函数本身
基线条件:
当前处理的字符串每个字母重复的次数均不小于k时,返回当前字符串的长度
当前字符串长度小于k,返回0
如果某个字符在s中出现次数少于k, 就不用考虑这个字符了, 因此以这个字符分割然后递归
class Solution:
def longestSubstring(self, s: str, k: int) -> int:
for c in set(s):
if s.count(c)<k:
# 若重复的字符小于k,以此字符分割字符串,形成字符串列,对列中每个字符串 递归调用,返回字符重复个数大于k的字符串列中的最大值
return max(self.longestSubstring(t,k) for t in s.split(c))
# 若每个字符重复的个数都大于k,则返回整个字符串的长度。
return len(s)
1053. 交换一次的先前排列
升序排列的数组无需交换
因为是比A小的最大可能排列,应该尽量处理后面的值,这样变动小
从后往前找到一个降序对,即A[I]>A[I+1],那么将A[I]换成一个比他小的值,生成的字典序一定小于A,
寻找在 A[i] 最左边且小于 A[i] 的最大的数字 A[j]
一次交换后字典序就变小,交换的两个数,肯定原先是大数在前,小数在后。交换后,小数换到前面来,大数换到后面去
那么被改变的那两位中,较高的一位肯定在序列中的位数越低越好(因为一旦位数更高,变小的就更多了),我们要先找到变化的两位中较高的那一位,再找较低的那一位;且找较高的那一位时,只能从后往前找(因为找到了就可以得到答案了呀,这个答案自然就是所有满足条件的答案中,较高位最低的一个),由条件1,为了最大,较低位一定是这个较高位右边比它更小且位数较高的那一位,这样变小的最少
class Solution:
def prevPermOpt1(self, A: List[int]) -> List[int]:
idx=-1
maxi=-1
flag=False
for i in range(len(A)-2,-1,-1):
if A[i]>A[i+1]:
#找到A[I]最左边的比A[I]小的最大值,这样两数交换可以得到比原数组小的最大排列
#必须满足 A[i] > A[j],否则不能满足交换后的字典序小于原始字典序
for j in range(i+1,len(A)):
if A[i]>A[j]:
flag=True
if A[j]>maxi:
maxi=A[j]
idx=j
if flag:
A[i],A[idx]=A[idx],A[i]
return A
return A
162. 寻找峰值
单调递增栈
class Solution:
def findPeakElement(self, nums: List[int]) -> int:
nums.append(float('-inf'))
stack=[]
for i in range(len(nums)):
if stack and nums[stack[-1]]>nums[i]:
return stack[-1]
stack.append(i)
二分查找:
因为题目告诉我们可以返回数组中的任意一个峰顶。所以我们只要确定某一半至少存在一个峰顶,那么另一半就可以抛弃掉。
log(n)的复杂度:二分查找
如果 nums[mid] < nums[mid + 1],此时在上升阶段,因为 nums[n] 看做负无穷,也就是最终一定会下降,所以 mid + 1 到 end 之间至少会存在一个峰顶,可以把左半部分抛弃。
如果 nums[mid] > nums[mid + 1],此时在下降阶段,因为 nums[0] 看做负无穷,最初一定是上升阶段,所以 start 到 mid 之间至少会存在一个峰顶,可以把右半部分抛弃。
class Solution:
def findPeakElement(self, nums: List[int]) -> int:
left,right=0,len(nums)-1
while left<right:
mid=left+(right-left)//2
if nums[mid]<nums[mid+1]:
left=mid+1
else:
right=mid
return left
852. 山脉数组的峰顶索引
求最大值的索引
class Solution:
def peakIndexInMountainArray(self, A: List[int]) -> int:
return A.index(max(A))
二分法
class Solution:
def peakIndexInMountainArray(self, A: List[int]) -> int:
left,right=0,len(A)-1
while left<right:
mid=left+(right-left)//2
if A[mid]<A[mid+1]:
left=mid+1
else:
right=mid
return left
704. 二分查找
class Solution:
def search(self, nums: List[int], target: int) -> int:
left,right=0,len(nums)-1
while left<right:
mid=left+(right-left)//2
if nums[mid]<target:
left=mid+1
else:
right=mid
return left if nums[left]==target else -1
1095. 山脉数组中查找目标值
先找到山顶元素 mountaintop 所在的索引
在前有序且升序数组中找 target 所在的索引,如果找到了,就返回,如果没有找到,就执行第 3 步;
如果步骤 2 找不到,就在后有序且降序数组中找 target 所在的索引。
一个数组找某个元素出现的最小下标,而这个数组的排列特点是先升后降
先二分搜索找到山顶,在进行左右两边二分搜索
有一个单调递增序列(峰值左边)和一个单调递减序列(峰值右边),我们只是不知道两个序列的分割点,即峰值在哪里。所以我们第一步应该首先找到峰值。
# """
# This is MountainArray's API interface.
# You should not implement it, or speculate about its implementation
# """
#class MountainArray:
# def get(self, index: int) -> int:
# def length(self) -> int:
class Solution:
def findInMountainArray(self, target: int, mountain_arr: 'MountainArray') -> int:
n=mountain_arr.length()
left,right=0,n-1
#寻找峰顶
while left<right:
mid=left+(right-left)//2
#[left,mid + 1]为递增区间,需要往右边搜索,left右移
if mountain_arr.get(mid)<mountain_arr.get(mid+1):
left=mid+1
else:
right=mid
# 山峰左边上升区间
l=0
r=left
#当l==r时退出循环
while l<r:
mid=l+(r-l)//2
if mountain_arr.get(mid)==target:
return mid
elif mountain_arr.get(mid)>target:
r=mid-1
else:
l=mid+1
#如果在左侧找到了,则返回索引
if mountain_arr.get(l)==target:
return l
#左边没有找到,去右边
l,r=left,n-1
while l<r:
mid=l+(r-l)//2
if mountain_arr.get(mid)==target:
return mid
elif mountain_arr.get(mid)<target:
r=mid-1
else:
l=mid+1
return l if mountain_arr.get(l) == target else -1
845. 数组中的最长山脉
先找上坡,后找下坡,用mid记录山顶位置。
新山脉的起点为下坡的终点
class Solution:
def longestMountain(self, A: List[int]) -> int:
res=0
start=end=0
while start<len(A):
end=start
#山脉一开始是上升的,扩展山脉终点
while end+1<len(A) and A[end]<A[end+1]:
end+=1
#记录山脉的山峰点
mid=end
#山脉开始下降
while end+1<len(A) and A[end]>A[end+1]:
end+=1
#符合山脉要求,记录长度
if start<mid<end:
res=max(res,end-start+1)
#如果山脉是平的,起点往后移
if start==end:
start+=1
#新一轮的山脉起点是上一轮山脉终点
else:
start=end
return res
457 环形数组循环
做深度优先搜素,利用字典visited来标记已经搜素过的节点。
利用numSet记录在一个方向上遇到的节点,如果新节点在numSet就有环。但需要在三个情况清空numSet:
从i节点开始DFS到底了,从i+1节点开始搜素时清空numSet.
当搜素方向direction改变符号,按题意要求同方向,清空numSet.
当一个节点的下一个节点是自身,清空numSet.
class Solution:
def circularArrayLoop(self, nums: List[int]) -> bool:
n=len(nums)
#每个点作为循环起点开始遍历
for i in range(n):
#标记遍历过的点
visited=[False for _ in range(n)]
j=i
count=0
#j还没被遍历过
while not visited[j]:
visited[j]=True
#方向是向前
if nums[j]>0:
#下一个点的索引
j=(j+nums[j])%n
if nums[j]<0:
break
else:
count+=1
#方向向后
else:
if (j+nums[j])%n>=0:
j=(j+nums[j])%n
else:
j=n+(j+nums[j])%n
if nums[j]>0:
break
count+=1
# 循环完判断是否回到原点且长度大于1
if i==j and count>1:
return True
return False
1160. 拼写单词
class Solution:
def countCharacters(self, words: List[str], chars: str) -> int:
res=0
for word in words:
for i in word:
if word.count(i)<=chars.count(i):
flag=1
continue
else:
flag=0
break
if flag==1:
res+=len(word)
return res
35. 搜索插入位置
二分查找
class Solution:
def searchInsert(self, nums: List[int], target: int) -> int:
l,r=0,len(nums)-1
while l<=r:
mid=l+(r-l)//2
if nums[mid]==target:
return mid
elif nums[mid]>target:
r=mid-1
else:
l=mid+1
return l