十大排序算法:
① 冒泡排序:重复地走访要排序的数列,一次比较两个元素,如果顺序错误就交换这两个元素。一直到没有再需要交换的为止。元素经交换慢慢“浮”到数列顶端
步骤:
- 比较相邻元素,若第一个比第二个大,就交换位置
- 对每一对相邻元素作同样操作,这样最大的元素一定会在末尾
- 对所有元素(除去最后一个(最后一个一定最大,无需比较))重复上述操作
- 重复1-3,直到排序完成
- 注:两层for循环,时O(N^2)
② 选择排序
思想和冒泡排序类似,可看为冒泡排序的优化。不断在剩余元素中找最大(小)指
步骤:
- 找到数组中最大(小)的那个元素
- 和数组的第一个元素交换位置(若第一个元素就是最大(小)元素那么就和它自己交换)
- 在剩下的元素中找到最大(小)的元素,和数组的第二个元素交换位置。如此往复知道将整个数组排序
- 注:找最小——升序;找最大:降序
- 注:两层for循环,时O(N^2)
③ 插入排序
对未排序的数,在已排序序列中从后往前扫描,找到相应位置并插入
注:要为待插入数据腾出空间,需要将插入位置之后的已排序元素先向后移动一位;插入排序所需时间取决于输入的序列的初始顺序,如果已排序或接近有序,则使用插入排序会很高效
④ 归并排序
对给定的一组数据,利用递归和分支将数组序列划分成越来越小的半子集,在对半子集排序后,再用递归的方法将排好序的半子表合并成更大的有序序列
解释:先划分为很小的子集,再对两两数组排序,然后两两有序数组合并
⑤ ⭐堆排序
⑥ ⭐快速排序(quicksort)
对冒泡的一种改进,是分治法的一个典型应用
- 首先任选一个数据作为关键数据(基准数),将所有比它小的数放前面,所有比它大的数放后面,这个过程称为一趟快速排序,也成为分区(partition)操作
- 再对基准数左右两边分别进行快速排序
- 整个排序过程递归进行,直到所有数组有序
- 时O(NlogN) 空O(logN)
⑦ 希尔排序
基于插入排序的改进,也称为缩小增量排序。
主要思想:对一组元素使用增量进行分组(如,增量为3,指下标差为3的元素分为一组),对每组元素直接使用插入排序;慢慢缩小增量(每组内元素会增加)至1(此时整个数组为一组),再次使用插入排序,置止完成整个数组的排序
增量选取,一般为N/2, (N/2)/2, ……, 1
⑧ 计数排序
适合于数组中元素值分布较连续,跨度小的情况
如:0 1 2 1 2 2 0 0
主要思想:设置一个数组大小为待排序数组中的最大元素值+1,初始化为0;用于记录每个元素出现的次数,然后根据次数重写待排数组,得到的就是排序好的数组
时:O(N);空:O(K)_K为待排数组的元素跨度
⑨ 基数排序
⑩ 桶排序
49 字母异位词分组
题目解释:给定一个字符串数组,将字母异位词组合在一起。字母异位词指字母相同,但排列不同的字符串。(字母异位词,如“abc”和“bca”,“cba”)
方法一:排序
字母异位词排序后一定相同
# 法1:快排+哈希表
def groupAnagrams(self, strs: List[str]) -> List[List[str]]:
hashmap = {}
for s in strs:
s_sorted = "".join(sorted(s))
if s_sorted not in hashmap:
hashmap[s_sorted] = [s] # 若不在哈希表中,则添加 注意使用[]
else:
hashmap[s_sorted].append(s) # 若在哈希表,则向value中添加字符串s
return list(hashmap.values()) # 转为列表输出
# 时:O(nklogk)——用到快排,n是字符串数量,k是最长字符串长度
# 空:O(nk)——哈希表存储所有字符串
方法二:计数
对每个字符串计数得到该字符串的计数数组,对于计数数组相同的字符串,就互为异位词。
把这个数组手动编码变成字符串作为哈希表的key。
比如将 [b,a,a,a,b,c] 编码成 a3b2c1。
# 法2:不使用快排而是记录每个字符串各元素的出现次数,若出现次数相同则为字母异位词
def groupAnagrams(self, strs: List[str]) -> List[List[str]]:
hashmap = {}
for s in strs:
counter = [0]*26 # 使用数组记录每个元素出现的次数
for i in s:
num = ord(i) - ord('a')
counter[num] += 1
recode = '' # 对数组再编码,如counter仅在0,1,2处分别有值1,2,3,则再编码后为a1b2c3,以此作为key
for i in range(26):
if counter[i] > 0:
key = chr(i + ord('a')) # 找到对应的字符
recode += key
recode += chr(counter[i])
if recode not in hashmap: # 将再编码得到的字符串作为哈希表的key
hashmap[recode] = [s]
else:
hashmap[recode].append(s)
return list(hashmap.values()) # 注意是.value()
# 时:O(nk) n是字符串数量,k是最长字符串长度
# 空:O(nk) 哈希表存储
438 找到字符串中所有字母异位词(滑动窗口)
75 颜色分类(较难)
def sortColors(self, nums: List[int]) -> None:
"""
Do not return anything, modify nums in-place instead.
"""
def swap(index1, index2):
nums[index1], nums[index2] = nums[index2], nums[index1]
p1 = 0 # p1左全为0
p2 = len(nums)-1 # p2右全为2
i = 0 # 遍历每个元素
while i <= p2: # 若i大于p2,则所有元素都为2,不用遍历
if nums[i] == 0:
swap(i, p1)
i += 1
p1 += 1
elif nums[i] == 2:
swap(i, p2)
p2 -= 1
else:
i += 1
时O(N) 空O(1)
148 排序链表(较难)
法1 递归(不满足进阶要求,复杂度怎么分析)
# Definition for singly-linked list.
# class ListNode:
# def __init__(self, val=0, next=None):
# self.val = val
# self.next = next
class Solution:
def sortList(self, head: Optional[ListNode]) -> Optional[ListNode]:
if not head or not head.next: # 链表长度为1则不用排序
return head
mid = self.middleNode(head)
left = self.sortList(head)
right = self.sortList(mid)
return self.merge(left, right) # 合并两个有序列表
def middleNode(self, head):
if not head or not head.next:
return head
dummynode = ListNode(0, head)
slow = dummynode
fast = head
while fast and fast.next:
fast = fast.next.next
slow = slow.next
newhead = slow.next
slow.next = None
return newhead
def merge(self, l1, l2):
if not l1:
return l2
if not l2:
return l1
if l1.val < l2.val:
l1.next = self.merge(l1.next, l2)
return l1
else:
l2.next = self.merge(l1, l2.next)
return l2
215 数组中的第K个最大元素
def findKthLargest(self, nums: List[int], k: int) -> int:
return self.quickselect(nums, k)
def quickselect(self, nums, k):
randomIndex = random.randint(0, len(nums)-1)
pivot = nums[randomIndex]
left = []
right = []
mid = []
for i in nums:
if i < pivot:
left.append(i)
elif i > pivot:
right.append(i)
else:
mid.append(i)
'''
if len(right)>=k:
return self.quickselect(right, k)
elif len(right) + len(mid) == k: # 去掉,可能pivot就是第k个,但等于pivot的数很多,此时就不满足这个判断
return pivot
else:
return self.quickselect(left, k - len(right) - len(mid))
'''
if len(right) >= k:
return self.quickselect(right,k)
elif len(right) + len(mid) < k:
return self.quickselect(left,k-len(right)-len(mid))
return pivot
注,上述代码是在快排基础上的排序,每次分区后只有一个区域需要递归,提高了时间效率——快速选择算法,时O(N)
由于使用了递归操作,空O(logn)
581 最短无序连续子数组
解释:
如果进入了右段,就没有比最大值小的数,所以最后一个比最大值小的数就是中段的右边界,同理,如果进入左段,就不会出现比最小值更大的情况,所以最后一个出现就视为中段左边界?
class Solution:
def findUnsortedSubarray(self, nums: List[int]) -> int:
n = len(nums)
maxnum = nums[0]
minnum = nums[n-1]
end = -1 # 为了无需排序的情况 即end-start+1=0
start = 0
for i in range(n):
if nums[i]<maxnum: # 从左向右 若找到比当前最大值小的则更新右边界,否则更新最大值(因为进入右端就一定大于最大值)
end = i
else:
maxnum = nums[i]
if nums[n-1-i]>minnum: # n-1-i是从右向左
start = n-1-i
else:
minnum = nums[n-1-i]
return end-start+1
621 任务调度器
题解
主要思想:找到出现次数最多的字符,结合冷却时间确定固有时间(m-1)×(n+1) + c,其中m是最大的出现次数,n是冷却时间,c是出现次数等于最大出现次数的字符数;当tasks中的字符数超过这个固有容量时,只需在每次的冷却时间后继续添加字符即可,此时需要的时间就是tasks的大小,所以最后的结果应该是max(len(tasks), (m-1)×(n+1) + c)
class Solution:
def leastInterval(self, tasks: List[str], n: int) -> int:
counter = collections.Counter(tasks)
m = 0 # 最大任务数量
c = 0 # 最大任务数量的个数
for i in counter.values():
if i > m:
m = i
c = 1
elif i == m:
c += 1
return max(len(tasks), (m-1)*(n+1) + c)
时:O(N) 空:O(X) 其中N为tasks的大小, X为tasks中字符种类数(最大为26)