这篇文章是看了“左程云”老师在b站上的讲解之后写的, 自己感觉已经能理解了, 所以就将整个过程写下来了。
这个是“左程云”老师个人空间的b站的链接, 数据结构与算法讲的很好很好, 希望大家可以多多支持左程云老师, 真心推荐.
https://space.bilibili.com/8888480?spm_id_from=333.999.0.0
1. 题目的解释
举一个例子:有一个数组:arr[100]
, 所以找这个 arr数组
中第 1
大的数字(或者是第 1
小的数字), 不就是找 arr数组
中第 100
小的数字(第 100
大的) 吗?
时间复杂度的要求是:O(n)
, 所以首先排除了先排序后找的方法, 因为无论怎么进行排序 (只要是基于比较的排序), 时间复杂度最少最少也是:O(n * log(n))
.
2. 逻辑实现
借用随机快速排序进行实现的.
假设, 此时我要找一个第 52大
的数字, 假设随机出来的数字是:34
, 一个数组: arr[100]
, 然后进行随机快速排序的过程, arr[0.........99]
, 假设 34
这个数字在这个数组中有很多个, 此时有三种情况:
- 排序好的位置是:
49 ~ 66
, 所以第52
大的数字肯定是34
. - 排好序的位置是:
25 ~ 32
, 所以第52
大的数字肯定在32位置
右边. 此时在这个位置上继续选择一个数字, 然后继续重复随机过程. - 排好序的位置是:
77 ~ 89
, 所以第52
大的数字肯定在77位置
左边. 此时在这个位置上继续选择一个数字, 然后继续重复随机过程.
这个过程将随机快速排序的划分过程高度利用了. 而且这个过程只看随机选择的数字有没有命中对应的位置. 若是没有命中, 那我经过随机快速的划分过程已经实现了划分, 所以只用走一侧就行了(左边右边选一个).
3. 时间复杂度分析
3 .1 最差情况
还是原来的数组 arr[100]
, 若是我想要的是 0
位置的数字, 但是恰好随机选择的数字是:99
位置的数字, 那么我要从 0 ~ 98
范围上进行选择, 然后我又运气不好, 选择了 98
位置的数字, 那么我要从 0 ~ 98
范围上进行选择, 以此类推, 直到最后我才选择到 0
位置的数字,
所以最差情况的时间复杂度是:O(n^2)
.
3.2 最好情况
最好情况就是我想要选择的数字是在中间位置的数字,
- 可能一开始经过随机选择过程我直接选择到了中间位置的数字, 直接返回,
- 要是没有返回, 继续在左右两侧选择一侧随机选择一个数字, 若是命中就返回, 没有命中继续选择一侧随机选择数字.
- 假设上述行为每一次都实现到
所以最开始过程是 N
, 第二次过程是 N/2
, 第三次过程是:N/4
, 所以这个过程加起来 N + N/2 + N/4 + ...
, 这个过程是一个等比数列, 最后肯定是收敛于 O(n)
.
3.3 总结
所以分析时间复杂度的方式还是根据期望进行求解, 最后求解出来的时间复杂度是:O(n)
. 只需要记住就行了, 不用去证明.
4. 代码实例
代码实例和逻辑实现的情况是一样的, 自己看肯定能看懂, 也没有什么需要注意的部分, 最后直接看就行了. 该写的注释也都写了.
// 随机选择算法,时间复杂度O(n)
public static int findKthLargest(int[] nums, int k) {
return randomizedSelect(nums, nums.length - k);
}
// 如果arr排序的话,在i位置的数字是什么
public static int randomizedSelect(int[] arr, int i) {
int ans = 0;
for (int l = 0, r = arr.length - 1; l <= r;) {
// 随机这一下,常数时间比较大
// 但只有这一下随机,才能在概率上把时间复杂度收敛到O(n)
partition(arr, l, r, arr[l + (int) (Math.random() * (r - l + 1))]);
// 因为左右两侧只需要走一侧
// 所以不需要临时变量记录全局的first、last
// 直接用即可
if (i < first) {
r = first - 1;
} else if (i > last) {
l = last + 1;
} else {
ans = arr[i];
break;
}
}
return ans;
}
// 荷兰国旗问题
public static int first, last;
public static void partition(int[] arr, int l, int r, int x) {
first = l;
last = r;
int i = l;
while (i <= last) {
if (arr[i] == x) {
i++;
} else if (arr[i] < x) {
swap(arr, first++, i++);
} else {
swap(arr, i, last--);
}
}
}
public static void swap(int[] arr, int i, int j) {
int tmp = arr[i];
arr[i] = arr[j];
arr[j] = tmp;
}