数据规模->时间复杂度
<=10^4 😮(n^2)
<=10^7:o(nlogn)
<=10^8:o(n)
10^8<=:o(logn),o(1)
总结
1.回顾常用排序算法
2.各排序算法的应用(归并、快排)
小规模数据,选择使用O(n^2)的排序算法
大规模数据,选择使用O(nlogn)的排序算法
1)如何写一个通用的排序算法
2)排序元素比较
3)分治算法思想
3.自定义排序的逻辑
4.有的时候,先对数据进行排序,可以降低复杂度
lc 912 :https://leetcode.cn/problems/sort-an-array/
提示:
1 <= nums.length <= 5 * 10^4
-5 * 10^4 <= nums[i] <= 5 * 10^4
#方案一:归并
class Solution:
def sortArray(self, nums: List[int]) -> List[int]:
#o(n)
self.sort_merge(nums,0,len(nums)-1,[0]*len(nums))
return nums
#o(nlogn):分治
def sort_merge(self, nums,lo,hi,tmp) -> None:
#递归终止条件:最小子问题,无法继续分解
if lo>=hi:return
#
mid=lo+(hi-lo)//2
self.sort_merge(nums,lo,mid,tmp)
self.sort_merge(nums,mid+1,hi,tmp)
self.merge(nums,lo,mid,hi,tmp)
#归并
def merge(self, nums,lo,mid,hi,tmp) -> None:
#
for i in range(lo,hi+1):
tmp[i]=nums[i]
#
i=lo
j=mid+1
for k in range(lo,hi+1):
if i==mid+1: #key
nums[k]=tmp[j]
j+=1
elif j==hi+1: #key
nums[k]=tmp[i]
i+=1
elif tmp[i]<=tmp[j]:
nums[k]=tmp[i]
i+=1
else:
nums[k]=tmp[j]
j+=1
#方案二:二路快排:o(nlogn),o(logn)
import random
class Solution:
def sortArray(self, nums: List[int]) -> List[int]:
self.quick_sort(nums,0,len(nums)-1)
return nums
#o(nlogn):分治
def quick_sort(self,nums,lo,hi) -> None:
#
if lo>=hi:return
#
index=self.pivot(nums,lo,hi)
self.quick_sort(nums,lo,index-1)
self.quick_sort(nums,index+1,hi)
#快排:o(nlogn)
def pivot(self, nums,lo,hi) -> None:
#随机选取pivot
i = random.randint(lo, hi)
nums[i],nums[hi]=nums[hi],nums[i] #换至最后,方便分区
pivot=nums[hi]
#分区
less=great=lo
while great<=hi-1:
if nums[great]<pivot:
nums[less],nums[great]=nums[great],nums[less]
less+=1
great+=1
nums[less],nums[great]=nums[great],nums[less]
#
return less
#三路快排:o(nlogn),o(logn)
import random
class Solution:
def sortArray(self, nums: List[int]) -> List[int]:
self.quick_sort(nums,0,len(nums)-1)
return nums
#o(nlogn):分治
def quick_sort(self,nums,lo,hi) -> None:
#
if lo>=hi:return
#
less,great=self.pivot(nums,lo,hi)
self.quick_sort(nums,lo,less-1)
self.quick_sort(nums,great+1,hi)
#三路快排:o(nlogn)
def pivot(self, nums,lo,hi) -> None:
#随机选取pivot
j = random.randint(lo, hi)
nums[j],nums[hi]=nums[hi],nums[j] #换至最后,方便分区
pivot=nums[hi]
#分区:未处理区[great,hi],其他为已处理区
i=lo
less=lo
great=hi
while i<=great:
if nums[i]<pivot:
nums[less],nums[i]=nums[i],nums[less]
less+=1
i+=1#注意:nums[i]与nums[less]已经得到处理
elif nums[i]>pivot:
nums[great],nums[i]=nums[i],nums[great]
great-=1
#注意无i+=1:此时nums[i]为待处理元素,因为之前nums[great]不明情况
else:i+=1
#
return less,great
阿里面试题 - 快速查找第二大数
#冒泡:时o(n),空o(1)
class Solution:
def getsecondmax(self, nums: List[int]) -> List[int]:
#
first=-2**31
second=-2**31
#
for num in nums:
if num>first:
second=first
first=num
else:num>second:
second=num
#
return second
lc 628 :https://leetcode.cn/problems/maximum-product-of-three-numbers/
提示:
3 <= nums.length <= 10^4
-1000 <= nums[i] <= 1000
#冒泡
class Solution:
def maximumProduct(self, nums: List[int]) -> int:
#o(1)
#第一小,第二小
min1=min2=float('inf')
#第一大,第二大,第三大
max1=max2=max3=float('-inf')
#o(n)
for num in nums:
min1,min2,_=sorted([min1,min2,num])
_,max3,max2,max1=sorted([max1,max2,max3,num])
#
return max(max1*max2*max3,min1*min2*max1)
lc 88:https://leetcode.cn/problems/merge-sorted-array/
提示:
nums1.length == m + n
nums2.length == n
0 <= m, n <= 200
1 <= m + n <= 200
-10^9 <= nums1[i], nums2[j] <= 10^9
class Solution:
def merge(self, nums1: List[int], m: int, nums2: List[int], n: int) -> None:
"""
Do not return anything, modify nums1 in-place instead.
"""
#o(1)
i=m-1
j=n-1
k=n+m-1
#o(n):nums1,nums2已经有序
while j>=0:
if i>=0 and nums1[i]>nums2[j]:
nums1[k]=nums1[i]
i-=1
else:
nums1[k]=nums2[j]
j-=1
k-=1
#
return nums1
【剑指 51】:https://leetcode.cn/problems/shu-zu-zhong-de-ni-xu-dui-lcof/
限制:
0 <= 数组长度 <= 50000
#逆序对:数组中的两个元素,前面的元素大于后面的元素
#方案一:暴力(超时)
class Solution:
def reversePairs(self, nums: List[int]) -> int:
#
if nums==None or len(nums)<2:return 0
#o(n^2)
count=0
for i in range(len(nums)):
for j in range(i+1,len(nums)):
if nums[i]>nums[j]:
count+=1
#
return count
#方案二:归并(阶段排序):整序的过程是消逆序对(有序程度)的过程
#分治算法:类比栈(先进后出)与 DFS(深度优先遍历)
#https://www.bilibili.com/video/BV1Qk4y1r7u5?spm_id_from=333.337.search-card.all.click
class Solution:
def reversePairs(self, nums: List[int]) -> int:
#
if len(nums)<2:return 0
#o(n),o(nlogn)
copy=[0]*len(nums) #不改变原数组
for i in range(len(nums)):
copy[i]=nums[i]
count=self.sort_merge_count(copy,0,len(nums)-1,[0]*len(nums))
return count
def sort_merge_count(self,copy,lo,hi,tmp) -> int:
#
if lo==hi:return 0 #只有一个元素,一定有序,一定非逆序数
#
mid=lo+(hi-lo)//2 #解决整形溢出问题
n1=self.sort_merge_count(copy,lo,mid,tmp)
n2=self.sort_merge_count(copy,mid+1,hi,tmp)
#优化-已经有序
if copy[mid]<=copy[mid+1]:
return n1+n2
n3=self.merge(copy,lo,mid,hi,tmp)
#
return n1+n2+n3
#copy[left,mid],copy[mid+1,right]已经排好序
def merge(self,copy,lo,mid,hi,tmp) -> int:
#
for i in range(lo,hi+1):
tmp[i]=copy[i]
#
i=k=lo
j=mid+1
count=0
while 0<=k<=hi: #key
if i==mid+1:
copy[k]=tmp[j]
j+=1
elif j==hi+1:
copy[k]=tmp[i]
i+=1
elif tmp[i]<=tmp[j]: #=:保证稳定排序
copy[k]=tmp[i]
i+=1
else:
copy[k]=tmp[j]
j+=1
count+=mid-i+1 #key:利用部分数组的有序性
k+=1
return count
lc 315:https://leetcode.cn/problems/count-of-smaller-numbers-after-self/
提示:
1 <= nums.length <= 10^5
-10^4 <= nums[i] <= 10^4
class Solution:
def countSmaller(self, nums: List[int]) -> List[int]:
#nums->index tmp->tmpindex
n=len(nums)
self.index=[i for i in range(n)]
self.tmp=[0]*n
self.tmpindex=[0]*n #key:绑定元素和对应的索引
#o(n)
self.res=[0]*n
self.sort_merge(nums,0,n-1)
return self.res
#o(nlogn):分治
def sort_merge(self, nums,lo,hi) -> None:
#递归终止条件:最小子问题,无法继续分解
if lo==hi:return
#
mid=lo+(hi-lo)//2
self.sort_merge(nums,lo,mid)
self.sort_merge(nums,mid+1,hi)
self.merge(nums,lo,mid,hi)
#归并
def merge(self, nums,lo,mid,hi) -> None:
#
for i in range(lo,hi+1):
self.tmp[i]=nums[i]
self.tmpindex[i]=self.index[i]
#
i=lo
j=mid+1
for k in range(lo,hi+1):
if i==mid+1: #key
nums[k]=self.tmp[j]
self.index[k]=self.tmpindex[j]
j+=1
elif j==hi+1: #key
nums[k]=self.tmp[i]
self.index[k]=self.tmpindex[i]
self.res[self.tmpindex[i]]+=(j-mid-1)
i+=1
elif self.tmp[i]<=self.tmp[j]:
nums[k]=self.tmp[i]
self.index[k]=self.tmpindex[i]
self.res[self.tmpindex[i]]+=(j-mid-1) #key优化:i-mid-1:比当前元素小的后面元素的个数
i+=1
else:
nums[k]=self.tmp[j]
self.index[k]=self.tmpindex[j]
j+=1
lc 327:https://leetcode.cn/problems/count-of-range-sum/
提示:
1 <= nums.length <= 10^5
-2^31 <= nums[i] <= 2^31 - 1
-10^5 <= lower <= upper <= 10^5
题目数据保证答案是一个 32 位 的整数
#问题转化(前缀和):查找[i,j]对使得: prefixSum[j] - prefixSum[i] ∈ [lower,upper]找到所有符合条件的[i,j]对,其中i!=j
#排序不影响结果:组间是独立的,不影响的
class Solution:
def countRangeSum(self, nums: List[int], lower: int, upper: int) -> int:
#前缀和
n=len(nums)
prefixsum=[0]*(n+1)
for i in range(1,n+1):
prefixsum[i]=prefixsum[i-1]+nums[i-1]
#
self.lower=lower
self.upper=upper
self.tmp=[0]*(n+1)
return self.sort_merge(prefixsum,0,n)
#o(nlogn):分治
def sort_merge(self, nums,lo,hi) -> int:
#递归终止条件:最小子问题,无法继续分解
if lo==hi:return 0
#
mid=lo+(hi-lo)//2
n1=self.sort_merge(nums,lo,mid)
n2=self.sort_merge(nums,mid+1,hi)
#nums[left,mid],nums[mid+1,right]已排序
count=0
i=lo
l=r=mid+1
while i<=mid:
while l<=hi and (nums[l]-nums[i])<self.lower:l+=1 #第一个>=lower
while r<=hi and (nums[r]-nums[i])<=self.upper:r+=1 #第一个>upper
count+=(r-l)
i+=1
#
self.merge(nums,lo,mid,hi)
#左半合并过程的统计(最小子问题)+右半合并过程的统计(最小子问题)+左半右半整体合并过程的统计
return n1+n2+count
#归并
def merge(self, nums,lo,mid,hi) -> int:
#
for i in range(lo,hi+1):
self.tmp[i]=nums[i]
#
i=lo
j=mid+1
for k in range(lo,hi+1):
if i==mid+1: #key
nums[k]=self.tmp[j]
j+=1
elif j==hi+1: #key
nums[k]=self.tmp[i]
i+=1
elif self.tmp[i]<=self.tmp[j]:
nums[k]=self.tmp[i]
i+=1
else:
nums[k]=self.tmp[j]
j+=1
lc 493:https://leetcode.cn/problems/reverse-pairs/
注意:
给定数组的长度不会超过50000。
输入数组中的所有数字都在32位整数的表示范围内。
class Solution:
def reversePairs(self, nums: List[int]) -> int:
n=len(nums)
self.tmp=[0]*n
return self.sort_merge(nums,0,n-1)
#o(nlogn):分治
def sort_merge(self, nums,lo,hi) -> int:
#递归终止条件:最小子问题,无法继续分解
if lo==hi:return 0
#
mid=lo+(hi-lo)//2
n1=self.sort_merge(nums,lo,mid)
n2=self.sort_merge(nums,mid+1,hi)
#nums[left,mid],nums[mid+1,right]已排序
count=0
i=lo
j=mid+1
while i <= mid:
while j <= hi and (nums[i]-2 * nums[j])>0 : j += 1 #第一个不满足的->前面都满足
count += (j - mid - 1)#i-mid-1:满足条件的后面元素的个数
i += 1
#
self.merge(nums,lo,mid,hi)
#左半合并过程的统计(最小子问题)+右半合并过程的统计(最小子问题)+左半右半整体合并过程的统计
return n1+n2+count
#归并
def merge(self, nums,lo,mid,hi) -> int:
#
for i in range(lo,hi+1):
self.tmp[i]=nums[i]
#
i=lo
j=mid+1
for k in range(lo,hi+1):
if i==mid+1: #key
nums[k]=self.tmp[j]
j+=1
elif j==hi+1: #key
nums[k]=self.tmp[i]
i+=1
elif self.tmp[i]<=self.tmp[j]:
nums[k]=self.tmp[i]
i+=1
else:
nums[k]=self.tmp[j]
j+=1
lc 50 【剑指 16】:https://leetcode.cn/problems/powx-n/
提示:
-100.0 < x < 100.0
-2^31 <= n <= 2^31-1
-10^4 <= xn <= 10^4
#快速幂+递归
#o(logn),o(logn)
class Solution:
def myPow(self, x: float, n: int) -> float:
if n<0:
x=1/x
n=-n
return self.quickpow(x,n)
#递归:O(logn),o(logn)
def quickpow(self,x,n)->float:
#
if n==0:return 1
if n==1:return x
#
mid=n//2
y=self.quickpow(x,mid)
#
return y*y if n%2==0 else x*y*y
#优化-结合位运算
class Solution:
def myPow(self, x: float, n: int) -> float:
#
if n<0:
x=1/x
n=-n
#o(32),o(1)
res=1
while n!=0:
if n&1==1 :res*=x #非零位,对结果影响
#更新
x*=x #x^2,x^4,x^8......
n>>=1
#
return res
快速排序 - 分区逻辑的应用
未处理区域[great,hi]
二项切分:[<=pivot][>pivot]
三项切分:[<pivot][=pivot][>pivot]
lc 75【top100】:https://leetcode.cn/problems/sort-colors/
提示:
n == nums.length
1 <= n <= 300
nums[i] 为 0、1 或 2
进阶:
你可以不使用代码库中的排序函数来解决这道题吗?
你能想出一个仅使用常数空间的一趟扫描算法吗?
#方案一:计数排序:o(n),o(1)
class Solution:
def sortColors(self, nums: List[int]) -> None:
"""
Do not return anything, modify nums in-place instead.
"""
#计数:o(n),o(1)
count=[0]*3
for i in range(len(nums)):
count[nums[i]]+=1
#排序
k=0
for i in range(3):
n=count[i]
for j in range(n):
nums[k]=i
k+=1
#
return nums
#方案二:三路快排类比 [==0][==1][==2]
class Solution:
def sortColors(self, nums: List[int]) -> None:
"""
Do not return anything, modify nums in-place instead.
"""
#o(1)
zero=0
two=len(nums)-1
i=0
#o(n)
pivot=1
while i<=two:
if nums[i]<pivot:
nums[i],nums[zero]=nums[zero],nums[i]
i+=1
zero+=1
elif nums[i]>pivot:
nums[i],nums[two]=nums[two],nums[i]
two-=1
else:i+=1
#
return nums
lc 179【剑指 45】:https://leetcode.cn/problems/largest-number/
提示:
1 <= nums.length <= 100
0 <= nums[i] <= 10^9
import random
class largestNumkey(str):
def __lt__(x,y):
return x+y>y+x #富比较
class Solution:
def largestNumber(self, nums: List[int]) -> str:
largestNum=''.join(sorted(map(str,nums),key=largestNumkey))
return largestNum if largestNum[0]!='0'else '0'
lc 56【剑指 74】:区间合并
https://leetcode.cn/problems/merge-intervals/
提示:
1 <= intervals.length <= 10^4
intervals[i].length == 2
0 <= starti <= endi <= 10^4
【计算之魂 11章】
class Solution:
def merge(self, intervals: List[List[int]]) -> List[List[int]]:
# 根据 start_i 进行排序
intervals.sort(key=lambda x: x[0])
merged = [intervals[0]]
for interval in intervals:
start, end = interval
# 如果列表为空,或者当前区间与上一区间不重合,直接添加
if merged[-1][1] < start:
merged.append(interval)
# 否则的话,我们就可以与上一区间进行合并
else:
merged[-1][1] = max(merged[-1][1], end)
return merged
lc 57 :https://leetcode.cn/problems/insert-interval/
提示:
0 <= intervals.length <= 10^4
intervals[i].length == 2
0 <= intervals[i][0] <= intervals[i][1] <= 10^5
intervals 根据 intervals[i][0] 按 升序 排列
newInterval.length == 2
0 <= newInterval[0] <= newInterval[1] <= 10^5
#方案一:
class Solution:
def insert(self, intervals: List[List[int]], newInterval: List[int]) -> List[List[int]]:
#
interval=intervals
interval.append(newInterval)
if len(interval)==0:return []
#o(n),o(1)
output=[]
interval.sort(key=lambda x:x[0])
output.append(interval[0]) #排序后的
for i in range(1,len(interval)):
curl,curr=interval[i][0],interval[i][1]
if curl<=output[-1][1]: #重叠
output[-1][1]=max(output[-1][1],curr)
else:output.append(interval[i])
#
return output
#方案二:
class Solution:
def insert(self, intervals: List[List[int]], newInterval: List[int]) -> List[List[int]]:
#o(n),o(1)
i=0
res=[]
while i<len(intervals) and intervals[i][1]<newInterval[0]:
res.append(intervals[i])
i+=1
#
while i<len(intervals) and intervals[i][0]<=newInterval[1]:#重叠
newInterval[0]=min(intervals[i][0],newInterval[0])
newInterval[1]=max(intervals[i][1],newInterval[1])
i+=1
res.append(newInterval)
#
while i<len(intervals):
res.append(intervals[i])
i+=1
#
return res
lc 905:https://leetcode.cn/problems/sort-array-by-parity/
提示:
1 <= nums.length <= 5000
0 <= nums[i] <= 5000
#方案一:暴力解法-O(n),o(n)
class Solution:
def sortArrayByParity(self, nums: List[int]) -> List[int]:
#
res=[0]*len(nums)
#
left=0
right=len(nums)-1
for i in range(len(nums)):
if nums[i]%2==0:
res[left]=nums[i]
left+=1
else:
res[right]=nums[i]
right-=1
#
return res
#方案二:类比二向快排o(n),o(1)
class Solution:
def sortArrayByParity(self, nums: List[int]) -> List[int]:
#o(n),o(1)
double=single=0
while single<=len(nums)-1:
if nums[double]%2 > nums[single]%2: #左奇右偶,换位
nums[double],nums[single]=nums[single],nums[double]
if nums[double]%2==0:double+=1
# double+=1
# elif nums[single]%2==0:double+=1 #偶+偶
# #其他:偶+奇
single+=1
#
return nums
#方案三:优化-减少交换次数
class Solution:
def sortArrayByParity(self, nums: List[int]) -> List[int]:
#o(n),o(1)
left=0
right=len(nums)-1
while left<=right:
if nums[left]%2 > nums[right]%2: #左奇右偶,换位
nums[left],nums[right]=nums[right],nums[left]
if nums[left]%2==0:left+=1
if nums[right]%2==1:right-=1
#
return nums
lc 922:https://leetcode.cn/problems/sort-array-by-parity-ii/
提示:
2 <= nums.length <= 2 * 10^4
nums.length 是偶数
nums 中一半是偶数
0 <= nums[i] <= 1000
进阶:可以不使用额外空间解决问题吗?
#双指针思路
class Solution:
def sortArrayByParityII(self, nums: List[int]) -> List[int]:
#o(1)
i=0
j=1
#o(n)
while i<=len(nums)-1:
if nums[i]%2 ==1:
while nums[j]%2 !=0:j+=2
nums[i],nums[j]=nums[j],nums[i]
i+=2
#
return nums
lc 1365:https://leetcode.cn/problems/how-many-numbers-are-smaller-than-the-current-number/
提示:
2 <= nums.length <= 500
0 <= nums[i] <= 100
#方案一:超时(快排/归并)
class Solution:
def smallerNumbersThanCurrent(self, nums: List[int]) -> List[int]:
#绑定
n=len(nums)
data=[[0]*2]*n
for i in range(n):
data[i][0]=nums[i]
data[i][1]=i
#o(nlogn),o(n)
data.sort(key=lambda x:x[0])
i=0
prev=-1
res=[0]*n
while i <n:
if prev==-1 or data[i]!=data[i-1]:
prev=i
res[i]=prev
#
return res
#计数排序-o(n),o(n)
class Solution:
def smallerNumbersThanCurrent(self, nums: List[int]) -> List[int]:
#o(n)
counts=[0]*(101)
for num in nums:
counts[num]+=1
for i in range(1,101):
counts[i]+=counts[i-1]
#o(n)
res=[0]*len(nums)
for i in range(len(nums)):
res[i]=counts[nums[i]-1] if nums[i] !=0 else 0 #key
#
return res
lc 164:https://leetcode.cn/problems/maximum-gap/
提示:
1 <= nums.length <= 10^5
0 <= nums[i] <= 10^9
#方案一:桶排序
#平均间距-gap=(max - min)/ (nums.length- 1)
#最大的间距肯定不会小于gap
'''
key:将间距小于gap的所有元素放在一个桶中
bucketNum = nums.length ∵有可能所有元素间间距都为gap
bucketld =(num -min)/ gap
'''
#桶间有序
import math
class Solution:
def maximumGap(self, nums: List[int]) -> int:
n=len(nums)
if n < 2: return 0
#o(n):初始桶
buckets_num=n
INTMAX,INTMIN=2**31-1,-2**31
buckets=[[INTMAX,INTMIN] for _ in range(buckets_num)] #key
#o(n:)定义桶
max_num,min_num=max(nums),min(nums) #变量化(优化)
if max(nums)==min(nums):return 0
gap=int(math.ceil((max_num-min_num)/(n-1)))
for num in nums:
bucket_id = (num - min_num) // gap
buckets[bucket_id][0] = min(buckets[bucket_id][0], num)
buckets[bucket_id][1] = max(buckets[bucket_id][1], num)
#求最大间隔
max_gap=0
prevbucketmax=min_num
for bucket in buckets:
if bucket[0]==INTMAX:continue #注
max_gap=max(max_gap,bucket[0]-prevbucketmax)
prevbucketmax=bucket[1]
return max_gap
#方案二:基数排序-适合数字范围很大(分别比较个位,十位......)
#核心:利用计数排序
class Solution:
def maximumGap(self, nums: List[int]) -> int:
n=len(nums)
data=self.sort(nums)
#
gap=0
for i in range(1,n):
gap=max(data[i]-data[i-1],gap)
#
return gap
#o(n),o(n)
def sort(self,data):
#位数
n=len(data)
maxnum=0
for i in range(n):
maxnum=max(maxnum,data[i])
exp=1
while maxnum//exp !=0:
self.countSort(data,exp)
exp*=10
#
return data
def countSort(self,data,exp):
n=len(data)
count=[0]*10
#
for i in range(n):
count[(data[i]//exp)%10]+=1
for i in range(1,10):
count[i]+=count[i-1]
#
res=[0]*n
for i in range(n):
k=count[(data[i]//exp)%10]-1
res[k]=data[i]
count[(data[i]//exp)%10]-=1
#
for i in range(n):
data[i]=res[i]