密斯特七少 發表於 2025-6-5 09:00:00

算法题:数组中的第k个最大元素

<p>力扣链接</p>
<h2 id="题意">题意</h2>
<p>给定整数数组&nbsp;<code>nums</code>&nbsp;和整数&nbsp;<code>k</code>,请返回数组中第&nbsp;<code>k</code> 个最大的元素。</p>
<p>请注意,你需要找的是数组排序后的第&nbsp;<code>k</code>&nbsp;个最大的元素,而不是第&nbsp;<code>k</code>&nbsp;个不同的元素。</p>
<p>你必须设计并实现时间复杂度为&nbsp;<code>O(n)</code>&nbsp;的算法解决此问题。</p>
<p><strong>示例 1:</strong></p>
<p><strong>输入:</strong> <code>,</code> k = 2<br>
<strong>输出:</strong> 5</p>
<p><strong>示例&nbsp;2:</strong></p>
<p><strong>输入:</strong> <code>,</code> k = 4<br>
<strong>输出:</strong> 4</p>
<p><strong>提示:</strong></p>
<ul>
<li><code>1 &lt;= k &lt;= nums.length &lt;= 105</code></li>
<li><code>-104&nbsp;&lt;= nums &lt;= 104</code></li>
</ul>
<h2 id="思路及解答">思路及解答</h2>
<h3 id="堆排序">堆排序</h3>
<p>这道题就是经典的 <strong><code>topK</code></strong> 问题, <strong>实现一个小根堆</strong>,规定堆的元素大小是 <strong><code>k</code></strong> 个,将比堆顶大的元素进堆。最后遍历完数组之后,此时堆顶是 <strong><code>k</code></strong> 个元素中最小的,有就是所有元素中第 <strong><code>k</code></strong> 大的了。</p>
<p>可以用PriorityQueue的最小堆来实现:</p>
<pre><code class="language-java">public class KthLargestMinHeap {
    public int findKthLargest(int[] nums, int k) {
      PriorityQueue&lt;Integer&gt; minHeap = new PriorityQueue&lt;&gt;(k);
      
      for (int num : nums) {
            if (minHeap.size() &lt; k) {
                minHeap.offer(num);
            } else if (num &gt; minHeap.peek()) {
                minHeap.poll();
                minHeap.offer(num);
            }
      }
      return minHeap.peek();
    }
}
</code></pre>
<h3 id="升序排序后索引为-len---k-的元素">升序排序后索引为 len - k 的元素</h3>
<p>其实题目已经告诉我们了:</p>
<blockquote>
<p>你需要找的是数组排序后的第 k 个最大的元素,而不是第 k 个不同的元素。</p>
</blockquote>
<p>因此,升序排序以后,返回索引为 len - k 这个元素即可。</p>
<p>代码如下:</p>
<pre><code class="language-java">public&nbsp;class&nbsp;Solution&nbsp;{

&nbsp;&nbsp;&nbsp;&nbsp;public&nbsp;int&nbsp;findKthLargest(int[]&nbsp;nums,&nbsp;int&nbsp;k)&nbsp;{
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;int&nbsp;len&nbsp;=&nbsp;nums.length;
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;Arrays.sort(nums);
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;return&nbsp;nums;
&nbsp;&nbsp;&nbsp;&nbsp;}
}
</code></pre>
<ul>
<li>时间复杂度:<em>O(NlogN)</em>。这里 N 是数组的长度,算法的性能消耗主要在排序,JDK 默认使用快速排序,因此时间复杂度为O(NlogN)。</li>
<li>空间复杂度:O(1)。这里是原地排序,没有借助额外的辅助空间。</li>
</ul>
<p>但是,如果出了这道题,显然不是想让你调 API ,而是看你对快排的理解</p>
<h3 id="借助快排-partition-操作定位推荐">借助快排 partition 操作定位(推荐)</h3>
<p>“快速排序” 中的 partition(切分)操作简单介绍如下:</p>
<ul>
<li>对于某个索引i,nums 已经排序完,即 nums 经过 partition(切分)操作以后会放置在它 “最终应该放置的地方”;</li>
<li>nums 到 nums 中的所有元素都不大于 nums;</li>
<li>nums 到 nums 中的所有元素都不小于 nums。</li>
</ul>
<p>很显然,nums 最终所在的位置,也就是它排序后的具体位置。也就是说,如果实现的排序是降序排序,那么第k个位置的数据,也确定就是第k大的</p>
<p>而这样每经过一次 partition操作就能缩小搜索的范围,这样的思想叫做 “减而治之”(是 “分而治之” 思想的特例)。下面是参考代码:</p>
<pre><code class="language-javascript">public&nbsp;class&nbsp;Solution&nbsp;{

&nbsp;&nbsp;&nbsp;&nbsp;public&nbsp;int&nbsp;findKthLargest(int[]&nbsp;nums,&nbsp;int&nbsp;k)&nbsp;{
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;int&nbsp;len&nbsp;=&nbsp;nums.length;
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;int&nbsp;left&nbsp;=&nbsp;0;
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;int&nbsp;right&nbsp;=&nbsp;len&nbsp;-&nbsp;1;

&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;//&nbsp;转换一下,第&nbsp;k&nbsp;大元素的索引是&nbsp;len&nbsp;-&nbsp;k
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;int&nbsp;target&nbsp;=&nbsp;len&nbsp;-&nbsp;k;

&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;while&nbsp;(true)&nbsp;{
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;int&nbsp;index&nbsp;=&nbsp;partition(nums,&nbsp;left,&nbsp;right);
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;if&nbsp;(index&nbsp;==&nbsp;target)&nbsp;{
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;return&nbsp;nums;
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;}&nbsp;else&nbsp;if&nbsp;(index&nbsp;&lt;&nbsp;target)&nbsp;{
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;left&nbsp;=&nbsp;index&nbsp;+&nbsp;1;
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;}&nbsp;else&nbsp;{
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;right&nbsp;=&nbsp;index&nbsp;-&nbsp;1;
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;}
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;}
&nbsp;&nbsp;&nbsp;&nbsp;}

        public static int partition(int[] array, int low, int high) {
          // 取最后一个元素作为中心元素
          int pivot = array;
          // 定义指向比中心元素大的指针,首先指向第一个元素
          int pointer = low;
          // 遍历数组中的所有元素,将比中心元素大的放在右边,比中心元素小的放在左边
          for (int i = low; i &lt; high; i++) {
                if (array &lt;= pivot) {
                        // 将比中心元素小的元素和指针指向的元素交换位置
                        // 如果第一个元素比中心元素小,这里就是自己和自己交换位置,指针和索引都向下一位移动
                        // 如果元素比中心元素大,索引向下移动,指针指向这个较大的元素,直到找到比中心元素小的元素,并交换位置,指针向下移动
                        swap(array, i, pointer);
                    pointer++;
                }
          }
          // 将中心元素和指针指向的元素交换位置
          swap(array, pointer, high);
          return pointer;
        }
       
        private static void swap(int[] arr, int i, int j) {
                int temp = arr;
                arr = arr;
                arr = temp;
        }
}
</code></pre>
<ul>
<li>时间复杂度:O(N)。这里 N 是数组的长度。</li>
<li>空间复杂度:O(1)。切分过程可以不借助额外的数组空间,仅通过交换数组元素实现,没有借助额外的辅助空间。</li>
</ul>


</div>
<div id="MySignature" role="contentinfo">
    <p>本文来自在线网站:seven的菜鸟成长之路,作者:seven,转载请注明原文链接:www.seven97.top</p><br><br>
来源:https://www.cnblogs.com/sevencoding/p/18905510
頁: [1]
查看完整版本: 算法题:数组中的第k个最大元素