返回

40 道最小的 k 个数:剖析四种解法,助力算法新突破

IOS

  1. 暴力法:简单直接,但效率低下
func findKthSmallest(_ nums: [Int], _ k: Int) -> [Int] {
    // 对数组进行排序
    let sortedNums = nums.sorted()
    // 获取前 k 个元素
    return Array(sortedNums[0..<k])
}

暴力法是最直接的解法,它通过对数组进行排序,然后取前 k 个元素即可。这种方法实现简单,但由于需要对整个数组进行排序,时间复杂度为 O(n log n),其中 n 为数组的长度。

2. 快速排序:效率较高,但存在最坏情况

func findKthSmallest(_ nums: [Int], _ k: Int) -> [Int] {
    // 数组拷贝
    var numsCopy = nums
    // 快速排序
    quickSort(&numsCopy, 0, numsCopy.count - 1)
    // 获取前 k 个元素
    return Array(numsCopy[0..<k])
}

func quickSort(_ nums: inout [Int], _ low: Int, _ high: Int) {
    if low < high {
        let pivotIndex = partition(&nums, low, high)
        quickSort(&nums, low, pivotIndex - 1)
        quickSort(&nums, pivotIndex + 1, high)
    }
}

func partition(_ nums: inout [Int], _ low: Int, _ high: Int) -> Int {
    let pivot = nums[high]
    var i = low - 1
    for j in low..<high {
        if nums[j] <= pivot {
            i += 1
            nums.swapAt(i, j)
        }
    }
    nums.swapAt(i + 1, high)
    return i + 1
}

快速排序是一种高效的排序算法,其平均时间复杂度为 O(n log n),最坏情况下的时间复杂度为 O(n^2)。快速排序通过选择一个枢轴元素,将数组划分为两部分,然后递归地对这两部分进行排序。

3. 优先队列:时间复杂度更优,但实现难度较高

func findKthSmallest(_ nums: [Int], _ k: Int) -> [Int] {
    // 创建优先队列,最大堆
    var pq = PriorityQueue<Int>(sort: >)
    // 将前 k 个元素加入优先队列
    for i in 0..<k {
        pq.enqueue(nums[i])
    }
    // 将剩余元素依次加入优先队列,并弹出最大元素
    for i in k..<nums.count {
        pq.enqueue(nums[i])
        pq.dequeue()
    }
    // 将优先队列中的元素取出并返回
    var result = [Int]()
    while !pq.isEmpty {
        result.append(pq.dequeue())
    }
    return result
}

class PriorityQueue<T: Comparable> {
    private var heap: [T] = []
    private let sort: (T, T) -> Bool

    init(sort: @escaping (T, T) -> Bool) {
        self.sort = sort
    }

    func enqueue(_ element: T) {
        heap.append(element)
        heapifyUp(heap.count - 1)
    }

    func dequeue() -> T? {
        guard !heap.isEmpty else {
            return nil
        }
        let first = heap[0]
        heap[0] = heap.removeLast()
        heapifyDown(0)
        return first
    }

    private func heapifyUp(_ index: Int) {
        var childIndex = index
        var parentIndex = (childIndex - 1) / 2
        while childIndex > 0 && sort(heap[childIndex], heap[parentIndex]) {
            heap.swapAt(childIndex, parentIndex)
            childIndex = parentIndex
            parentIndex = (childIndex - 1) / 2
        }
    }

    private func heapifyDown(_ index: Int) {
        var parentIndex = index
        while true {
            let leftChildIndex = 2 * parentIndex + 1
            let rightChildIndex = 2 * parentIndex + 2
            var largestIndex = parentIndex
            if leftChildIndex < heap.count && sort(heap[leftChildIndex], heap[largestIndex]) {
                largestIndex = leftChildIndex
            }
            if rightChildIndex < heap.count && sort(heap[rightChildIndex], heap[largestIndex]) {
                largestIndex = rightChildIndex
            }
            if largestIndex == parentIndex {
                break
            }
            heap.swapAt(parentIndex, largestIndex)
            parentIndex = largestIndex
        }
    }
}

优先队列是一种支持快速查找最大或最小元素的数据结构,其时间复杂度为 O(log n)。优先队列通过维护一个有序的堆结构,并在每次操作时调整堆的结构,从而实现快速查找。

4. 冒泡排序:简单易懂,但效率低下

func findKthSmallest(_ nums: [Int], _ k: Int) -> [Int] {
    // 冒泡排序
    var numsCopy = nums
    for i in 0..<numsCopy.count {
        for j in i+1..<numsCopy.count {
            if numsCopy[j] < numsCopy[i] {
                numsCopy.swapAt(i, j)
            }
        }
    }
    // 获取前 k 个元素
    return Array(numsCopy[0..<k])
}

冒泡排序是一种简单的排序算法,其时间复杂度为 O(n^2)。冒泡排序通过反复比较相邻元素的大小,并交换顺序不正确