zoukankan      html  css  js  c++  java
  • 微软面试题: LeetCode 215. 数组中的第K个最大元素 出现次数:6

    LeetCode 215.  数组中的第K个最大元素

    描述:在未排序的数组中找到第 k 个最大的元素。请注意,你需要找的是数组排序后的第 k 个

    最大的元素,而不是第 k 个不同的元素。

    方法 0 : 直接 std::sort 排序  时间O(n*logn)    空间O(1)

         求 数组中的第K个最大元素 直观解法就是,先将数组 nums 排好序,然后取nums[n - k]即可。

     int findKthLargest(vector<int>& nums, int k) {
            std::sort(nums.begin(),nums.end());
            return nums[nums.size()-k];
        }

    分析:时间复杂度就是 std::sort 的时间复杂度  O(n*logn)。

    方法 1 :快速选择算法     时间O(n)    空间O(1) 

         求解 k-th Element 问题 通常还可以使用快速选择算法,即快速排序的 “partition 算法思想” 。

     1 class Solution {
     2 public:
     3 //time:O(n*logn)  space:O(1);
     4     // int findKthLargest(vector<int>& nums, int k) {
     5     //     std::sort(nums.begin(),nums.end());
     6     //     return nums[nums.size()-k];
     7     // }
     8     
     9     int findKthLargest(vector<int>& nums, int k) 
    10     {
    11         random_shuffle(nums.begin(), nums.end());//洗牌算法
    12         int l = 0,r = nums.size() - 1;
    13         int target_index = nums.size() - k;
    14         while(l <= r)
    15         {
    16             int mid = quickSelcetion(nums,l,r);
    17             if(mid == target_index)
    18             {
    19                 return nums[mid];
    20             }
    21             else if(mid < target_index)
    22             {
    23                 l = mid + 1;
    24             }
    25             else
    26             {
    27                 r = mid - 1;
    28             }
    29         }
    30         return INT_MAX; 
    31     }
    32 //快速排序的partition思想
    33 //以nums[l] 为 pivot ,把 nums[l] 放在它最终的位置上
    34 //快速排序就是执行一次partition划分,确定一个元素的最终位置,
    35 //直到最后所有的元素到放到最终的位置,排序完成。
    36     int quickSelcetion(vector<int>& nums,int l,int r)
    37     {
    38         int i = l + 1, j = r;
    39         while(true)
    40         {   
    41             //i 没有和j 交错 且 遇到的 nums[i] 都不大于 pivot,
    42             //就一直往右走,走过的路径上的所有元素以后都会放在 pivot的左边
    43             while(i <= j && nums[i] <= nums[l])
    44             {
    45                 ++i;
    46             }
    47             //j走过的路径上的所有元素以后都会放在pivot的右边
    48             while(j >= i && nums[j] >= nums[l])
    49             {
    50                 --j;
    51             }
    52             //i和j交错了,直接跳出外层循环
    53             if(j < i)
    54             {
    55                 break;
    56             }
    57             std::swap(nums[i],nums[j]);
    58         }
    59         //此时nums [j+1,r]一定都 >= nums[l],
    60         std::swap(nums[l],nums[j]);
    61         return j;
    62         //或者
    63         // std::swap(nums[l],nums[i - 1]);
    64         // return i - 1;
    65     }
    66 };

    分析:在 方法 一  中使用了 c++ STL 中提供的std::sort ,std::sort 是个复杂同时也非常高效的排序算法,

      主要使用了 快速排序的思想,整体性能很高。快速排序的逻辑是:

    若要对nums[lo..hi]进行排序,我们先找一个分界点p,通过交换元素使得nums[lo..p-1]都小于等于nums[p]

    nums[p+1..hi]都大于nums[p],然后递归地去nums[lo..p-1]nums[p+1..hi]中寻找新的分界点,

    最后整个数组就被排序了。

      但是本题只需要知道 在 nums 排序好后,在target_index =  nums.size() - k 的位置上的元素。

    不需要再继续对区间[0,target_index-1]和区间 [target_index+1,nums.size()-1 ]排序了。所以只需要借助 快速排序的 partition 划分思想,

    partition 的算法框架需要记住!!!

    疑问 :

          1.   为什么时间 是 O(n)?如何分析证明?

          答: O(N)的时间复杂度是个均摊复杂度,需要在算法开始的时候对nums数组来一次随机打乱。上面代码加了

    random_shuffle(nums.begin(), nums.end());  直接从12% 提升到 99%。

     

          2.  为什么在上面的代码中 返回 j 或 i - 1,而不能返回 i ?

          答:细节问题。下面代码代码参考《算法4》,是众多写法中最漂亮简洁的一种。可以先背下 partition框架。

    再慢慢体会。

           

     1 int partition(int[] nums, int lo, int hi) {
     2     if (lo == hi) return lo;
     3     // 将 nums[lo] 作为默认分界点 pivot
     4     int pivot = nums[lo];
     5     // j = hi + 1 因为 while 中会先执行 --
     6     int i = lo, j = hi + 1;
     7     while (true) {
     8         // 保证 nums[lo..i] 都小于 pivot
     9         while (nums[++i] < pivot) {
    10             if (i == hi) break;
    11         }
    12         // 保证 nums[j..hi] 都大于 pivot
    13         while (nums[--j] > pivot) {
    14             if (j == lo) break;
    15         }
    16         if (i >= j) break;
    17         // 如果走到这里,一定有:
    18         // nums[i] > pivot && nums[j] < pivot
    19         // 所以需要交换 nums[i] 和 nums[j],
    20         // 保证 nums[lo..i] < pivot < nums[j..hi]
    21         swap(nums, i, j);
    22     }
    23     // 将 pivot 值交换到正确的位置
    24     swap(nums, j, lo);
    25     // 现在 nums[lo..j-1] < nums[j] < nums[j+1..hi]
    26     return j;
    27 }
    28 
    29 // 交换数组中的两个元素
    30 void swap(int[] nums, int i, int j) {
    31     int temp = nums[i];
    32     nums[i] = nums[j];
    33     nums[j] = temp;
    34 }

    方法 2:遍历nums,维护一个大小为 k 的 小顶堆  时间O(n* logK)     空间O(k) 

    小顶堆可以使用 c++ STL 中的   priority_queue

      priority_queue:最大值先出的数据结构,默认基于vector实现堆结构。它可以在O(n log n)
    的时间排序数组,O(log n) 的时间插入任意值,O(1) 的时间获得最大值,O(log n) 的时
    间删除最大值。priority_queue 常用于维护数据结构并快速获取最大或最小值。

           维护1 个大小为 k 的小顶堆,堆顶就是堆中的最小元素,遍历nums,要是堆内的元素个数 小于

    k,直接将 nums[i]   元素 push 到堆中。

           要是堆的大小已经是 k 了,将堆顶元素和 nums[i] 比较,要是nums[i] <= 堆顶元素,则nums[i] 

    肯定不在 nums 的前 k 个最大元素中,不用考虑,直接跳过。否则才将堆中元素pop 掉一个,再插入nums[i] 到堆中。

        代码如下:

          int findKthLargest(vector<int>& nums, int k) 
     2     {
     3        //基于vector 的 小顶堆,默认是大顶堆的 ,需加上greater<int> 
               std::priority_queue<int,vector<int>,greater<int>> pq;
     4         for(auto num : nums)
     5         {
     6             // 不在前k个最大元素内的,直接跳过
                    if(pq.size() == k && pq.top() >= num ) continue;
     7             if(pq.size() == k)
     8             {
     9                 pq.pop();
    10             }
    11             pq.push(num);
    12         }
    13         return pq.top();
    14     }
    15             

           

  • 相关阅读:
    以“处理器”为中心的时代过去了
    新宿事件里的一句话
    2
    了解企业要招的,再去学相应的东西
    maxim
    ORA00980 无效同名
    Oracle 字符集的查看和修改
    Linux挂载磁盘
    ORA28002: 7 天之后口令将过期
    ORAdump函数
  • 原文地址:https://www.cnblogs.com/wangxf2019/p/13965330.html
Copyright © 2011-2022 走看看