目录
[1. 入口方法:findKthLargest](#1. 入口方法:findKthLargest)
[2. 核心方法:randomizedSelect](#2. 核心方法:randomizedSelect)
[3. 划分方法:partition](#3. 划分方法:partition)
[4. 工具方法:swap](#4. 工具方法:swap)
1.前言
哈喽大家好吖,上次我们学了随机快速排序,并大致了解了其通过随机来使时间复杂度收敛到更小的程度,今天学习的随机选择算法是随机快速排序的延伸,话不多说让我们开始吧。
2.正文
先附上测试链接:
首先我们需要先明确一个问题,找一个无序数组中的第K个最大的元素,和在这个无序数组找第n-K个最小的元素,这俩个问题等价,这样我们就实现了问题的转化。
另外还需要注意一个问题,题目提醒找的不是第K个不同的元素,意味着会有重复,就可能会出现同一个数组第x个和第x+y个最大的元素是同一个数,清楚好这一点后,我们按照上一讲随机快速排序的思想对其实现,排序完后直接返回ans即可。下面每一段代码详细讲解:
1. 入口方法:findKthLargest
java
public static int findKthLargest(int[] nums, int k) {
return randomizedSelect(nums, nums.length - k);
}
- 功能 :找到数组中第
k
大的元素。- 转换 :
- 第
k
大相当于按升序排序后第nums.length - k
小的元素。- 比如数组
[3, 2, 1, 5, 6, 4]
,第 2 大元素是 5,按升序排序后是第 4 小。
2. 核心方法:randomizedSelect
java
public static int randomizedSelect(int[] arr, int i) {
int ans = 0;
for (int l = 0, r = arr.length - 1; l <= r;) {
partition(arr, l, r, arr[l + (int) (Math.random() * (r - l + 1))]);
if (i < first) {
r = first - 1;
} else if (i > last) {
l = last + 1;
} else {
ans = arr[i];
break;
}
}
return ans;
}
- 输入 :
arr
: 待查找的数组。i
: 目标索引,即排序后第i
小的元素。- 过程 :
- 定义搜索范围
l
和r
,初始为整个数组。- 在
[l, r]
范围内随机选择一个基准元素(arr[l + (int) (Math.random() * (r - l + 1))]
)。- 调用
partition
方法按照基准元素划分数组。- 判断目标索引
i
所在的部分:
- 如果
i < first
,说明目标在左部分,缩小范围到[l, first - 1]
。- 如果
i > last
,说明目标在右部分,缩小范围到[last + 1, r]
。- 否则,
i
在基准元素的范围[first, last]
内,直接返回结果。- 退出条件 :
- 找到目标索引
i
的元素,赋值给ans
,并跳出循环。
3. 划分方法:partition
java
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--);
}
}
}
- 输入 :
arr
: 当前数组。l
,r
: 当前区间范围。x
: 基准值。- 功能 :实现荷兰国旗划分,将数组分为三部分:
- 小于
x
的元素放左侧。- 等于
x
的元素居中。- 大于
x
的元素放右侧。- 过程 :
- 初始化两个指针
first
和last
,分别表示小于x
部分的右边界和大于x
部分的左边界。- 遍历数组:
- 如果
arr[i] == x
,说明是中间部分,跳过。- 如果
arr[i] < x
,交换到左侧,并更新first
和i
。- 如果
arr[i] > x
,交换到右侧,并更新last
。- 输出 :
- 通过修改全局变量
first
和last
,标记中间部分的范围。
4. 工具方法:swap
java
public static void swap(int[] arr, int i, int j) {
int tmp = arr[i];
arr[i] = arr[j];
arr[j] = tmp;
}
- 功能:交换数组中两个索引的元素。
最后汇总一下代码并梳理一下大体思路:
java
public static int findKthLargest(int[] nums, int k) {
return randomizedSelect(nums, nums.length - k);
}
public static int randomizedSelect(int[] arr, int i) {
int ans = 0;
for (int l = 0, r = arr.length - 1; l <= r;) {
partition(arr, l, r, arr[l + (int) (Math.random() * (r - l + 1))]);
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;
}
- 调用
findKthLargest
,将问题转化为查找第nums.length - k
小的元素。randomizedSelect
递归地在数组中划分并缩小范围:
- 随机选择一个基准元素,划分数组。
- 根据目标索引所在的部分,只递归需要的部分。
- 使用
partition
划分数组。
- 通过荷兰国旗问题,将数组划分为三部分。
- 返回结果
提交以下没有问题。
3.小结
今天的分享到这里就结束了,喜欢的小伙伴点点赞点点关注,你的支持就是对我最大的鼓励,加油!