TopK
剑指Offer40.最小的k个数,昨天想好的今天就写这道题,不过得用多种方式写出来,所以也算写了五道题,哈哈哈哈。
方式一:普通快排
解析过程:
其实这道题一眼看下去,最直接的方法就是对整个区间进行排序,然后再去输出前k个元素就完成了。快排的时间复杂度是O(nlogn)。
那么我们来回顾一下快排?
整体思路就是选一个元素,将小的放在其左面,大的放在其右面,然后递归执行,直到每个区间有序,使得整个区间有序。最难的也就是这个partition过程了,具体讲解在我的博客排序算法里都有。
代码实现:
class solution {
//方式一:最直接方法,先将整体排序,再输出前k个元素,直接使用快排
public int[] getLeastNumbers(int[] arr, int k) {
quickSort(arr,0,arr.length-1);
int[] res = new int[k];
for(int i = 0; i < k;i++){
res[i] = arr[i];
}
return res;
}
public static void quickSort(int[] arr,int left,int right){
if(left >= right)return;
int i = left,j = right;
while(i < j){
while(i < j && arr[j] >= arr[left])j--;
while(i < j && arr[i] <= arr[left])i++;
swap(arr,i,j);
}
swap(arr,i,left);
quickSort(arr,left,i-1);
quickSort(arr,i+1,right);
}
public static void swap(int[] arr,int i,int j){
int temp = arr[i];
arr[i] = arr[j];
arr[j] = temp;
}
}
方式二:快排思想
解析过程:
有了上面的铺垫,那么方式二才算正式的解题。
因为我们只需要输出前k个元素,而无需对其进行排序,那么快排就是这样的过程啊。我们只要找到k对应的基准值(i),那么也就找到了前k个元素了。
- k == i,说明前k个元素都小于基准值,也就是topK了。
- k < i,说明前k个元素还在左 ;半个区域,那么就继续对左半部分快排。
- k > i,说明前k个元素在右半个区域,对右半部分进行快排。
这时候我们去分析时间复杂度,第一次是N,第二次
代码实现:
class solution{
public int[] getLeastNumbers(int[] arr, int k) {
if(k >= arr.length)return arr;
return quickSort(arr,0,arr.length-1,k);
}
public static int[] quickSort(int[] arr,int left,int right,int k){
int i = left,j = right;
while(i < j){
while(i < j && arr[j] >= arr[left])j--;
while(i < j && arr[i] <= arr[left])i++;
swap(arr,i,j);
}
swap(arr,i,left);
if(i > k)return quickSort(arr,left,i-1,k);
if(i < k)return quickSort(arr,i+1,right,k);
return Arrays.copyOf(arr,k);
}
public static void swap(int[] arr,int i,int j){
int temp = arr[i];
arr[i] = arr[j];
arr[j] = temp;
}
}
方式三:堆排
解析过程:
与之前第一种方式其实是一样的,不过这里就是换了排序算法而已,也是为第四种方式做铺垫。堆排的整体思路是建堆,这里我们是要找最小值,所以建大堆,然后再将根值依次放到无序区间最后一个位置,详细解析之前博客也有,也在排序算法博客中。
代码实现:
class solution{
public int[] getLeastNumbers(int[] arr, int k) {
if(k == 0)return new int[k];
if(k >= arr.length) return arr;
//1.建大堆,使得最大值在数组第一个位置
createBigHeap(arr);
//2.进行堆排
for(int i = 0;i < arr.length-1;i++){
int temp = arr[0];
arr[0] = arr[arr.length-i-1];
arr[arr.length-i-1] = temp;
adJustDown(arr,0,arr.length-i-1);
}
//3.返回前k个数
return Arrays.copyOf(arr,k);
}
//建大堆
public static void createBigHeap(int[] arr){
int lastIndex = arr.length-1;
int lastPIndex = (lastIndex-1)/2;
int size = arr.length;
while(lastPIndex >= 0){
adJustDown(arr,lastPIndex,size);
lastPIndex--;
}
}
//向下调整
public static void adJustDown(int[] arr,int index,int size){
int leftIndex = index*2+1;
//1.判断当前位置是否为叶子节点
while(leftIndex < size){
//找子节点中最大的那个
int maxIndex = leftIndex;
int rightIndex = leftIndex + 1;
if(rightIndex < size && arr[rightIndex] > arr[maxIndex]){
maxIndex = rightIndex;
}
//判断最大值是否大于根节点,如果大于等于则直接返回
if(arr[index] >= arr[maxIndex])break;
//到这里说明是小于,所以与子节点进行交换
int temp = arr[index];
arr[index] = arr[maxIndex];
arr[maxIndex] = temp;
//将index设置为maxIndex
index = maxIndex;
leftIndex = index*2 + 1;
}
}
}
方式四:堆排优化
解析过程:
在堆排的基础上,我们可以对其进行优化,同样的是针对输出前k个元素,对输出的顺序没有要求,那么我们就可以先将原数组的前k个元素单独建堆,然后这时候遍历原数组后面所有的元素,将它们与堆中最大元素进行比较,如果比最大元素小,则交换,对该元素进行向下调整即可。这样的话时间复杂度就优化了很多,为nlogk,注意这里k是一个常数。向下调整以及建大堆过程都是一样的。
代码实现:
class solution{
public int[] getLeastNumbers(int[] arr, int k) {
if(k == 0)return new int[k];
if(k >= arr.length)return arr;
//建一个含有k个元素的大根堆,也就是arr的前k个元素
int[] res = new int[k];
for(int i = 0;i < k;i++){
res[i] = arr[i];
}
createBigHeap(res);
for(int i = k;i < arr.length;i++){
if(arr[i] < res[0]){
res[0] = arr[i];
adJustDown(res,0,k);
}
}
return res;
}
//建大堆
public static void createBigHeap(int[] arr){
int lastIndex = arr.length-1;
int lastPIndex = (lastIndex-1)/2;
int size = arr.length;
while(lastPIndex >= 0){
adJustDown(arr,lastPIndex,size);
lastPIndex--;
}
}
//向下调整
public static void adJustDown(int[] arr,int index,int size){
int leftIndex = index*2+1;
//1.判断当前位置是否为叶子节点
while(leftIndex < size){
//找子节点中最大的那个
int maxIndex = leftIndex;
int rightIndex = leftIndex + 1;
if(rightIndex < size && arr[rightIndex] > arr[maxIndex]){
maxIndex = rightIndex;
}
//判断最大值是否大于根节点,如果大于等于则直接返回
if(arr[index] >= arr[maxIndex])break;
//到这里说明是小于,所以与子节点进行交换
int temp = arr[index];
arr[index] = arr[maxIndex];
arr[maxIndex] = temp;
//将index设置为maxIndex
index = maxIndex;
leftIndex = index*2 + 1;
}
}
}