考点
- 常考的 x5:
- merge sort -
O(nlogn) - quick sort (quick select) - Avg
O(N) - bucket sort - 严格
O(N) - counting sort - 严格
O(N) - heap sort -
O(nlogn)
- merge sort -
- 少考的 x1:
- pancake sort
- 不考的 x5:
- bubble sort
- selection sort
- insertion sort
- shell sort
- radix sort -
O(N * K)
Quick Sort
partition时:
pivot = nums[right]wall左侧的num都< pivot;右侧都> pivot
模版
class Solution:
def quicksort(self, nums):
self.helper(nums, 0, len(nums) - 1)
return nums
def helper(self, nums, start, end) -> None:
if start >= end:
return
pivot = self.partition(nums, start, end)
self.helper(start, pivot - 1)
self.helper(pivot + 1, end)
def partition(self, nums, start, end) -> int:
pivot = nums[end]
wall = start
for i in range(start, end):
if nums[i] < pivot:
nums[i], nums[wall] = nums[wall], nums[i]
wall += 1
nums[wall], nums[end] = nums[end], nums[wall]
return wall
215. 数组中的第K个最大元素(Medium)
Solu:
- Quickselect:find the k-th smallest element in an unordered list
- 把
> pivot的数字都排到前面,一旦最终pivot's idx == k - 1,则找到了k-th biggest
Code:
class Solution:
def findKthLargest(self, nums: List[int], k: int) -> int:
def partition(l, r) -> int: # divide
pivot, wall = nums[r], l
for i in range(l, r):
if nums[i] > pivot:
nums[i], nums[wall] = nums[wall], nums[i]
wall += 1
nums[r], nums[wall] = nums[wall], nums[r]
return wall
def helper(l, r): # conquer
if l >= r:
return
idx = partition(l, r)
if idx == k - 1:
return
elif idx < k - 1:
helper(idx + 1, r)
else:
helper(l, idx - 1)
helper(0, len(nums) - 1)
return nums[k - 1]
Merge Sort
模版
class Solution:
def mergesort(self, nums):
def merge(left, right):
res = []
i, j = 0, 0
while i < len(left) and j < len(right):
if left[i] <= right[j]:
res.append(left[i])
i += 1
else:
res.append(right[j])
j += 1
while i < len(left):
res.append(left[i])
i += 1
while j < len(right):
res.append(right[j])
j += 1
return res
def helper(l, r):
if l == r:
return [nums[l]]
mid = (l + r) // 2
left = helper(l, mid)
right = helper(mid + 1, r)
return merge(left, right)
return helper(0, len(nums) - 1)
148. 排序链表(Medium)
Solu:
- array sort变成ListNode sort,本质上不变,照抄模版
Code:
class Solution:
def sortList(self, head: Optional[ListNode]) -> Optional[ListNode]:
def merge(l1, l2):
dummy = ListNode(0)
cur = dummy
i, j = l1, l2
while i and j:
if i.val < j.val:
cur.next = i
i = i.next
else:
cur.next = j
j = j.next
cur = cur.next
cur.next = i if i else j
return dummy.next
if not head or not head.next:
return head
slow, fast = head, head.next
while fast and fast.next:
slow = slow.next
fast = fast.next.next
l1, l2 = head, slow.next
slow.next = None
l1 = self.sortList(l1)
l2 = self.sortList(l2)
return merge(l1, l2)
Count Inversion
\
Solu:
- 在merge时,一旦有
l1[i] > l2[j],那么l2[j]和l1[i : ]中的任意元素都可以产生一个inversion count
Code:
class Solution:
def invCount(self, nums):
def mergeAndCount(l1, l2):
count = 0
l3 = []
while l1 and l2:
if l1[0] > l2[0]:
count += len(l1)
l3.append(l2.pop(0))
else:
l3.append(l1.pop(0))
if l1:
l3.extend(l1)
if l2:
l3.extend(l2)
return l3, count
if len(nums) < 2:
return 0
mid = len(nums) // 2
leftCopy = copy.copy(nums[:mid])
rightCopy = copy.copy(nums[mid:])
left = self.invCount(leftCopy)
right = self.invCount(rightCopy)
_, count = mergeAndCount(leftCopy, rightCopy)
return left + right + count
❤️ 315. 计算右侧小于当前元素的个数(Hard)
Solu:merge sort
- 在「归并」的过程中,元素的位置会发生变化,不便于誊写答案 -> 使用「索引数组」
- 实际修改的是「索引数组」
aux;但实际拿来比较的是nums
- 实际修改的是「索引数组」
nums[:mid+1]和nums[mid+1:]中各自的「逆序对」已经在递归调用的__merge_and_count_smaller中计算好了,接下来只需要计算对于每个nums[i] (0 ≤ i ≤ mid),有多少个在它右边的元素比它自身小
Code:
class Solution:
def countSmaller(self, nums: List[int]) -> List[int]:
n = len(nums)
res = [0] * n
nums = [(i, num) for i, num in enumerate(nums)] # idx : num
def mergeAndCount(nums1, nums2): # nums1 and nums2 are both sorted ascendingly
ans = []
l1, l2 = len(nums1), len(nums2)
i, j = 0, 0
for _ in range(l1 + l2):
if i == l1:
ans.append(nums2[j])
j += 1
elif j == l2:
ans.append(nums1[i])
res[nums1[i][0]] += l2
i += 1
elif nums1[i][1] <= nums2[j][1]:
ans.append(nums1[i])
res[nums1[i][0]] += j
i += 1
else:
ans.append(nums2[j])
j += 1
return ans
def helper(l, r):
if l == r:
return [nums[l]]
mid = (l + r) // 2
left = helper(l, mid)
right = helper(mid + 1, r)
return mergeAndCount(left, right)
helper(0, n - 1)
return res
Heap Sort
- 父节点:
node[k]'s parent = node[(k-1)/2]- 子节点:
node[k]'s children = node[2k+1] and node[2k+2]- 最大堆:
root.val >= max(children's vals)- 最小堆:
root.val <= min(children's vals)
Heap Sort:
- 对于input data
arr建立最大堆- rearrange后的
arr不一定是sorted的,但堆顶一定是max(arr)
- rearrange后的
- 将最大元素(堆顶)放置在
arr末端 - 对尚未sorted的部分(末端idx之前的部分)递归的去heapify,直至
size(heap) == 1
模版
class Solution:
def heapify(self, nums, idx, length) -> None:
largest, l, r = idx, 2 * idx + 1, 2 * idx + 2
if l < length and nums[l] > nums[largest]:
largest = l
if r < length and nums[r] > nums[largest]:
largest = r
if largest != idx:
nums[idx], nums[largest] = nums[largest], nums[idx]
self.heapify(nums, largest, length) # 递归heapify受到影响的左/右subtree
def buildHeap(self, nums) -> None:
for i in range(len(nums) // 2, -1, -1): # 只需要对nums的后一半做heapify
self.heapify(nums, i, len(nums))
def heapSort(self, nums) -> None:
self.buildHeap(nums) # 建立最大堆,rearrange array
for i in range(len(nums) - 1, 0, -1):
nums[0], nums[i] = nums[i], nums[0] # 将最大值放到最后
self.heapify(nums, 0, i) # 将nums[0]~nums[i-1]重新建立最大堆
Counting Sort
- 用一个count_arr计算每个num的frequency
- 对
count_arr做前缀和,这样count_arr[i] = 元素i在output sequence中的位置
模版
class Solution:
def countSort(self, nums):
res = [0] * len(nums)
count = [0] * (256)
for num in nums: # 计算每个元素的frequency
count[num] += 1
for i in range(1, 256): # 前缀和:count[i] = 元素i在sort array中的位置
count[i] += count[i - 1]
for i in range(len(nums) - 1, -1, -1):
res[count[nums[i]] - 1] = nums[i]
count[nums[i]] -= 1
return res
75. 颜色分类(Medium)
Solu 1:双指针
- 双pivot,遇到0向
zero左侧甩;遇到2向two右侧甩swap(i, two)之后,nums[i]仍有可能是2,不能i++swap(i, zero)之后,由于i是正序遍历的,必定可以确保已经处理过的nums[ : i]是sorted的,可以大胆的i++
Code 1:
class Solution:
def sortColors(self, nums: List[int]) -> None:
i, zero, two = 0, 0, len(nums) - 1
while i <= two:
if nums[i] == 1:
i += 1
elif nums[i] == 2:
nums[i], nums[two] = nums[two], nums[i]
two -= 1
else:
nums[i], nums[zero] = nums[zero], nums[i]
zero += 1
i += 1
Solu 2:counting sort
运用counting-sort模版
Code 2:
class Solution:
def sortColors(self, nums: List[int]) -> None:
count = [0] * 3
for num in nums:
count[num] += 1
for i in range(1, 3):
count[i] = count[i - 1] + count[i]
res = [0] * len(nums)
for num in nums:
count[num] -= 1
res[count[num]] = num
nums[:] = res
Bucket Sort
- step1:按照每个元素的共性,分配到不同的bucket里
- step2:对每个bucket进行sort
451. 根据字符出现频率排序
Solu 1:
- 直接在dict内按frequenct(value)进行sort
Code 1:
class Solution:
def frequencySort(self, s: str) -> str:
dic = collections.Counter(s)
dic = sorted(dic.items(), key=lambda x: x[1], reverse=True)
res = ""
for c, freq in dic:
res += c * freq
return res
Solu 2:
- 统计完每个char的frequency之后,具有相同freq的放入同一个bucket里(bucket中顺序无所谓)
Code 2:
class Solution:
def frequencySort(self, s: str) -> str:
dic = collections.Counter(s)
freq = [[] for _ in range(len(s) + 1)]
for c, f in dic.items():
freq[f].append(c)
res = ''
for i in range(len(s) + 1)[::-1]:
for c in freq[i]:
res += c * i
return res
Radix Sort
- 先从最低位(个位)开始比较,一步一步退到最高位
模版
class Solution:
def radixSort(self, nums):
m = max(nums)
exp = 1
while m / exp > 0:
self.countingSort(nums, len(nums), exp)
exp *= 10
def countingSort(self, nums, length, exp):
res = [0] * length
count = [0] * 10
for i in range(length):
count[(nums[i] // exp) % 10] += 1
for i in range(1, 10): # prefix sum
count[i] += count[i - 1]
for i in range(length - 1, -1, -1):
res[count[(nums[i] // exp) % 10] - 1] = nums[i]
count[(nums[i] // exp) % 10] -= 1
nums[:] = res
164. 最大间距(Hard)
Solu:radix sort
O(N)时间复杂度要求 -> radix sort/bucket sort
Code:
class Solution:
def maximumGap(self, nums: List[int]) -> int:
def radixSort():
m = max(nums)
exp = 1
while m // exp > 0:
countingSort(len(nums), exp)
exp *= 10
def countingSort(length, exp):
res = [0] * length
count = [0] * 10
for i in range(length):
count[(nums[i] // exp) % 10] += 1
for i in range(1, 10): # prefix sum
count[i] += count[i - 1]
for i in range(length - 1, -1, -1):
res[count[(nums[i] // exp) % 10] - 1] = nums[i]
count[(nums[i] // exp) % 10] -= 1
for i in range(length):
nums[i] = res[i]
radixSort()
return max(nums[i] - nums[i - 1] for i in range(1, len(nums))) if len(nums) > 1 else 0
Pancake Sort
- 第
k轮(k ≥ 1),把k-th biggest element先翻转到nums[0]处;再把最大值的位置翻转到nums[-1]处;以此类推
969. 煎饼排序(Medium)
Solu:
- 因为满足
1 <= arr[i] <= len(arr)且arr[i] is unique,所以第i轮的largest = len(arr) - i - 假设
arr.index(largest) = k,因此先翻转nums[0]~nums[k],再翻转nums[0]~nums[-1]
Code:
class Solution:
def pancakeSort(self, arr: List[int]) -> List[int]:
def flip(idx):
for i in range(idx // 2 + 1):
arr[i], arr[idx - i] = arr[idx - i], arr[i]
res = []
largest = len(arr)
while largest > 0:
largest_idx = arr.index(largest)
if largest_idx != largest - 1:
flip(largest_idx)
flip(largest - 1)
res.append(largest_idx + 1)
res.append(largest)
largest -= 1
return res
Reference: