给定整数数组 nums
和整数 k
,请返回数组中第 k
个最大的元素。
请注意,你需要找的是数组排序后的第 k
个最大的元素,而不是第 k
个不同的元素。
你必须设计并实现时间复杂度为 O(n)
的算法解决此问题。
解法一:(快速排序)
- 最优时间复杂度:
O(nlogn)
- 最坏时间复杂度:
O(n^2)
- 稳定性:不稳定
- 改进,每次只排一个的位置
class Solution {
Random random=new Random();
public int findKthLargest(int[] nums, int k) {
return quickSelect(nums,k,0,nums.length-1);
}
public int quickSelect(int[] nums,int k,int left,int right){
//随机获取一个值作为目标值
int index=random.nextInt(right-left+1)+left;//0,1(左闭右开)+0
//保存目标值
int target=nums[index];
//在进行第一次交换时,会丢失nums[left],因此需要保存
nums[index]=nums[left];
int i=left,j=right;
while(i<j){
while(i<j&&nums[j]<=target) j--;
nums[i]=nums[j];
while(i<j&&nums[i]>=target) i++;
nums[j]=nums[i];
}
nums[i]=target;
//经过交换后,i左部均大于nums[i],右部均小于nums[i]
if(i==k-1) return nums[i];
//说明在左部分
else if(i>k-1) return quickSelect(nums,k,left,i-1);//i>k-1表示基准数小了
else return quickSelect(nums,k,i+1,right);
}
}
(1)random类
在 Java中要生成一个指定范围之内的随机数字有两种方法:一种是调用 Math 类的 random() 方法,一种是使用 Random 类。
Random 类
提供了丰富的随机数生成方法,可以产生 boolean、int、long、float、byte 数组以及 double 类型的随机数,这是它与 random() 方法最大的不同之处。
import java.util.Random;
public class Test06 {
public static void main(String[] args) {
Random r = new Random();
double d1 = r.nextDouble(); // 生成[0,1.0]区间的小数
double d2 = r.nextDouble() * 7; // 生成[0,7.0]区间的小数
int i1 = r.nextInt(10); // 生成[0,10]区间的整数
int i2 = r.nextInt(18) - 3; // 生成[-3,15)区间的整数
long l1 = r.nextLong(); // 生成一个随机长整型值
boolean b1 = r.nextBoolean(); // 生成一个随机布尔型值
float f1 = r.nextFloat(); // 生成一个随机浮点型值
System.out.println("生成的[0,1.0]区间的小数是:" + d1);
System.out.println("生成的[0,7.0]区间的小数是:" + d2);
System.out.println("生成的[0,10]区间的整数是:" + i1);
System.out.println("生成的[-3,15]区间的整数是:" + i2);
System.out.println("生成一个随机长整型值:" + l1);
System.out.println("生成一个随机布尔型值:" + b1);
System.out.println("生成一个随机浮点型值:" + f1);
System.out.print("下期七星彩开奖号码预测:");
for (int i = 1; i < 8; i++) {
int num = r.nextInt(9); // 生成[0,9]区间的整数
System.out.print(num);
}
}
}
random()
只能产生 double 类型的 0~1 的随机数。
Math 类的 random() 方法没有参数,它默认会返回大于等于 0.0、小于 1.0 的 double 类型随机数,即 0<=随机数<1.0。对 random() 方法返回的数字稍加处理,即可实现产生任意范围随机数的功能。
public class Test07 {
public static void main(String[] args) {
int min = 2; // 定义随机数的最小值
int max = 102; // 定义随机数的最大值
// 产生一个2~100的数
int s = (int) min + (int) (Math.random() * (max - min));
if (s % 2 == 0) {
// 如果是偶数就输出
System.out.println("随机数是:" + s);
} else {
// 如果是奇数就加1后输出
System.out.println("随机数是:" + (s + 1));
}
}
}
由于 m+(int)(Math.random()*n) 语句可以获取 m~m+n 的随机数,所以 2+(int)(Math. random()*(102-2)) 表达式可以求出 2~100 的随机数。在产生这个区间的随机数后还需要判断是否为偶数,这里使用了对 2 取余数,如果余数不是零,说明随机数是奇数,此时将随机数加 1 后再输出。
Random 类位于 java.util 包中,该类常用的有如下两个构造方法。
- Random():该构造方法使用一个和当前系统时间对应的数字作为种子数,然后使用这个种子数构造 Random 对象。
- Random(long seed):使用单个 long 类型的参数创建一个新的随机数生成器
- Random 类提供的所有方法生成的随机数字都是均匀分布的,也就是说区间内部的数字生成的概率是均等的,在表 1 中列出了 Random 类中常用的方法。
-
Random random=new Random();
简单的:
class Solution {
public int findKthLargest(int[] nums, int k) {
Arrays.sort(nums);
int n = nums.length;
return nums[n-k];
}
}
解法二:(堆排序)
- 最优时间复杂度:
o(nlogn)
- 最坏时间复杂度:
o(nlogn)
- 稳定性:不稳定
建立一个大根堆,做k−1 次删除操作后堆顶元素就是我们要找的答案。在很多语言中,都有优先队列或者堆的的容器可以直接使用,但是在面试中,面试官更倾向于让更面试者自己实现一个堆。所以建议读者掌握这里大根堆的实现方法,在这道题中尤其要搞懂「建堆」、「调整」和「删除」的过程。
class Solution {
public int findKthLargest(int[] nums, int k) {
int heapSize = nums.length;
buildMaxHeap(nums, heapSize);
//建堆完毕后,nums【0】为最大元素。逐个删除堆顶元素,直到删除了k-1个。
for (int i = nums.length - 1; i >= nums.length - k + 1; --i) {
//先将堆的最后一个元素与堆顶元素交换,由于此时堆的性质被破坏,需对此时的根节点进行向下调整操作。
swap(nums, 0, i);
//相当于删除堆顶元素,此时长度变为nums.length-2。即下次循环的i
--heapSize;
maxHeapify(nums, 0, heapSize);
}
return nums[0];
}
public void buildMaxHeap(int[] a, int heapSize) {
//从最后一个父节点位置开始调整每一个节点的子树。数组长度为heasize,因此最后一个节点的位置为heapsize-1,所以父节点的位置为heapsize-1-1/2。
for (int i = (heapSize-2)/ 2; i >= 0; --i) {
maxHeapify(a, i, heapSize);
}
}
public void maxHeapify(int[] a, int i, int heapSize) { //调整当前结点和子节点的顺序。
//left和right表示当前父节点i的两个左右子节点。
int left = i * 2 + 1, right = i * 2 + 2, largest = i;
//如果左子点在数组内,且比当前父节点大,则将最大值的指针指向左子点。
if (left < heapSize && a[left] > a[largest]) {
largest = left;
}
//如果右子点在数组内,且比当前父节点大,则将最大值的指针指向右子点。
if (right < heapSize && a[right] > a[largest]) {
largest = right;
}
//只用largest标记最大的数
//如果最大值的指针不是父节点,则交换父节点和当前最大值指针指向的子节点。
if (largest != i) {
swap(a, i, largest);
//由于交换了父节点和子节点,因此可能对子节点的子树造成影响,所以对子节点的子树进行调整。
maxHeapify(a, largest, heapSize);
}
}
public void swap(int[] a, int i, int j) {
int temp = a[i];
a[i] = a[j];
a[j] = temp;
}
}
小根堆:
class Solution {
public:
int findKthLargest(vector<int>& nums, int k) {
// 对前k个元素建成小根堆
for (int i = 0; i < k; i++) {
swim(nums, i);
}
// 剩下的元素与堆顶比较,若大于堆顶则去掉堆顶,再将其插入
for (int i = k; i < nums.size(); i++) {
if (nums[i] > nums[0]) {
swap(nums[0], nums[i]);
sink(nums, 0, k - 1);
}
}
// 结束后第k个大的数就是小根堆的堆顶
return nums[0];
}
private:
// 若v1比v2优先度高,返回true
bool priorityThan(int v1, int v2) { return v1 < v2; }
// 上浮 从下到上调整堆
void swim(vector<int>& heap, int i) {
while (i > 0 && priorityThan(heap[i], heap[(i - 1) / 2])) {
swap(heap[i], heap[(i - 1) / 2]);
i = (i - 1) / 2;
}
}
// 下沉 从下到上调整堆
void sink(vector<int>& heap, int i, int N) {
while (2 * i + 1 <= N) {
int j = 2 * i + 1;
if (j + 1 <= N && priorityThan(heap[j + 1], heap[j])) {
j++;
}
if (priorityThan(heap[i], heap[j])) {
break;
}
swap(heap[i], heap[j]);
i = j;
}
}
};