题目: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)

思路:

是插入排序的优化。

外层循环按照步长每次分组,内层循环在组内进行插入排序。

数据结构——三分钟搞定希尔排序_哔哩哔哩_bilibili

代码:

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 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 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 bucket = count[(nums[j]/dev)%mod + 10];

bucket.add(nums[j]);

}

int p = 0;

// 遍历每一个桶,覆盖原数组

for(int j = 19; j >= 0; j--) {

List bucket = count[j];

if(bucket.size() != 0) {

for(int num : bucket) {

nums[p++] = num;

}

}

}

}

return nums[k - 1];

}

}

参考

1.0 十大经典排序算法 | 菜鸟教程 (runoob.com)

215. 数组中的第K个最大元素 - 力扣(LeetCode)题解

数据结构——三分钟搞定希尔排序_哔哩哔哩_bilibili

【从堆的定义到优先队列、堆排序】 10分钟看懂必考的数据结构——堆_哔哩哔哩_bilibili