在未排序的数组中找到第 k 个最大的元素。请注意,你须要找的是数组排序后的第 k 个最大的元素,而不是第 k 个不一样的元素。ios
输入: (分别是数组长度、k值。这里
与力扣题目要求略有不一样
,请本身根据须要酌情修改代码)
6 2
[3,2,1,5,6,4]web
输出:
5算法
实现思路分析:
很容易想到的办法是,给未排序的数组排序,若是是升序排列,假设数组长度为n,那么所求第k个最大元素的值即下标为(n-k)对应的值
。
选择一个高效的排序算法,写好对应的输入输出函数便可。通常可采用堆排序、快速排序等时间复杂度、空间复杂度都比较低的算法,本文采用快速排序。数组
优化:
容易想到:冒泡排序中,第k大的元素在第k趟就已经排好了,能够直接提取,后面的(n-k)趟实际上是不须要的操做,那么快速排序是否也存在这种“省力”的机会呢?架构
快速排序的思想是:选取一个pivot枢纽元素,比它大的放在右边,比它小的放在左边, 针对左边的一部分,重复上述操做递归。若是这个pivot通过第一轮递归后的位置是正好是(n-k),那么能够直接返回pivot的值即答案。若是(n-k)在pivot下标值的右边,那么只须要递归执行右边部分寻找便可,这样算法的时间复杂度可从 n log n 进一步下降到 n
。svg
上面思路提及来简单,然而实现起来确实另外一回事了。(哭泣)若是好久不看而后忽然考到真的写不出来。难就难在,每个变量的做用、变量在循环外仍是循环内,如何实现递归、正确终止递归
,以及最难的,在不开辟新数组空间的状况下,如何实现(与pivot比较后)大小数的交换。即 partition 部分算法的实现。
函数
首先说递归部分
,有两种常见的实现方式。一种是单独写partition部分,将其返回值做为递归数组的边界;另外一种是只用一个函数搞定,直接在内部传递pivot下标值递归。
大体实现思路分别以下:测试
第一种:优化
int partition(int arr[], int left, int right) //找基准数 划分 { ...... //为了方便看清递归架构,此处具体实现省略,后面3再具体讲。 ...... } void quick_sort(int arr[], int left, int right) { if (left > right) return; int j = partition(arr, left, right); quick_sort(arr, left, j - 1); quick_sort(arr, j + 1, right); }
第二种:ui
void QuickSort(int array[], int start, int last) { int i = start; int j = last; int pivot = array[i]; if (i < j) { ...... //此处依然省略partition部分的代码 ...... //把基准数放到i位置 array[i] = pivot; //递归方法 QuickSort(array, start, i - 1); QuickSort(array, i + 1, last); } }
利用双指针
的思想,从左右两头开始向中间走,边走边和pivot的值比较,左边应该比pivot小,若是遇到比pivot大的就停下来准备交换,右边同理,遇到比pivot小的就中止移动指针。对于具体如何实现交换
,主要有两种实现思路:
第一种是:
- 先走right,右边找到小于pivot的值时,right指针停下;
(必定要先走right,由于它遇到比pivot小的值停下,最后 left 和 right 遇到的时候,能够拿两者指向的值与pivot交换,先走right能够保证它必定小于pivot。)
- 再走left,左边找到大于pivot的值时,left指针停下;
- 此时若是 left < right,交换两者;
- 循环上述三步,直到 left 和right 相遇;
- 交换当前 left(right)指针指向的位置与初始first位置的数值。
代码以下:
int partition(int *nums, int left, int right) { int pivot = nums[left]; int first = left; while (left < right) { while (left < right & nums[right] >= pivot) { right--; } while (left < right & nums[left] <= pivot) { left++; } if(left < right){ int tem = nums[right]; nums[right] = nums[left]; nums[left] = tem; }; } nums[first] = nums[left]; nums[left] = pivot; return left; }
第二种是:
- 选取 left 指针指向的值为pivot(首个元素);
- 先走 right,遇到比pivot小的值,停下指针,将这个值赋给 left 指针指向的元素;(此时,left 的值已经赋值给了pivot,因此直接覆盖没问题)
- 再走 left,遇到比 pivot 大的值,就停下指针。将 left 中的值赋给第2步中 right 指针指向的元素;(第2步中,刚给 left 赋的值是必定小于pivot的)
- 循环执行二、3步,直至 left 和 right 指针相遇;
- 至此,二、3两步就完成了交换一大一小两个值,而后最开始 0 位置的值存在 pivot中,将 pivot 放到排序以后的枢纽位置,即 left 和 right 指针指向的位置。
代码以下:
int partition(vector<int> &nums, int left, int right) { int pivot = nums[left]; while (left < right) { while (left < right & nums[right] >= pivot) { right--; } nums[left] = nums[right]; while (left < right & nums[left] < pivot) { left++; } nums[right] = nums[left]; } nums[left] = pivot; return left; }
最后附上一份能够直接在编译器 / IDE中执行的C++代码:
#include <iostream> #include <stdio.h> #include <stdlib.h> int partition(int *nums, int left, int right) { int pivot = nums[left]; int first = left; while (left < right) { while (left < right & nums[right] >= pivot) { right--; } while (left < right & nums[left] <= pivot) { left++; } if(left < right){ int tem = nums[right]; nums[right] = nums[left]; nums[left] = tem; }; } nums[first] = nums[left]; nums[left] = pivot; return left; } int theKthNumber(int *nums, int n, int k){ int left = 0; int right = n - 1; int target = n - k; while (true) { int p = partition(nums, left, right); if (p == target) { return nums[p]; } else if (target < p) { right = p - 1; } else { left = p + 1; } } } int main(){ int len,k; scanf("%d%d", &len, &k); int *N = (int *)malloc(len*(sizeof(int))); for (int i = 0; i <len ; ++i) { scanf("%d", &N[i]); } printf("%d\n",theKthNumber(N, len, k)); return 0; }