【分治】快排與歸并專題

分治思想

分(Divide):將待排序數組不斷拆分為兩個等長(或近似等長)的子數組,直到子數組長度為 1(天然有序)。
治(Conquer):遞歸排序每個子數組。
合(Merge):將兩個已排序的子數組合并為一個更大的有序數組,重復此過程直到合并為完整的有序數組。

歸并排序

  • 基本步驟:

1.遞歸拆分:
計算數組中點 mid,將數組分為左子區間 [left, mid] 和右子區間 [mid+1, right]。
遞歸拆分左右子區間,直到子區間長度為 1(left >= right 時終止遞歸)。
2.合并有序子區間:
用兩個指針分別遍歷左右兩個有序子區間,每次將較小的元素放入臨時數組 tmp。
處理剩余未遍歷完的子區間,將剩余元素追加到 tmp 中。將 tmp 中的有序元素拷貝回原數組的對應位置。

  • 關鍵特點

1.時間復雜度:
最好 / 最壞 / 平均情況均為 O(n log n)。原因:拆分過程產生 log n 層遞歸,每層合并總耗時 O(n)。
2.空間復雜度:
O(n),需額外的臨時數組 tmp 存儲合并結果。
3.穩定性:
穩定排序。當元素相等時,左區間元素先放入臨時數組,保持原相對順序。
4.適用場景:
適合大規模數據排序(時間復雜度穩定)。
適合鏈表排序(可通過指針操作實現 O (1) 額外空間,避免數組拷貝開銷)。
不適合內存受限場景(需額外空間)。

一. (75.) 顏色分類

在這里插入圖片描述
解法:
類?數組分兩塊的算法思想,這?是將數組分成三塊,可以再添加?個指針。此題還可以用內置接口sort(開始迭代器,結束迭代器)來直接做
在這里插入圖片描述

