排序专题

166 阅读3分钟

1. 排序

面试常考三个排序:归并排序、快排和堆排

1. 归并排序

基本思想:分而治之,每次将数组对半分,借助额外空间,合并两个有序数组,得到一个更大的有序数组
时间复杂度:O(N logN)
空间复杂度:O(N)
是稳定性的算法
可视化:www.cs.usfca.edu/~galles/vis… 中的 Merge Sort

88. 合并两个有序数组

做一次归并

func merge(nums1 []int, m int, nums2 []int, n int)  {
    buff := make([]int, 0, m+n)
    left, right := 0, 0
    for left < m && right < n {
        if nums1[left] < nums2[right] {
            buff = append(buff, nums1[left])
            left++
        } else {
            buff = append(buff, nums2[right])
            right++
        }
    }
    buff = append(buff, nums1[left:m]...)
    buff = append(buff, nums2[right:n]...)
    for i, v := range buff {
        nums1[i] = v
    }
}

912. 排序数组

func sortArray(arr []int) []int {
    mergeSort(arr, 0, len(arr)-1)
    return arr
}

func mergeSort(arr []int, left, rightEnd int) {
    if left < rightEnd {
        mid := left + (rightEnd-left) >> 1
        mergeSort(arr, left, mid)
        mergeSort(arr, mid+1, rightEnd)
        // merge [left,mid] [mid+1,rightEnd]
        merge(arr, left, mid, mid+1, rightEnd)
    }
}

func merge(arr []int, left, leftEnd, right, rightEnd int) {
    size := rightEnd-left+1
    buff := make([]int, 0, size)
    start := left
    for left <= leftEnd && right <= rightEnd {
        if arr[left] < arr[right] {
            buff = append(buff, arr[left])
            left++
        } else {
            buff = append(buff, arr[right])
            right++
        }
    }
    // 追加[left,leftEnd]
    buff = append(buff, arr[left:leftEnd+1]...)
    // 追加[right,rightEnd]
    buff = append(buff, arr[right:rightEnd+1]...)
    for i, v := range buff {
        arr[start+i] = v
    }
}

剑指 Offer 51. 数组中的逆序对

在每次合并的时候判断一组逆序对
例如:
left: [1,5,7] right:[2,3,6]
当 left[1]= 5 <= right[2]=6,那 5 肯定比 6 前面的数大,可以一次算到 [5,2] [5,3] 两个逆序对
问题:

  1. 逆序对更新时机是?当左边值大于等于右边值时更新,如果相等时让 right 走了一步,那下次更新时,右边可能存在和左边更新时相等的值
  2. 逆序对更新的值是?右边走的步数,right - leftEnd - 1,额外减 1,因为初始 right 没走的时候,值肯定是 0
var res int
func reversePairs(arr []int) int {
    res = 0
    mergeSort(arr, 0, len(arr)-1)
    return res
}
func mergeSort(arr []int, left, rightEnd int) {
    if left < rightEnd {
        mid := left + (rightEnd-left) >> 1
        mergeSort(arr, left, mid)
        mergeSort(arr, mid+1, rightEnd)
        merge(arr, left, mid, mid+1, rightEnd)
    }
}
func merge(arr []int, left, leftEnd, right, rightEnd int) {
    size := rightEnd-left+1
    buff := make([]int, 0, size)
    start := left
    for left <= leftEnd && right <= rightEnd {
        if arr[left] <= arr[right] {
            buff = append(buff, arr[left])
            res += right-leftEnd-1
            left++
        } else {
            buff = append(buff, arr[right])
            right++
        }
    }
    for left <= leftEnd {
        buff = append(buff, arr[left])
        res += right-leftEnd-1
        left++
    }
    for right <= rightEnd {
        buff = append(buff, arr[right])
        right++
    }
    for i, v := range buff {
        arr[i+start] = v
    }
}

2. 快排

基本思想:每次选定一个元素排序让它待在应该待的地方,然后递归左右部分,依次下去,直到数组有序
时间复杂度:O(N logN)
空间复杂度:O(logN)
不是稳定性的排序算法
可视化:www.cs.usfca.edu/~galles/vis… 中的 Quick Sort

912. 排序数组

填坑法,要排序的元素挖个坑,然后从后往前找个更大的元素挖掉,把坑填上,再从前往后找个更小的元素把坑挖掉,填上一个的坑,最后用最开始要排序的元素去填上一轮的坑

func sortArray(nums []int) []int {
    quickSort(nums, 0, len(nums)-1)
    return nums
}

func quickSort(arr []int, l, r int) {
    if l < r {
        idx := partition(arr, l, r)
        quickSort(arr,l, idx-1)
        quickSort(arr,idx+1, r)
    }
}
func partition(arr []int, l, r int) int {
    // 在 l 处挖个坑
    p := arr[l]
    for l < r {
        // 从右往左找到比坑更小的值
        for l < r && arr[r] >= p {
            r--
        }
        // 用新找到的数填坑
        arr[l] = arr[r]
        // 从左往右找到比坑更大的值
        for l < r && arr[l] <= p {
            l++
        }
        // 用新找到的数填坑
        arr[r] = arr[l]
    }
    // 最终填坑
    arr[l] = p
    return l
}

