leecode215

leecode215

给定整数数组 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;
        }
    }
};

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值