215. 數組中的第K個最大元素(快速排序、堆排序)

根據這道題總結一下快速排序和堆排序,再根據這兩種方法寫這道題。

給定整數數組 nums 和整數 k,請返回數組中第 k 個最大的元素。

請注意,你需要找的是數組排序后的第 k 個最大的元素,而不是第 k 個不同的元素。

你必須設計并實現時間復雜度為 O(n) 的算法解決此問題。

示例 1:

輸入: [3,2,1,5,6,4], k = 2
輸出: 5

示例 2:

輸入: [3,2,3,1,2,4,5,5,6], k = 4
輸出: 4

提示:

  • 1 <= k <= nums.length <= 105
  • -104 <= nums[i] <= 104

我們首先給出快速排序的代碼,快速排序的思路是先選取一個基準值,然后把小于基準值的放到基準值左邊,把大于基準值的放到基準值右邊,這樣就會變成三部分(基準值左邊部分、基準值、基準值右邊部分),對基準值左右再遞歸進行這個步驟。代碼分三部分:快速排序輔助分區部分、排序部分和主函數,分區部分就是把比基準值小的放左邊,比基準值大的放右邊,然后把基準值放中間,排序部分就是遞歸排序。

#include <iostream>
#include <vector>
#include <utility> // for std::swap// 快速排序的輔助函數,進行分區
int partition(std::vector<int> &nums, int low, int high) {// 選擇最左側的元素作為基準值(pivot)int pivot = nums[low];int i = low + 1; // i指針用來記錄比基準值小的區域的最后一個元素的位置int j = high; // j指針用來記錄比基準值大的區域的第一個元素的位置// 循環進行分區操作while(true) {// 從左向右找,找到大于等于基準值的元素while (nums[i] < pivot) {i++;}// 從右向左找,找到小于等于基準值的元素while (nums[j] > pivot) {j--;}if (i < j) {std::swap(nums[i], nums[j]);} else {// 完成分區,左邊全是小于等于基準值,右邊全是大于等于基準值break;}}// 交換基準值到分區的中間std::swap(nums[low], nums[j]);// 返回基準值的最終位置return i;
}// 快速排序的遞歸函數
void quickSort(std::vector<int> &nums, int low, int high) {if (low < high) {// 分區操作int pivotIndex = partition(nums, low, high);// 對基準值左邊的子序列進行快速排序quickSort(nums, low, pivotIndex - 1);// 對基準值右邊的子序列進行快速排序quickSort(nums, pivotIndex + 1, high);}
}int main() {std::vector<int> nums = {10, 7, 8, 9, 1, 5};int n = nums.size();quickSort(nums, 0, n - 1);for (int num : nums) {std::cout << num << " ";}return 0;
}

運行結果(每一步分區的過程)為:

6 7 8 9 1 5 3 3 6 1 10 
6 1 3 3 1 5 6 9 8 7 10 
5 1 3 3 1 6 6 9 8 7 10 
1 1 3 3 5 6 6 9 8 7 10 
1 1 3 3 5 6 6 9 8 7 10 
1 1 3 3 5 6 6 9 8 7 10 
1 1 3 3 5 6 6 7 8 9 10 
1 1 3 3 5 6 6 7 8 9 10 
1 1 3 3 5 6 6 7 8 9 10 

快速排序的時間復雜度是O(nlogn)

基于快速排序可以寫出做這道題的快速選擇方法的代碼,與快速排序一樣,需要先分區,之后確定了基準值最終所在的位置,然后不需要進行排序操作,只需要知道第k大的元素是在基準值左邊還是右邊,然后在那個分區找就可以了,也是遞歸來查找,這個就是在快排的過程中直接找到了,所以不需要進行完整的快排,因此復雜度變低:

#include <iostream>
#include <vector>
#include <utility> // for std::swap// 快速排序的輔助函數,進行分區
int partition(std::vector<int> &nums, int low, int high) {// 選擇最左側的元素作為基準值(pivot)int pivot = nums[low];int i = low + 1; // i指針用來記錄比基準值小的區域的最后一個元素的位置int j = high; // j指針用來記錄比基準值大的區域的第一個元素的位置// 循環進行分區操作while(true) {// 從左向右找,找到大于等于基準值的元素while (nums[i] < pivot) {i++;}// 從右向左找,找到小于等于基準值的元素while (nums[j] > pivot) {j--;}if (i < j) {std::swap(nums[i], nums[j]);} else {// 完成分區,左邊全是小于等于基準值,右邊全是大于等于基準值break;}}// 交換基準值到分區的中間std::swap(nums[low], nums[j]);// 返回基準值的最終位置return j;
}// 快速排序的遞歸函數
int quickSelect(std::vector<int> &nums, int low, int high, int kIndex) {if (low == high) {// 當子數組只有一個元素時,返回該元素return nums[low];}int pivotIndex = partition(nums, low, high);if (kIndex <= pivotIndex) {// 第k大的元素索引在左側子數組中return quickSelect(nums, low, pivotIndex, kIndex);} else {// 第k大的元素索引在右側子數組中return quickSelect(nums, pivotIndex + 1, high, kIndex);}
}int main() {std::vector<int> nums = {10, 5, 3, 2, 1, 6, 8, 7};int n = nums.size();int k = 3;// 第k大的元素的索引是k-1int kIndex = k - 1;int ans = quickSelect(nums, 0, n - 1, n - 1 - kIndex);std::cout << "The ans is " << ans << std::endl;return 0;
}

注意,當求第k大的元素時,傳入的是索引k-1,當求第k小的元素(第n-k+1大)時,傳入索引n-k(即n-1-kIndex)。這個方法時間復雜度是O(n)

下面來總結一下堆排序和這道題,我們給出堆排序的代碼:

#include <iostream>
#include <vector>
#include <algorithm> // for std::swap// 自上向下調整堆,保證堆的性質
void heapify(std::vector<int> &nums, int n, int i) {int largest = i; // 初始時假設當前節點為最大值int left = 2 * i + 1;  // 左子節點int right = 2 * i + 2; // 右子節點// 如果左子節點存在且大于當前節點,更新最大值節點if (left < n && nums[left] > nums[largest]) {largest = left;}// 如果右子節點存在且大于當前節點,更新最大值節點if (right < n && nums[right] > nums[largest]) {largest = right;}// 如果最大值節點發生了變化,交換當前節點和最大值節點的值,并繼續調整if (largest != i) {std::swap(nums[i], nums[largest]);heapify(nums, n, largest);}
}// 堆排序
void heapSort(std::vector<int> &nums) {int n = nums.size();// 從最后一個非葉子節點開始建堆,即從 (n/2 - 1) 節點開始for (int i = n / 2 - 1; i >= 0; i--) {heapify(nums, n, i);}// 從最后一個元素開始,交換元素并進行調整堆操作for (int i = n - 1; i > 0; i--) {std::swap(nums[0], nums[i]); // 將當前堆的最大值放到數組末尾heapify(nums, i, 0); // 調整堆,新的堆大小為 i}
}int main() {std::vector<int> nums = {16, 10, 8, 7, 2, 3, 4, 1, 9, 14};heapSort(nums);std::cout << "Sorted array: ";for (int num : nums) {std::cout << num << " ";}return 0;
}

堆排序有三個重要部分:維護堆的性質,建堆,排序。以大根堆為例,這是一顆完全二叉樹,父節點的值大于子節點的值,下標為i的節點的父節點下標是(i - 1) / 2(整數除法),下標為i的節點的左孩子下標是i * 2 + 1,右孩子下標是i * 2 + 2,因此,假如有n個元素,那么堆的最后一個非葉子節點的下標是n / 2 - 1

  • 維護堆的性質,即為保證父節點值大于子節點值,從上而下調整,比如當前i節點不滿足這個性質,那么交換i節點和它的左右孩子中最大的那個,然后再判斷子節點那里是否滿足堆的性質(之所以需要這樣是因為如果進行了交換,那么子節點那里可能會發生變化,比如3 6 5 2 4這個情況,首先36進行了交換,變成了6 3 5 2 4,那么3 2 4那個部分(之前是6 2 4)就需要再次進行交換)。
  • 建堆,即從最后一個非葉子節點開始,自下而上維護堆的性質,直到根節點。
  • 堆排序,將當前堆的最大值放到數組末尾,然后把它排除出去,再從根向下進行堆的維護,新的堆的大小為n-1,重復這個過程,直到只剩一個元素。
    運行結果為:
create heap
16 14 8 9 10 3 4 1 7 2
sort heap for 9 nums 14 10 8 9 2 3 4 1 7 16
sort heap for 8 nums 10 9 8 7 2 3 4 1 14 16
sort heap for 7 nums 9 7 8 1 2 3 4 10 14 16
sort heap for 6 nums 8 7 4 1 2 3 9 10 14 16
sort heap for 5 nums 7 3 4 1 2 8 9 10 14 16
sort heap for 4 nums 4 3 2 1 7 8 9 10 14 16
sort heap for 3 nums 3 1 2 4 7 8 9 10 14 16
sort heap for 2 nums 2 1 3 4 7 8 9 10 14 16
sort heap for 1 nums 1 2 3 4 7 8 9 10 14 16
sorted heap
1 2 3 4 7 8 9 10 14 16

可以看到16, 10, 8, 7, 2, 3, 4, 1, 9, 14經過建堆過程(自最后一個非葉子節點向上維護堆),變成了16 14 8 9 10 3 4 1 7 2,然后需要進行堆排序,將1614交換,然后不管16了,這個時候它是最后一個元素,再從根向下維護堆,得到14 10 8 9 2 3 4 1 7 16,然后再將147交換,進行相同的步驟,最后排序成功。

有了堆排序的基礎,我們利用堆排序解決數組中的第K個最大元素的問題,事實上,在堆排序取最大值的過程中,已經體現出來了,在第一次取16,這就是第1大的元素,第二次取14就是第2大的元素,那么我們想得到第k大元素的值,只需要設置堆排序的停止條件為i > n - k,然后這時候的nums[0](即根節點值)為第k大的元素。如果我們想得到第’k’小的元素,那么就取第n-k+1大的元素。

詳細代碼如下:

#include <iostream>
#include <vector>
#include <algorithm> // for std::swap// 自上向下調整堆,保證堆的性質
void heapify(std::vector<int> &nums, int n, int i) {int largest = i; // 初始時假設當前節點為最大值int left = 2 * i + 1;  // 左子節點int right = 2 * i + 2; // 右子節點// 如果左子節點存在且大于當前節點,更新最大值節點if (left < n && nums[left] > nums[largest]) {largest = left;}// 如果右子節點存在且大于當前節點,更新最大值節點if (right < n && nums[right] > nums[largest]) {largest = right;}// 如果最大值節點發生了變化,交換當前節點和最大值節點的值,并繼續調整if (largest != i) {std::swap(nums[i], nums[largest]);heapify(nums, n, largest);}
}// 堆排序取數
int heapSelect(std::vector<int> &nums, int k) {int n = nums.size();// 從最后一個非葉子節點開始建堆,即從 (n/2 - 1) 節點開始for (int i = n / 2 - 1; i >= 0; i--) {heapify(nums, n, i);}std::cout << "create heap" << std::endl;for (int num : nums) {std::cout << num << " ";}std::cout << "\n";// 從最后一個元素開始,交換元素并進行調整堆操作for (int i = n - 1; i > n - k; i--) {std::swap(nums[0], nums[i]); // 將當前堆的最大值放到數組末尾heapify(nums, i, 0); // 調整堆,新的堆大小為 istd::cout << "sort heap for " << i << " nums" << " ";for (int num : nums) {std::cout << num << " ";}std::cout << "\n";}std::cout << "sorted heap" << std::endl;  for (int num : nums) {std::cout << num << " ";}return nums[0];
}int main() {std::vector<int> nums = {16, 10, 8, 7, 2, 3, 4, 1, 9, 14};int n = nums.size();int k = 4;int ans = heapSelect(nums, n - k + 1);std::cout << "ans=" << ans << std::endl;return 0;
}

運行結果:

create heap
16 14 8 9 10 3 4 1 7 2
sort heap for 9 nums 14 10 8 9 2 3 4 1 7 16
sort heap for 8 nums 10 9 8 7 2 3 4 1 14 16
sort heap for 7 nums 9 7 8 1 2 3 4 10 14 16
sort heap for 6 nums 8 7 4 1 2 3 9 10 14 16
sort heap for 5 nums 7 3 4 1 2 8 9 10 14 16
sort heap for 4 nums 4 3 2 1 7 8 9 10 14 16
sorted heap
4 3 2 1 7 8 9 10 14 16 ans=4

時間復雜度是O(nlogn),建堆的復雜度是O(n),刪除堆頂元素的復雜度是O(klogn),所以總共的時間復雜度是O(n+klogn)=O(nlogn)

本文來自互聯網用戶投稿,該文觀點僅代表作者本人,不代表本站立場。本站僅提供信息存儲空間服務,不擁有所有權,不承擔相關法律責任。
如若轉載,請注明出處:http://www.pswp.cn/bicheng/15698.shtml
繁體地址,請注明出處:http://hk.pswp.cn/bicheng/15698.shtml
英文地址,請注明出處:http://en.pswp.cn/bicheng/15698.shtml

如若內容造成侵權/違法違規/事實不符,請聯系多彩編程網進行投訴反饋email:809451989@qq.com,一經查實,立即刪除!

相關文章

qmt量化交易策略小白學習筆記第6期【qmt如何獲取股票歷史漲跌停價格】

qmt如何獲取股票歷史漲跌停價格 qmt更加詳細的教程方法&#xff0c;會持續慢慢梳理。 也可找尋博主的歷史文章&#xff0c;搜索關鍵詞查看解決方案 &#xff01; 感謝關注&#xff0c;需免費開通量化回測與咨詢實盤權限&#xff0c;可以和博主聯系&#xff01; 獲取股票歷史…

[數據結構] -- 單鏈表

&#x1f308; 個人主頁&#xff1a;白子寰 &#x1f525; 分類專欄&#xff1a;C打怪之路&#xff0c;python從入門到精通&#xff0c;數據結構&#xff0c;C語言&#xff0c;C語言題集&#x1f448; 希望得到您的訂閱和支持~ &#x1f4a1; 堅持創作博文(平均質量分82)&#…

c++編程14——STL(3)list

歡迎來到博主的專欄&#xff1a;c編程 博主ID&#xff1a;代碼小豪 文章目錄 list成員類型構造、析構、與賦值iterator元素訪問修改元素list的操作 list list的數據結構是一個鏈表&#xff0c;準確的說應該是一個雙向鏈表。這是一個雙向鏈表的節點結構&#xff1a; list的使用…

Vue學習筆記3——事件處理

事件處理 1、事件處理器&#xff08;1&#xff09;內聯事件處理器&#xff08;2&#xff09;方法事件處理器 2、事件參數3、事件修飾符 1、事件處理器 我們可以使用v-on 指令(簡寫為)來監聽DOM事件&#xff0c;并在事件觸發時執行對應的JavaScript。 用法: v-on:click"me…

JVM學習-執行引擎

執行引擎 執行引擎是Java虛擬機核心組成部分之一虛擬機是一個相對于物理機的概念&#xff0c;這兩種機器都有代碼執行能力&#xff0c;其區別是物理機的執行引擎是直接建立在處理器、緩存、指令集和操作系統層面上的&#xff0c;而虛擬機的執行引擎是由軟件自行實現的&#xf…

【算法】遞歸、搜索與回溯——簡介

簡介&#xff1a;遞歸、搜索與回溯&#xff0c;本節博客主要是簡單記錄一下關于“遞歸、搜索與回溯”的相關簡單概念&#xff0c;為后續算法做鋪墊。 目錄 1.遞歸1.1遞歸概念2.2遞歸意義2.3學習遞歸2.4寫遞歸代碼步驟 2.搜索3.回溯與剪枝 遞歸、搜索、回溯的關系&#xff1a; …

ICML2024 定義新隱私保護升級:DP-BITFIT新型微調技術讓AI模型學習更安全

DeepVisionary 每日深度學習前沿科技推送&頂會論文分享&#xff0c;與你一起了解前沿深度學習信息&#xff01; 引言&#xff1a;差分隱私在大模型微調中的重要性和挑戰 在當今的深度學習領域&#xff0c;大型預訓練模型的微調已成為提高各種任務性能的關鍵技術。然而&am…

推特熱帖:大語言模型自薦能夠替代的20種人類工作!快來看你是否需要轉行!

最近推特上有一個例子引起了廣泛的討論&#xff0c;事情的起因是這樣的&#xff1a;網友讓 GPT-4o 預測一下自己未來將會替代人類哪些工作&#xff1f; 這聽起來很有趣&#xff01;GPT-4o會給出什么樣的預測呢&#xff1f; 3.5研究測試&#xff1a;hujiaoai.cn 4研究測試&…

02-Linux【基礎篇】

一、Linux的目錄結構 1.基本介紹 Linux的文件系統采用層級式的樹狀目錄結構&#xff0c;在此結構中的最上層是根目錄"/"&#xff0c;然后在此目錄下再創建其他的目錄 深刻理解Linux樹狀文件目錄是非常重要的 記住一句經典的話&#xff1a;在Linux世界里&#xff…

如何在 DigitalOcean Droplet 云主機上創建 Ubuntu 服務器

在本文中&#xff0c;你將通過 DigitalOcean 的管理面板創建一個 Ubuntu 服務器&#xff0c;并將其配置為使用你的 SSH 密鑰。設置好服務器后&#xff0c;你可以在其上部署應用程序和網站。 本教程是DigitalOcean云課程簡介的一部分&#xff0c;它指導用戶完成將應用程序安全地…

win10右鍵沒有默認打開方式的選項的處理方法

問題描述 搞了幾個PDF書籍學習一下&#xff0c;不過我不想用默認的WPS打開&#xff0c;因為WPS太惡心人了&#xff0c;占用資源又高。我下載了個Sumatra PDF&#xff0c;這時候我像更改pdf文件默認的打開程序&#xff0c;發現右擊沒有這個選項。 問題解決 右擊文件–屬性–…

汽車以太網發展現狀及挑戰

一、汽車以太網技術聯盟 目前推動汽車以太網技術應用與發展的組織包括&#xff1a;OPEN Alliance&#xff08;One-Pair Ether-Net Alliance SIG&#xff09;聯盟&#xff0c;主要致力于汽車以太網推廣與使用&#xff0c;該聯盟通過推進 BroadR- Reach 單對非屏蔽雙絞線以太網傳…

設計新境界:大數據賦能UI的創新美學

設計新境界&#xff1a;大數據賦能UI的創新美學 引言 隨著大數據技術的蓬勃發展&#xff0c;它已成為推動UI設計創新的重要力量。大數據不僅為界面設計提供了豐富的數據資源&#xff0c;還賦予了設計師以全新的視角和工具來探索美學的新境界。本文將探討大數據如何賦能UI設計…

面試八股之JVM篇3.5——垃圾回收——G1垃圾回收器

&#x1f308;hello&#xff0c;你好鴨&#xff0c;我是Ethan&#xff0c;一名不斷學習的碼農&#xff0c;很高興你能來閱讀。 ??目前博客主要更新Java系列、項目案例、計算機必學四件套等。 &#x1f3c3;人生之義&#xff0c;在于追求&#xff0c;不在成敗&#xff0c;勤通…

1688. 比賽中的配對次數

題目&#xff1a; 給你一個整數 n &#xff0c;表示比賽中的隊伍數。比賽遵循一種獨特的賽制&#xff1a; 如果當前隊伍數是 偶數 &#xff0c;那么每支隊伍都會與另一支隊伍配對。總共進行 n / 2 場比賽&#xff0c;且產生 n / 2 支隊伍進入下一輪。 如果當前隊伍數為 奇數 …

python梯度下降法求解三元線性回歸系數,并繪制結果

import numpy as np import matplotlib.pyplot as plt # 生成隨機數據 np.random.seed(0) X1 2 * np.random.rand(100, 1) X2 3 * np.random.rand(100, 1) X3 4 * np.random.rand(100, 1) y 4 3 * X1 5 * X2 2 * X3 np.random.randn(100, 1) # 合并特征 X_b np.hsta…

Vue中組件之間的通信有哪些方法

在Vue中&#xff0c;組件之間的通信有多種方法&#xff0c;以下是一些常見的方法&#xff1a; Props和$emit&#xff1a; 父組件通過props向子組件傳遞數據。子組件通過$emit觸發事件&#xff0c;將數據傳遞給父組件。 provide和inject&#xff1a; 在Vue 2.2.0版本中引入的選…

云計算-特殊機制(Specialsed Mechanisms)

自動擴展監聽器 (Automated Scaling Listener) 自動擴展監聽器是一種特定類型的服務代理。它運行在云提供商的網絡中&#xff0c;監控云消費者和云服務之間的網絡流量。通過分析消費者和服務之間的消息量和類型&#xff0c;它可以測量云服務的負載。 自動擴展監聽器對變化的負載…

常見 JVM 面試題補充

原文地址 : 26 福利&#xff1a;常見 JVM 面試題補充 (lianglianglee.com) CMS 是老年代垃圾回收器&#xff1f; 初步印象是&#xff0c;但實際上不是。根據 CMS 的各個收集過程&#xff0c;它其實是一個涉及年輕代和老年代的綜合性垃圾回收器。在很多文章和書籍的劃分中&…

SpringCloud Alibaba的相關組件的簡介及其使用

Spring Cloud Alibaba是阿里巴巴為開發者提供的一套微服務解決方案&#xff0c;它基于Spring Cloud項目&#xff0c;提供了一系列功能強大的組件&#xff0c;包括服務注冊與發現、配置中心、熔斷與限流、消息隊列等。 本文將對Spring Cloud Alibaba的相關組件進行簡介&#xff…