75. 颜色分类

func sortColors(nums []int)  {
    quickSort(nums, 0, len(nums)-1)
}
func quickSort(nums []int, l, r int) {
    // [l, r]
    if l < r {
        idx := partition(nums, l, r)
        quickSort(nums, l, idx-1)
        quickSort(nums, idx+1, r)
    }
}
func partition(nums []int, l, r int) int {
    p := nums[l]
    for l < r {
        for l < r && nums[r] >= p {
            r--
        }
        nums[l] = nums[r]
        for l < r && nums[l] <= p {
            l++
        }
        nums[r] = nums[l]
    }
    nums[l] = p
    return l
}

3. 堆排

基本思想:将所有待排序元素存入一个用数组存储的完全二叉树中,该完全二叉树的特征是每个节点的值是其子树所有节点值中的最大值(或最小值),插入和删除元素都需要维持这个特征
时间复杂度:O(N logN)
空间复杂度:O(1)
不是稳定性的排序算法
可视化:www.cs.usfca.edu/~galles/vis… 中的 Heap Sort

912. 排序数组

最小堆

// 最小堆
func sortArray(nums []int) []int {
    buildMinHeap(nums)
    return getK(nums, len(nums))
}

func getK(nums []int, k int) []int {
    res := make([]int, 0, k)
    for i := 0; i < k; i++ {
        // 拿到当前堆的最小值
        res = append(res, nums[0])
        // 最小值出堆
        // 把要出堆的元素换到最后
        nums[0], nums[len(nums)-1] = nums[len(nums)-1], nums[0]
        // 删除最后一个元素
        nums = nums[:len(nums)-1]
        // 调整当前堆
        adjust(nums, 0)
    }
    return res
}

func buildMinHeap(nums []int) {
    // 从最后一个有子节点的节点开始找
    for i := len(nums)/2; i>= 0; i-- {
        adjust(nums, i)
    }
}

func adjust(nums []int, i int) {
    // 左右儿子
    l, r, less := i*2+1, i*2+2, i
    // 有左儿子且更小
    if l < len(nums) && nums[l] < nums[less] {
        less = l
    }
    // 有右儿子且更小
    for r < len(nums) && nums[r] < nums[less] {
        less = r
    }
    // 儿子更小
    if less != i {
        nums[i], nums[less] = nums[less], nums[i]
        // 儿子可能也需要调整
        adjust(nums, less)  
    }
}

215. 数组中的第K个最大元素

最大堆

// 最大堆
func findKthLargest(nums []int, k int) int {
    buildHeap(nums)
    return getK(nums, k)
}

func getK(nums []int, k int) int {
    ans := 0
    for i := 0; i < k; i++ {
        ans = nums[0]
        nums[0], nums[len(nums)-1] = nums[len(nums)-1], nums[0]
        nums = nums[:len(nums)-1]
        adjust(nums, 0)
    }
    return ans
}

func buildHeap(nums []int) {
    for i := len(nums)/2; i >= 0; i-- {
        adjust(nums, i)
    }
}

// 区别在调整时,是找最大值作为父节点
func adjust(nums []int, i int) {
    l, r, largest := i*2+1, i*2+2, i
    if l < len(nums) && nums[l] > nums[largest] {
        largest = l
    }
    if r < len(nums) && nums[r] > nums[largest] {
        largest = r
    }
    if largest != i {
        nums[largest], nums[i] = nums[i], nums[largest]
        adjust(nums, largest)
    }
}

剑指 Offer 40. 最小的k个数

最小堆

// 最小堆
func getLeastNumbers(arr []int, k int) []int {
    buildHeap(arr)
    return getK(arr, k)
}

func getK(arr []int, k int) []int {
    res := make([]int, 0, k)
    for i := 0; i < k; i++ {
        res = append(res, arr[0])
        arr[0], arr[len(arr)-1] = arr[len(arr)-1], arr[0]
        arr = arr[:len(arr)-1]
        adjust(arr, 0)
    }
    return res
}

func buildHeap(arr []int) {
    for i := len(arr)/2; i >= 0; i-- {
        adjust(arr, i)
    }
}

func adjust(arr []int, i int) {
    l, r, less := i*2+1, i*2+2, i
    if l < len(arr) && arr[l] < arr[less] {
        less = l
    }
    if r < len(arr) && arr[r] < arr[less] {
        less = r
    }
    if less != i {
        arr[less], arr[i] = arr[i], arr[less]
        adjust(arr, less)
    }
}


堆排序的插入操作

// 最小堆插入操作
func (t *MedianFinder) minHeapInsert(val int) {
    // 当前插入最后一个
    t.minHeap = append(t.minHeap, val)
    i := len(t.minHeap)-1
    // 需要往上浮,和当前节点的父节点对比,谁更小
    for i > 0 && t.minHeap[i] < t.minHeap[(i-1)/2] {
        swap(t.minHeap, i, (i-1)/2)
        // 继续看父节点
        i = (i-1)/2
    }
}