【LeetCode】912. 排序数组、手撕快速排序
题目
【LeetCode】912. 排序数组
给你一个整数数组 nums
,请你将该数组升序排列。
你必须在 不使用任何内置函数 的情况下解决问题,时间复杂度为 O(nlog(n))
,并且空间复杂度尽可能小。
示例 1:
输入:nums = [5,2,3,1] 输出:[1,2,3,5] 解释:数组排序后,某些数字的位置没有改变(例如,2 和 3),而其他数字的位置发生了改变(例如,1 和 5)。
示例 2:
输入:nums = [5,1,1,2,0,0] 输出:[0,0,1,1,2,5] 解释:请注意,nums 的值不一定唯一。
提示:
1 <= nums.length <= 5 * 104
-5 * 104 <= nums[i] <= 5 * 104
解题思路
核心要点:
- 将整数数组按升序排列
- 数组长度可能达到 5×10^4,需要高效的排序算法
- 可以使用多种排序算法,选择时间复杂度较低的算法更合适
对于这个问题,快速排序是一个很好的选择,它的平均时间复杂度为 O(n log n),在实际应用中表现优异。快速排序的基本思想是:
1. 选择一个基准元素(通常是数组的第一个或最后一个元素)
2. 将数组分区:所有比基准小的元素移到基准前面,所有比基准大的元素移到基准后面
3. 递归地对基准前后的子数组进行排序
代码实现
import java.util.Arrays;public class SortArray {public int[] sortArray(int[] nums) {quickSort(nums, 0, nums.length-1);return nums;}void quickSort(int[]nums,int l,int r){if(l >= r)return;//选取基准值x,此处为数组中间元素的值 //>>>是无符号右移,等价(l + r) / 2int i = l - 1,j = r + 1,x = nums[(l + r)>>>1];while(i < j){// 从左向右找到第一个大于x的元素do i++;while(nums[i] < x);// 从右向左找到第一个小于x的元素do j--;while(nums[j] > x);// 如果i和j没有交错,则交换nums[i]和nums[j]的值if(i < j)swap(i,j,nums);}/*while(i < j)循环结束时,i和j可能满足以下三种情况之一:i == j(刚好相遇)i > j(交叉)i指向第一个≥pivot的元素,j指向最后一个≤pivot的元素关键特性:无论哪种情况,j左侧的所有元素都≤pivot,j右侧的所有元素都≥pivot。因此j是安全的分区点。*/// 对基准值左侧的子数组进行快速排序quickSort(nums, l, j);// 对基准值右侧的子数组进行快速排序quickSort(nums, j+1, r);}void swap(int i,int j,int [] nums){int temp = nums[i];nums[i] = nums[j];nums[j] = temp;}// 测试示例public static void main(String[] args) {SortArray solution = new SortArray();int[] nums1 = {5,2,3,1};System.out.println(Arrays.toString(solution.sortArray(nums1))); // 输出:[1,2,3,5]int[] nums2 = {5,1,1,2,0,0};System.out.println(Arrays.toString(solution.sortArray(nums2))); // 输出:[0,0,1,1,2,5]int[] nums3 = {3,1,2};System.out.println(Arrays.toString(solution.sortArray(nums3))); // 输出:[1,2,3]}
}
复杂度分析
- 时间复杂度:O(n log n),其中 n 是数组的长度。快速排序的平均时间复杂度为 O(n log n),最坏情况下为 O(n²),但通过合理选择基准元素(如随机选择)可以避免最坏情况。
- 空间复杂度:O(log n),主要是递归调用栈的空间开销,最坏情况下为 O(n)。
其他可行解法
除了快速排序,还有其他高效的排序算法可以解决这个问题:
- 归并排序:时间复杂度 O(n log n),空间复杂度 O(n),是一种稳定的排序算法
- 堆排序:时间复杂度 O(n log n),空间复杂度 O(1),但实际性能略逊于快速排序
- Java 内置排序:可以直接使用
Arrays.sort(nums)
,底层通常采用双轴快速排序(Dual-Pivot QuickSort)
对于本题,快速排序是综合性能较好的选择,既保证了高效性,又易于理解和实现。
关键要点
- 快速排序的核心是分区操作,通过选择基准元素将数组分为两部分
- 递归思想的应用,将大问题分解为小问题解决
- 交换操作的实现,用于调整元素位置
- 边界条件的处理,当子数组长度为 1 时停止递归