基础算法9 - 排序

214 阅读5分钟

考点

  • 常考的 x5:
    • merge sort - O(nlogn)
    • quick sort (quick select) - Avg O(N)
    • bucket sort - 严格O(N)
    • counting sort - 严格O(N)
    • heap sort - O(nlogn)
  • 少考的 x1:
    • pancake sort
  • 不考的 x5:
    • bubble sort
    • selection sort
    • insertion sort
    • shell sort
    • radix sort - O(N * K)

Quick Sort

image.png

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)

image.png

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

image.png

模版

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)

image.png

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

image.png\

Solu:

  • 在merge时,一旦有l1[i] > l2[j],那么l2[j]和l1[i : ]中的任意元素都可以产生一个inversion count

image.png

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)

image.png

Solu:merge sort

image.png

image.png

  • 在「归并」的过程中,元素的位置会发生变化,不便于誊写答案 -> 使用「索引数组」
    • 实际修改的是「索引数组」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

image.png

  • 父节点: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:

  1. 对于input data arr建立最大堆
    • rearrange后的arr不一定是sorted的,但堆顶一定是max(arr)
  2. 将最大元素(堆顶)放置在arr末端
  3. 对尚未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

  1. 用一个count_arr计算每个num的frequency
  2. 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)

image.png

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

image.png


451. 根据字符出现频率排序

image.png

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

  • 先从最低位(个位)开始比较,一步一步退到最高位

image.png

模版

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)

image.png

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)

image.png

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: