912. 排序数组(快速排序)
给你一个整数数组 nums
,请你将该数组升序排列。
示例 1:
输入:nums = [5,2,3,1]
输出:[1,2,3,5]
示例 2:
输入:nums = [5,1,1,2,0,0]
输出:[0,0,1,1,2,5]
这道题是LeetCode的912题,要求将一个数组排序,我们本文给出其快速排序的解法。
这次直接先上代码:
class Solution {
Random random = new Random();
public int[] sortArray(int[] nums) {
quickSort(nums,0,nums.length-1);
return nums;
}
public void quickSort(int[] nums,int left,int right){
if(left < right){
//一定要加判断,否则在测试用例1中如果随机到0,那么5将会被移到数组的末尾,返回的index为3
//那么下次划分区间的时候,[index+1,right]的区间值就会变成了[4,3]
//会报IllegalArgumentException非法参数异常
int index = partition(nums,left,right);
quickSort(nums,left,index-1);
quickSort(nums,index+1,right);
}
}
public int partition(int[] nums,int left,int right){
int randomIndex = random.nextInt(right-left+1)+left;
swap(nums,left,randomIndex);
int lt = left;
int privot = nums[left];
for(int i=left+1; i<=right; i++){
if(lt <= right && nums[i] < privot){
lt++;
swap(nums,lt,i);
}
}
swap(nums,lt,left);
return lt;
}
public void swap(int[] num,int i,int j){
int temp = num[i];
num[i] = num[j];
num[j] = temp;
}
}
我们可以看到,快拍的中心思想,就是每次将一个数字放到它自己本来的位置,然后这个数字就将整个数组分成了两个数组,再对这两个数组进行递归处理即可。
quickSort
为快排函数,调用partition
函数,拿到返回的位置索引,我们就可以根据这个索引将原数组分割为两个子数组,做递归处理。
partition
为快排函数中的核心函数,该函数的作用是从给定数组的给定区间中随机选取一个数字,然后将这个数字放在其排序后应在的位置,并将这个位置的索引返回。
swap
函数是一个工具函数,主要作用是将给定数组中下标为i和下标为j的元素做交换。
- 时间复杂度:
O(nlogn)
。 - 空间复杂度:
O(n)
。
下面举例说明
第一次调用quickSort
,传入参数数组nums
,left = 0
, right = nums.length-1=5
假设第一次产生的随机数为random.nextInt(right-left+1)+0 = 3
,这个数就是我们选中做枢纽元素的树,接下来我们会找到他在排序好的数组中的位置,并将它放回去。也就是说,最终这个元素左边的数都不大于它,这个元素右边的数都不小于它。
交换nums[left]
和nums[randomIndex]
的位置,即交换nums[0]
和nuns[3]
的位置。
我们用变量privot
来保存这个枢纽元素的值,用变量lt
来指向这个数组最左边的位置。然后从数组下标i = left+1
即i =1
开始,向后遍历数组,将每个比枢纽元素大的数和x和x后面第一个小于等于枢纽元素的数交换位置。
可以看出,指针lt
是用来指向大于枢纽元素privot
的数的,刚开始lt
和i
一起向后走,当遇到比privot
大的数的时候,lt
停下来记录这个数的位置,而指针i
继续向后走的过程中,一旦发现小于privot
的数,便和lt
指向的数交换位置,重复此过程。
当i=3
时,nums[i]=5 > privot=3
,此时,lt
不再继续随着指针i
向前移动,而是留下来定位这个比privot
大的元素。
随着i继续向前移动,找到了第一个比privot
小的值,将其和刚才比privot
大的值进行交换位置。
随后,i
和lt
一起向前,如果i
碰到比privot
小的元素就会和lt
交换位置,保证比privot
小的元素全部在lt
的前面。
至此我们可以确定当前lt位置的左边都是比privot小的值(left除外,里面保存着privot的值),lt位置的右边都是比privot大的值,然后将nums[left]
和nums[lt]
交换位置
此时,lt
的值为privot=2
,其位置左边的数值皆小于等于privot
,其位置右边的数值皆大于privot
,此时元素privot
已经处于其最终的位置,返回值lt = 4
。
我们接下来需要对nums
数组的[0,3]
区间和[5,5]
区间进行排序,由于5=5
所以我们只需要对[0,3]
区间执行快速排序。
先来一起分析快速排序核心代码partition
的执行过程。
这次我们假设随机值为randomIndex=random.nextInt(right-left+1)+left =1
交换left
和randomIndex
的位置
不巧的是,这次给定的区间范围内没有比privot
大的值,于是lt
和i
均指向了元素nums[right]
,交换了当前nums[lt]
和nums[left]
元素的位置,返回lt=3
根据递归要求,我们接下来需要对区间[0,2]和区间[4,3]进行快速排序,显然[4,3]区间范围不合法。
于是我们对nums数组的[0,2]区间进行快速排序。
一起分析快速排序核心代码partition
的执行过程。
这次我们假设随机值为randomIndex=random.nextInt(right-left+1)+left =1
同样的过程,交换位置,遍历数组。
虽然找到了比privot
大的数,但由于在right
位置,所以不用交换了,返回lt=1
。
按照递归,应当与nums数组的区间[0,0]和[2,2]区间进行快速排序,因为其均不满足left<right的条件,故递归结束。
整个快速排序过程结束。