题目:215. 数组中的第K个最大元素 - 力扣(LeetCode)
1 冒泡排序
时间复杂度O(n^2)
思路:
每次相邻的元素做比较,将较大或较小的数冒泡到最后。
外层循环表示扫描的趟数,每扫描一趟就有一个数放到应该的位置,n个数所以扫描n趟,但当n-1个数都到正确的位置时,最后一个数也一定是在正确的位置,所以扫描n-1趟就行了。
内层循环表示从头到尾扫描,第i趟已经有i个数排好了,所以可以不用再扫描了,扫到n-1-i就行了。
代码:
class Solution {
public int findKthLargest(int[] nums, int k) {
int n = nums.length;
// 扫描趟数
for(int i = 0; i < n - 1; i++) {
// 比较次数
for(int j = 0; j < n - 1 - i; j++) {
if(nums[j] < nums[j + 1]) {
int t = nums[j];
nums[j] = nums[j + 1];
nums[j + 1] = t;
}
}
}
return nums[k - 1];
}
}
2 选择排序
时间复杂度O(n^2)
思路:
每次选择出当前位置之后的最大或最小的数和当前位置做交换。
外层循环表示扫描的趟数,每扫描一趟就有一个数到应该的位置,n个数所以扫描n趟,但当n-1个数都到正确的位置时,最后一个数也一定是在正确的位置,所以扫描n-1趟就行了。
内层循环表示从第i + 1个数到结尾,把最大(最小)的数和第i个位置的数做交换。
代码:
class Solution {
public int findKthLargest(int[] nums, int k) {
int n = nums.length;
//扫描趟数
for(int i = 0; i < n - 1; i++) {
// 记录最大的数的下标
int max = i;
for(int j = i + 1; j < n; j++) {
if(nums[j] > nums[max]) {
max = j;
}
}
// 交换,放到对应的位置
int t = nums[max];
nums[max] = nums[i];
nums[i] = t;
}
return nums[k - 1];
}
}
3 插入排序
时间复杂度O(n^2)
思路:
把数组分为两部分,前半部分是排好的序列,依次把后面的数插入到前面序列正确的位置。
第0个元素看作排好的序列,所以从第1个元素开始确定位置。
j表示插入的位置,从当前位置开始向前遍历,如果要确认的元素比插入位置的前一个数大(小)就说明这个位置不是插入的位置,把前一个位置处的元素后移。
最后把i元素放到j插入位置处。
代码:
class Solution {
public int findKthLargest(int[] nums, int k) {
int n = nums.length;
// 第0个数是有序的,从第1个数开始确定
for(int i = 1; i < n; i++) {
int cur = nums[i];
// j 表示插入的位置
int j = i;
// 当前数比插入的位置的前一个数大,就不符合,直到小于等于前一个数
while(j > 0 && cur > nums[j - 1]) {
nums[j] = nums[j - 1];
j--;
}
// 把插入位置赋值为当前元素
nums[j] = cur;
}
return nums[k - 1];
}
}
4 希尔排序
时间复杂度O(nlogn)
思路:
是插入排序的优化。
外层循环按照步长每次分组,内层循环在组内进行插入排序。
代码:
class Solution {
public int findKthLargest(int[] nums, int k) {
int n = nums.length;
int inter = 2;
// 步长进行分组
for(int step = n / inter; step >= 1; step /= inter) {
// 组内进行插入排序
for(int i = step; i < n; i++) {
int cur = nums[i];
int j = i;
while(j - step >= 0 && cur > nums[j - step]) {
nums[j] = nums[j - step];
j -= step;
}
nums[j] = cur;
}
}
return nums[k - 1];
}
}
5 归并排序
时间复杂度O(nlogn)
思路:
每次从中间分开,分别排序,之后合并在一起,递归执行,直到元素个数为1。
代码:
class Solution {
private int[] temp;
public int findKthLargest(int[] nums, int k) {
int n = nums.length;
temp = new int[n];
mergeSort(nums, 0, n - 1);
return nums[k - 1];
}
private void mergeSort(int[] nums, int l, int r) {
if(l >= r) return;
// 中间分开
int mid = l + (r - l) / 2;
// 排序左边
mergeSort(nums, l, mid);
// 排序右边
mergeSort(nums, mid + 1, r);
// 合并
int i = l, j = mid + 1, p = 0;
while(i <= mid && j <= r) {
if(nums[i] < nums[j]) {
temp[p++] = nums[j];
j++;
}
else {
temp[p++] = nums[i];
i++;
}
}
while(i <= mid) {
temp[p++] = nums[i++];
}
while(j <= r) {
temp[p++] = nums[j++];
}
// 赋值回去
for(int k = 0; k < p; k++) {
nums[l + k] = temp[k];
}
}
}
6 快速排序
时间复杂度O(nlogn)
思路:
每次随机选一个数作为标准位,这里选择左边界的数,左指针找到比标准位小(大)的数,右指针找到比标准位大(小)的数,两者交换,最终把数组分为两部分,左半部分都是比标准位大(小)的,右半部分都是比标准位小(大)的。继续递归两部分。
代码:
class Solution {
public int findKthLargest(int[] nums, int k) {
int n = nums.length;
quickSort(nums, 0, n - 1);
return nums[k - 1];
}
private void quickSort(int[] nums, int l, int r) {
if(l >= r) return;
// x 为标准位
int x = nums[l], i = l - 1, j = r + 1;
while(i < j) {
// 找到比标准位小的
do i++; while(nums[i] > x);
// 找到比标准位大的
do j--; while(nums[j] < x);
if(i < j) {
int t = nums[i];
nums[i] = nums[j];
nums[j] = t;
}
}
// 排序左部分
quickSort(nums, l, j);
// 排序右部分
quickSort(nums, j + 1, r);
}
}
7 堆排序
时间复杂度O(nlogn)
思路:
使用堆数据结构,建立小根堆,从后向前依次确定位置,将后边的位置处的元素和根位置处的元素做交换,然后在指定范围内调整堆。
【从堆的定义到优先队列、堆排序】 10分钟看懂必考的数据结构——堆_哔哩哔哩_bilibili
代码:
class Solution {
public int findKthLargest(int[] nums, int k) {
int n = nums.length;
heapSort(nums);
return nums[k - 1];
}
private void heapSort(int[] nums) {
int n = nums.length;
build(nums);
// 每次把根元素放到最后,然后从0到排除刚刚放的结点做一个调整,即可得到有序序列
for(int i = n - 1; i > 0; i--) {
int t = nums[0];
nums[0] = nums[i];
nums[i] = t;
adjust(nums, 0, i);
}
}
private void build(int[] nums) {
int n = nums.length;
// 从最后一个非叶子结点开始,依次调整
for(int i = n/ 2 - 1; i >= 0; i--) {
adjust(nums, i, n);
}
}
private void adjust(int[] nums, int root, int n) {
// 左结点
int l = 2*root + 1;
// 右结点
int r = 2*root + 2;
// 记录最小的位置
int min = root;
if(l < n && nums[l] < nums[min]) {
min = l;
}
if(r < n && nums[r] < nums[min]) {
min = r;
}
if(min != root) {
int t = nums[min];
nums[min] = nums[root];
nums[root] = t;
// 继续递归调整交换后的位置
adjust(nums, min, n);
}
}
}
8 计数排序
时间复杂度O(n + k),k和存储的元素的最大值和最小值有关
思路:
先统计出数组的最大值和最小值,然后将数组范围的数组映射到新的数组中,统计每一个数字出现的次数,最后按照顺序覆盖原数组即可。
代码:
class Solution {
public int findKthLargest(int[] nums, int k) {
int n = nums.length;
int max = nums[0];
int min = nums[0];
// 找到最大值和最小值
for(int i = 0; i < n; i++) {
if(nums[i] > max) {
max = nums[i];
}
if(nums[i] < min) {
min = nums[i];
}
}
int[] bucket = new int[max - min + 1];
// 统计每一个数字的个数
for(int i = 0; i < n; i++) {
bucket[nums[i] - min]++;
}
int p = 0;
// 依次覆盖
for(int i = bucket.length - 1; i >= 0; i--) {
int cnt = bucket[i];
if(cnt != 0) {
for(int j = 0; j < cnt; j++) {
nums[p++] = i + min;
}
}
}
return nums[k - 1];
}
}
9 桶排序
时间复杂度O(n + k),k和桶选择的排序方法有关。
思路:
将数组中的数字根据范围放到不同的桶中,然后遍历每一个桶,采用排序算法对桶中数字进行排序,并覆盖原数组。
代码:
class Solution {
public int findKthLargest(int[] nums, int k) {
int n = nums.length;
int max = nums[0];
int min = nums[0];
// 找到最大值和最小值
for(int i = 0; i < n; i++) {
if(nums[i] > max) {
max = nums[i];
}
if(nums[i] < min) {
min = nums[i];
}
}
// 分组:元素个数/桶大小 向上取整,不足1的视为1
List[] bucket = new List[(max - min + 1 + 9)/10];
Arrays.setAll(bucket, a -> new ArrayList<>());
// 数字放入桶中
for(int i = 0; i < n; i++) {
bucket[(nums[i] - min)/10].add(nums[i]);
}
int p = 0;
for(int i = bucket.length - 1; i >= 0; i--) {
if(bucket[i].size() != 0) {
List<Integer> t = bucket[i];
// 桶中元素排序
quick_sort(t, 0, t.size() - 1);
// 覆盖原数组
for(int j = 0; j < t.size(); j++) {
nums[p++] = t.get(j);
}
}
}
return nums[k - 1];
}
// 快速排序
private void quick_sort(List<Integer> t, int l, int r) {
if(l >= r) return;
int x = t.get(l), i = l - 1, j = r + 1;
while(i < j) {
do i++; while(t.get(i) > x);
do j--; while(t.get(j) < x);
if(i < j) {
int temp = t.get(i);
t.set(i, t.get(j));
t.set(j, temp);
}
}
quick_sort(t, l, j);
quick_sort(t, j + 1, r);
}
}
10 基数排序
时间复杂度O(n*k),k和最大数字的长度有关
思路:
先求出最大值的位数作为最大位数。然后创建一个桶,依次根据每一个数字的个位、十位、百位...放入桶中,桶的0-9装负数,11-19装正数。
从个位开始遍历每一个位数,遍历每一个数字,取出对应的位数放入桶中,然后遍历每一个桶,覆盖原有的数组。循环执行,直到遍历完所有的位数。因为根据位数放入桶再放回数组,保证了低位的有序性,然后在处理高位的时候,低位自然就是有序的。
代码:
class Solution {
public int findKthLargest(int[] nums, int k) {
int n = nums.length;
int max = nums[0], maxDigit = 0;
// 求出最长的位数
for(int i = 0; i < n; i++) {
if(nums[i] > max) {
max = nums[i];
}
}
while(max != 0) {
max = max / 10;
maxDigit++;
}
int dev = 1, mod = 10;
for(int i = 0; i < maxDigit; i++, dev *= 10) {
// 创建桶
List[] count = new List[20];
Arrays.setAll(count, a -> new ArrayList<>());
// 把对应的位数放入桶中
for(int j = 0; j < n; j++) {
List<Integer> bucket = count[(nums[j]/dev)%mod + 10];
bucket.add(nums[j]);
}
int p = 0;
// 遍历每一个桶,覆盖原数组
for(int j = 19; j >= 0; j--) {
List<Integer> bucket = count[j];
if(bucket.size() != 0) {
for(int num : bucket) {
nums[p++] = num;
}
}
}
}
return nums[k - 1];
}
}
参考
1.0 十大经典排序算法 | 菜鸟教程 (runoob.com)