912. Sort an Array
MediumDescription
給定整數陣列
nums,將其升序排序並回傳。必須在 O(n log n) 時間複雜度內完成,且盡量使用最少的空間。不能使用內建排序函式。
Example:
Input: nums = [5,2,3,1] Output: [1,2,3,5]
Intuition#
核心思路:要求 O(n log n) 且不用內建排序,Merge Sort 是最穩定的選擇。Quick Sort 在最壞情況下 O(n^2) 可能 TLE,需要隨機 pivot。
- Merge Sort:穩定、保證 O(n log n),但需要 O(n) 額外空間
- Quick Sort:平均 O(n log n),但最壞 O(n^2)(需隨機化 pivot 避免)
- Heap Sort:O(n log n) 且 O(1) 空間,但常數因子較大
Approaches#
1: Merge Sort#
- 概念: 分治法——將陣列一分為二,遞迴排序後合併
- 時間複雜度:
O(n log n)- 保證 - 空間複雜度:
O(n)- 合併時需要暫存陣列
class Solution {
fun sortArray(nums: IntArray): IntArray {
if (nums.size <= 1) return nums
val mid = nums.size / 2
val left = sortArray(nums.copyOfRange(0, mid))
val right = sortArray(nums.copyOfRange(mid, nums.size))
return merge(left, right)
}
private fun merge(left: IntArray, right: IntArray): IntArray {
val result = IntArray(left.size + right.size)
var i = 0; var j = 0; var k = 0
while (i < left.size && j < right.size) {
if (left[i] <= right[j]) {
result[k++] = left[i++]
} else {
result[k++] = right[j++]
}
}
while (i < left.size) result[k++] = left[i++]
while (j < right.size) result[k++] = right[j++]
return result
}
}⭐2: Quick Sort(隨機 Pivot)#
- 概念: 隨機選 pivot,將小於 pivot 的放左邊、等於放中間、大於放右邊(三向切分),遞迴排序左右部分
- 時間複雜度: 平均
O(n log n),最壞O(n^2)(隨機化後極不可能) - 空間複雜度:
O(log n)- 遞迴堆疊
class Solution {
fun sortArray(nums: IntArray): IntArray {
quickSort(nums, 0, nums.size - 1)
return nums
}
private fun quickSort(nums: IntArray, lo: Int, hi: Int) {
if (lo >= hi) return
// 隨機選 pivot
val pivotIdx = lo + (Math.random() * (hi - lo + 1)).toInt()
val pivot = nums[pivotIdx]
// 三向切分:[lo, lt) < pivot, [lt, gt] == pivot, (gt, hi] > pivot
var lt = lo; var i = lo; var gt = hi
while (i <= gt) {
when {
nums[i] < pivot -> {
nums[i] = nums[lt].also { nums[lt] = nums[i] }
lt++; i++
}
nums[i] > pivot -> {
nums[i] = nums[gt].also { nums[gt] = nums[i] }
gt--
}
else -> i++
}
}
quickSort(nums, lo, lt - 1)
quickSort(nums, gt + 1, hi)
}
}補充:Heap Sort
使用最大堆的性質,先建堆再逐一取出最大元素放到陣列末尾。O(1) 額外空間。
class Solution {
fun sortArray(nums: IntArray): IntArray {
// Build max heap
for (i in nums.size / 2 - 1 downTo 0) {
heapify(nums, nums.size, i)
}
// Extract elements one by one
for (i in nums.size - 1 downTo 1) {
nums[0] = nums[i].also { nums[i] = nums[0] }
heapify(nums, i, 0)
}
return nums
}
private fun heapify(nums: IntArray, n: Int, i: Int) {
var largest = i
val left = 2 * i + 1
val right = 2 * i + 2
if (left < n && nums[left] > nums[largest]) largest = left
if (right < n && nums[right] > nums[largest]) largest = right
if (largest != i) {
nums[i] = nums[largest].also { nums[largest] = nums[i] }
heapify(nums, n, largest)
}
}
}🔑 Takeaways#
- Pattern: 排序演算法實作是面試基本功,必須能手寫 Merge Sort 和 Quick Sort
- 關鍵技巧: Quick Sort 一定要隨機化 pivot,否則 LeetCode 的測資會卡到 O(n^2);三向切分(Dutch National Flag)處理大量重複元素特別有效