class Solution {
public:void sortColors(vector<int>& nums) {//將left和right置-1和n位置是為了保證區間內首元素交換正確int left=-1,right=nums.size(),cur=0;while(cur<right){if(nums[cur]==0) swap(nums[++left],nums[cur++]);else if(nums[cur]==1) cur++;else if(nums[cur]==2) swap(nums[--right],nums[cur]);}}
};

二. (912.) 排序數組-快排

在這里插入圖片描述

快速排序(劃分三數組實現優化)

算法原理
快排最核?的?步就是 Partition (分割數據):將數據按照?個標準,分成左右兩部分。
隨機選擇基準算法流程:
函數設計:int randomKey(vector& nums, int left, int right)
a. 在主函數那?種?顆隨機數種?;
b. 在隨機選擇基準函數這??成?個隨機數;
c. 由于我們要隨機產??個基準,因此可以將隨機數轉換成隨機下標:讓隨機數 % 上區間??,然后加上區間的左邊界即可。
快速排序算法主要流程:
a. 定義遞歸出?;
b. 利?隨機選擇基準函數?成?個基準元素;
c. 將數組劃分為 左 中 右 三部分:左邊是?基準元素?的數據,中間是與基準元素相同的數據,右邊是?基準元素?的數據。
d. 遞歸處理左邊區域和右邊區域。
采取數組分三塊的思想實現快排
在這里插入圖片描述
時間復雜度:

時間復雜度為NlogN,,遞歸調用棧的深度為logN,算法導論中概率求期望證明
快速排序每次劃分后,需要遞歸處理左右兩個子區間,總工作量是:
O(n) + O(n/2) + O(n/2) + O(n/4) + O(n/4) + … = O(n log n)(每次遞歸的兩個子區間工作量相加,導致總規模是 n log n)。

class Solution {
public:vector<int> sortArray(vector<int>& nums) {srand(time(NULL));//種下一顆隨機數種子qsort(nums,0,nums.size()-1);return nums;}void qsort(vector<int>& nums,int left,int right){if(left>=right) return;//遞歸返回條件//獲取隨機數,自定義函數int key = getRandom(nums, left, right);int l=left-1,i=left,r=right+1;while(i<r){//注意條件判斷只能走一次,防止i一直++后>right超出范圍if(nums[i]<key) swap(nums[++l],nums[i++]);else if(nums[i]==key) i++;else if(nums[i]>key) swap(nums[--r],nums[i]);}qsort(nums,left,l);//遞歸處理qsort(nums,r,right);}int getRandom(vector<int>& nums,int left,int right){int r=rand();return nums[r%(right-left+1)+left];}
};

三. (215.) 數組中的第K個最?元素

在這里插入圖片描述

優先級隊列方法:

class Solution {
public:int findKthLargest(vector<int>& nums, int k) {priority_queue<int> st(nums.begin(),nums.end());//for(int i=0;i<k-1;i++)while(--k){st.pop();}return st.top();}
};

快速選擇排序方法:
思路:

依舊是通過獲取隨機數當key,快排將數組分為三塊的方法,但是計算每一個區間的數量與k進行比較,選擇相應區間遞歸一次即可獲得結果

分情況討論:

將數組劃分三塊后,只能保證大小順序,但每個區域內部還是無序狀態,所以還要進行遞歸,本題的優勢在于根據第k大,可以選擇一個區域遞歸。
在這里插入圖片描述

時間復雜度:

快速選擇只處理一個子區間,避免了另一半的工作量,因此總時間復雜度從 O (n log n) 降至 O (n)。
在這里插入圖片描述

class Solution {
public:int findKthLargest(vector<int>& nums, int k) {srand(time(NULL));//可以這么記s代表start,即開始隨機數return qsort(nums,k,0,nums.size()-1);}int qsort(vector<int>& nums,int k,int left,int right){//每一個區域都是從左往右遍歷,所以返回nums[左下標]if(left>=right) return nums[left];int key=getrandom(nums,left,right);//將數組分為三塊int i=left,l=left-1,r=right+1;while(i<r){if(nums[i]<key) swap(nums[++l],nums[i++]);else if(nums[i]==key) i++;else swap(nums[--r],nums[i]);}//劃分區間,選擇性遞歸int b=r-l-1,c=right-r+1;//獲取中,右區間元素,左邊通過前兩者可得if(c>=k) return qsort(nums,k,r,right);if(b+c>=k) return key;else return qsort(nums,k-b-c,left,l);}int getrandom(vector<int>nums,int left,int right){int r=rand();return nums[r%(right-left+1)+left];}
};

四. (劍指 Offer 40.) 最小的k個數

在這里插入圖片描述
思路:
和第三題相同,區別在于從求第k大的元素變成求k個最小的元素,主邏輯相同,利用隨機選擇快排將數組分三塊,我們的目的就是將前k個最小的元素放到數組前k個位置,其他位置順序不用考慮
情況討論:
在這里插入圖片描述
abc分別為小、等于、大三個區間元素的個數。

五. (912.) 排序數組-歸并

在這里插入圖片描述

簡單對比歸并與快排

解法:
題目與二相同,但采取歸并解法
在這里插入圖片描述
快排與歸并本質思想都相同,分區間處理問題,只是處理問題的時機不同,歸并排序類似于二叉樹的后序遍歷,快排相當于二叉樹的前序遍歷。
1.歸并就是先遞歸到最底層,然后逐層排序左區間和右區間一層層由小及大返回
2.快排是每一層先排序好了再往下由大到小遞歸

class Solution {
public:vector<int> sortArray(vector<int>& nums) {int*tmp=(int *)malloc(sizeof(int)*nums.size());mergesort(nums,0,nums.size()-1,tmp);return nums;}void mergesort(vector<int>& nums,int left,int right,int *tmp){if(left>=right) return;int mid=(right+left)>>1;//與除2操作等價,但位運算效率更高mergesort(nums,left,mid,tmp);//遞歸處理左右子區間mergesort(nums,mid+1,right,tmp);//歸并int begin1=left,begin2=mid+1;int i=left;//臨時變量來遍歷while(begin1<=mid&&begin2<=right){if(nums[begin1]<nums[begin2]) tmp[i++]=nums[begin1++];else tmp[i++]=nums[begin2++];}//排序其中一個區間剩下的元素while(begin1<=mid){tmp[i++]=nums[begin1++];}while(begin2<=right){tmp[i++]=nums[begin2++];}//將排序后元素拷貝回臨時數組for(int i=left;i<=right;i++){nums[i]=tmp[i];}}};

也可以采取這樣的方式定義類成員函數

class Solution
{vector<int> tmp;
public:vector<int> sortArray(vector<int>& nums) {tmp.resize(nums.size());

效率差異:
1.malloc 僅做 “內存分配”,開銷集中在與操作系統的內存管理交互(如查找空閑內存塊)。
2.resize 不僅分配內存,還涉及 “對象管理”,resize 會在分配內存后對新元素進行初始化,內部還需要維護 size、capacity 等成員變量的更新,所以開銷會比malloc大一些

六. (劍指 Offer 51.) 數組中的逆序對

在這里插入圖片描述
解法:

  • 暴力解法

兩層for循環,依次對每個元素可能的逆序對做查找,會超時

  • 歸并排序

運用分治思想,遞歸將數組分為兩部分,計算出左邊區間的逆序對個數和右邊區間的個數,最后再一左一右來計算逆序對個數,在左右兩邊分別求出逆序對的個數會在遞歸過程中完成(因為遞歸到左右區間元素各為1個開始返回,兩個元素合并區間,相當于求得了各自區間的逆序對,只需考慮一左一右區間即可,如此往復),實際上只用考慮合并過程中統計出的逆序對數量。

  • 升序過程
    在這里插入圖片描述
    重點在遞歸合并后會形成有序的數組,以此遞歸下去,可利用數組的有序性,一次性獲得一串逆序對,如上圖中mid-cur1+1
class Solution {//輔助數組與輔助計數int num=0;vector<int> v;
public:int reversePairs(vector<int>& nums) {v.resize(nums.size());mergesort(nums,0,nums.size()-1);return num;}void mergesort(vector<int>& nums,int left,int right){if(left>=right) return ;//計算中間值int mid=(right+left)>>1;//遞歸mergesort(nums,left,mid);mergesort(nums,mid+1,right);int cur1=left,cur2=mid+1,i=left;//歸并,處理一左一右區間的逆序對個數while(cur1<=mid&&cur2<=right){if(nums[cur1]<=nums[cur2]){v[i++]=nums[cur1++];}else {num+=(mid-cur1+1);v[i++]=nums[cur2++];}}//剩余元素處理while(cur1<=mid) v[i++]=nums[cur1++];while(cur2<=right) v[i++]=nums[cur2++];//將輔助數組中元素更新到原數組,形成新的左或右子區間遞歸for(int i=left;i<=right;i++) nums[i]=v[i];}
};
  • 降序過程

在這里插入圖片描述

//歸并,處理一左一右區間的逆序對個數while(cur1<=mid&&cur2<=right){if(nums[cur1]<=nums[cur2]){v[i++]=nums[cur2++];}else {num+=(right-cur2+1);v[i++]=nums[cur1++];}}

只有歸并這一部分不同

七. (315.) 計算右側?于當前元素的個數

在這里插入圖片描述
解法:
思路與上一題逆序對相同,采用上題降序的思路,區別在于還需要創建一個數組來存儲原數組元素與下標的對應關系,方便返回數組更新時的下標查找,因為歸并過程讓原數組重新排序了。在歸并時除了重新排序原數組之外還要重新排序下標數組,以保持一樣的映射關系

class Solution {//定義輔助數組vector<int> tmp1;//返回值vector<int> tmp2;//下標關系//定義返回數組vector<int> ret;vector<int> index;
public:vector<int> countSmaller(vector<int>& nums) {ret.resize(nums.size());tmp1.resize(nums.size());tmp2.resize(nums.size());index.resize(nums.size());//初始化index數組為下標for(int i=0;i<nums.size();i++) index[i]=i;mergesort(nums,0,nums.size()-1);return ret;}void mergesort(vector<int>&nums,int left,int right){if(left>=right) return;//計算中間值int mid=(left+right)>>1;//遞歸處理左右子區間mergesort(nums,left,mid);mergesort(nums,mid+1,right);//歸并排序int cur1=left,cur2=mid+1;int i=left;//臨時數組下標while(cur1<=mid&&cur2<=right){if(nums[cur1]<=nums[cur2]){tmp2[i]=index[cur2];tmp1[i++]=nums[cur2++];}else{//更新返回數組,+=是因為該元素對應位置可能已經更新過了,//排序后重新計算,要用+=避免覆蓋之前結果ret[index[cur1]]+=right-cur2+1;//注意這里查找cur1的下標,因為降序數組是固定cur1,查找cur2的小于情況tmp2[i]=index[cur1];tmp1[i++]=nums[cur1++];}}//處理剩余元素while(cur1<=mid) tmp2[i]=index[cur1],tmp1[i++]=nums[cur1++];while(cur2<=right) tmp2[i]=index[cur2],tmp1[i++]=nums[cur2++];//更新原數組使之有序,并更新對應下標數組for(int i=left;i<=right;i++) nums[i]=tmp1[i],index[i]=tmp2[i];}
};

八.(493.) 翻轉對

在這里插入圖片描述
解法:
采用計算逆序對的思路,通過升序或降序數組,利用數組有序性一次性獲得一堆符合條件的翻轉對。不同的是這里判斷條件不同,不能在歸并排序的過程中順便計算翻轉對,而是要先計算翻轉對,再進行歸并排序。
注意:
1.需要注意判斷條件不要用大于兩倍的方法,會整數溢出,用原數的一半來判斷,要/2.0保證精度判斷
2.關于降序和升序題六中已詳解,本質是利用雙指針同向移動找出區間范圍內符合翻轉對的元素,固定一個指針再另一個指針中找符合條件的區間范圍,一般固定一側指針只能用升序數組或降序數組其中一種方法,另一種會重復計算

  • 降序數組方法:
class Solution {int ret=0;vector<int> tmp;
public:int reversePairs(vector<int>& nums) {tmp.resize(nums.size());mergesort(nums,0,nums.size()-1);return ret;}void mergesort(vector<int>&nums,int left,int right){if(left>=right) return;int mid=(left+right)>>1;//遞歸計算左右兩側翻轉對數量mergesort(nums,left,mid);mergesort(nums,mid+1,right);//先計算翻轉對降序方法固定cur1,有多少個cur2小于int cur1=left,cur2=mid+1,i=left;while(cur1<=mid&&cur2<=right){if((nums[cur1]/2.0)>nums[cur2]){ret+=right-cur2+1;cur1++;}else cur2++;}//再歸并排序cur1=left,cur2=mid+1,i=left;;while(cur1<=mid&&cur2<=right){if(nums[cur1]>nums[cur2]){tmp[i++]=nums[cur1++];}else tmp[i++]=nums[cur2++];}//處理剩余元素while(cur1<=mid) tmp[i++]=nums[cur1++];while(cur2<=right) tmp[i++]=nums[cur2++];//使原數組有序,進行下次遞歸for(int i=left;i<=right;i++) nums[i]=tmp[i];}
};
  • 升序數組方法

只有這一部分與降序不同,注意兩數倍數大小關系比較邏輯不變

//先計算翻轉對,升序方法固定cur2,有多少個cur1大于int cur1=left,cur2=mid+1,i=left;while(cur1<=mid&&cur2<=right){if((nums[cur1]/2.0)>nums[cur2]){ret+=mid-cur1+1;cur2++;}else cur1++;}//再歸并排序cur1=left,cur2=mid+1,i=left;;while(cur1<=mid&&cur2<=right){if(nums[cur1]>nums[cur2]){tmp[i++]=nums[cur2++];}else tmp[i++]=nums[cur1++];}

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

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

相關文章

[Linux]學習筆記系列 -- mm/page_alloc

文章目錄mm/page_alloc.c 伙伴系統內存分配器(Buddy System Memory Allocator) 內核物理內存管理的核心歷史與背景這項技術是為了解決什么特定問題而誕生的&#xff1f;它的發展經歷了哪些重要的里程碑或版本迭代&#xff1f;目前該技術的社區活躍度和主流應用情況如何&#xf…

3秒傳輸大文件:cpolar+Localsend實現跨網絡秒傳

文章目錄前言1. 在Windows上安裝LocalSend2. 安裝Cpolar內網穿透3. 公網訪問LocalSend4. 固定LocalSend公網地址用 cpolar 讓 Localsend 突破距離限制就是這么簡單&#xff01;三步輕松搞定&#xff1a;在手機和電腦上都安裝 Localsend&#xff0c;在其中一臺設備上運行 cpolar…

基于STM32單片機智能RFID刷卡汽車位鎖樁設計

1 系統功能介紹 本系統是一個 基于 STM32 單片機的智能 RFID 刷卡車位鎖樁控制系統&#xff0c;其設計理念來源于現實中智能停車場的車位鎖樁管理。通過 RFID 刷卡認證、LCD1602 顯示、繼電器控制以及按鍵輔助操作&#xff0c;實現對車位的安全管理。該系統不僅模擬了車輛駛入與…

SQL185 試卷完成數同比2020年的增長率及排名變化

描述現有試卷信息表examination_info&#xff08;exam_id試卷ID, tag試卷類別, difficulty試卷難度, duration考試時長, release_time發布時間&#xff09;&#xff1a;試卷作答記錄表exam_record&#xff08;uid用戶ID, exam_id試卷ID, start_time開始作答時間, submit_time交…

網絡編程中的TCP——TCP的連接的建立、關閉、狀態轉移

網絡編程中的TCP——TCP的連接的建立、關閉、狀態轉移 TCP連接的建立和關閉wireshark捕獲數據&#xff1a;TCP三次握手四次揮手的時序圖&#xff1a;三次握手&#xff1a; 報文段1包含SYN標志&#xff0c;這是一個同步報文段&#xff0c;表示發起連接請求&#xff0c;包含自己起…

SQL 語句拼接在 C 語言中的實現與安全性分析

代碼解析 // 構建SQL插入語句 char *sql_insert (char *)malloc(sizeof(char) * 200); // 分配200字節內存 strcpy(sql_insert, "INSERT INTO user(username, passwd) VALUES("); // 復制基礎SQL語句 strcat(sql_insert, ""); // 添加單引號 strcat(sq…

`lock()` 和 `unlock()` 線程同步函數

1) 函數的概念與用途 lock() 和 unlock() 不是特定的標準庫函數&#xff0c;而是線程同步原語的一般概念&#xff0c;用于在多線程環境中保護共享資源。在不同的編程環境和庫中&#xff0c;這些函數有不同的具體實現&#xff08;如 POSIX 線程的 pthread_mutex_lock() 或 C 的 …

升級openssh后ORACLE RAC EM 安裝失敗處理

升級過程中由于SCP傳輸時目標目錄/tmp/tempRACTrans_2025_08_22--18-25-44-032/ractrans 不存在導致的OC4J配置失敗&#xff1a;WARNING: /usr/bin/scp: dest open "/tmp/tempRACTrans_2025_08_22--18-25-44-032/ractrans": No such file or directory/usr/bin/scp…

ADB 調試工具的學習[特殊字符]

一、ADB 的工作原理 1.1 ADB 概念 ADB (Android Debug Bridge)&#xff1a;Android 調試橋&#xff0c;是開發/測試 Android 應用必備的調試工具。作用&#xff1a;通過 電腦終端命令 操作 安卓手機/模擬器。 1.2 ADB 構成與原理 ADB 由三部分組成&#xff1a; Client 端&#…

用一根“數據中樞神經”串起業務從事件流到 Apache Kafka

1. 為什么是“事件流”&#xff1f; 在一個軟件定義、自動化、永遠在線的世界里&#xff0c;系統之間最需要的是&#xff1a;把發生了什么這件事&#xff0c;第一時間、按正確順序、可靠地傳到該知道的人/系統那里。 事件流就像企業的中樞神經&#xff1a;它把數據庫更新、設備…

【RAGFlow代碼詳解-4】數據存儲層

數據庫基礎設施 RAGFlow 使用關系數據庫&#xff08;MySQL 或 PostgreSQL&#xff09;作為主要元數據存儲&#xff0c;通過具有連接池和重試機制的 Peewee ORM 進行管理。 連接管理 數據庫連接通過 service_conf.yaml 和環境變量進行配置。該系統支持具有可配置連接池的 MySQL …

ES_映射

一、 映射&#xff08;Mapping&#xff09;是什么&#xff1f; 簡單來說&#xff0c;映射就像是關系型數據庫中的表結構定義&#xff08;Schema&#xff09;。它定義了索引&#xff08;Index&#xff09;中的文檔&#xff08;Document&#xff09;可以包含哪些字段&#xff08;…

【Linux | 網絡】多路轉接IO之poll

一、poll函數二、poll的優缺點三、實現poll服務器&#xff08;只關心讀事件&#xff09;3.1 Log.hpp&#xff08;日志&#xff09;3.2 Lockguard.hpp&#xff08;自動管理鎖&#xff09;3.3 Socket.hpp&#xff08;封裝套接字&#xff09;3.4 PollServer.hpp&#xff08;服務端…

一站式資源共享平臺模板,助力快速搭建專屬資源站源碼

內容目錄一、詳細介紹二、效果展示1.部分代碼2.效果圖展示三、學習資料下載一、詳細介紹 這個資源分享網站模板是一個功能完整、設計現代的單頁網站&#xff0c;非常適合快速搭建資源分享平臺。以下是關于這個模板的詳細介紹&#xff0c;幫助你更好地理解并發布到自己的網站&a…

ngnix的部分配置

1. 禁止特定IP地址訪問你可以通過在Nginx配置文件中添加deny指令來阻止特定IP地址或IP地址段的訪問。server {listen 80;server_name example.com;location / {deny 192.168.1.0/24;allow all;} }2. 允許特定IP地址訪問如果你想允許只有特定IP地址或IP地址段的訪問&#xff0c;…

Qwt7.0-打造更美觀高效的Qt開源繪圖控件庫

概述 Qt 生態里能畫圖的庫不多&#xff0c;主流的為QCustomPlot、Qwt、Qt Charts和KDChart&#xff0c;Qt6.8之后把原來的 Qt Charts&#xff08;2D&#xff09; 與 Qt DataVisualization&#xff08;3D&#xff09; 合并為統一的Qt Graphs模塊&#xff08;注意不是Qt Graphic…

NFC線圈設計計算

對工作于13.56MHz的電感耦合的NFC系統,針對小距離的傳統天線通常是環形或者矩形的扁平線圈。 圓形扁平線圈計算評估 對于二階估計,我們可以由匝數決定的電感等式為 考慮到線圈的物理參數,設置平均直徑:D_averD0-N(gw) 線圈周長: &#xff1b;d2*(w t)/π 初始設置中的這種電感…

mac設置鼠標滾輪方向

mac中滾輪的滑動方向和windows是相反的&#xff0c;如果需要設置和windows相同&#xff0c;設置如下&#xff1a;將自然滾動關閉即可。

QSpinBox的用法及其使用QSS對其美化

摘要 在現代應用程序開發中&#xff0c;提供一個直觀且用戶友好的界面至關重要。Qt框架提供了豐富的控件和工具&#xff0c;幫助開發者實現這一目標。本文將詳細介紹如何使用Qt的QSpinBox控件讓用戶輸入數值&#xff0c;并通過Qt Style Sheets (QSS) 美化界面&#xff0c;提升…

18 繼續學習

要設計出一個好的系統&#xff0c;需要多年的知識積累。有一個捷徑是研究真實世界的系統架構。本文將介紹一些有幫助的閱讀材料。 務必留意那些真實系統之間共通的原理和相同的底層技術。研究每個技術并了解它解決了什么問題&#xff0c; 這是一個鞏固基礎知識和完善設計過程的…