算法系列--分治排序|歸并排序|逆序對的求解

一.基本概念與實現

歸并排序(mergeSort)也是基于分治思想的一種排序方式,思路如下:

  1. 分解:根據中間下標mid將數組分解為兩部分
  2. 解決:不斷執行上述分解過程,當分解到只有一個元素時,停止分解,此時就是有序的
  3. 合并:合并兩個有序的子區間,所有子區間合并的結果就是原問題的解

歸并排序和快速排序的區別在于排序的時機不同,歸并排序是等到分解完畢之后在進行排序,快速排序是先進行分解,再排序;更形象的說,歸并排序更像二叉樹的后序遍歷,遍歷完左右子樹再打印根節點;快速排序反之
在這里插入圖片描述

代碼:

class Solution {int[] tmp;public int[] sortArray(int[] nums) {tmp = new int[nums.length];mergeSort(nums, 0, nums.length - 1);return nums;}private void mergeSort(int[] nums, int start, int end) {if(start >= end) return;// 遞歸出口int mid = (start + end) >> 1;// 分解左右子樹mergeSort(nums, start, mid);mergeSort(nums, mid + 1, end);// 合并兩個有序序列merge(nums, start, mid, end);}private void merge(int[] nums, int l, int mid, int r) {// 合并兩個有序列表int i = 0, i1 = l, i2 = mid + 1;while(i1 <= mid && i2 <= r)tmp[i++] = nums[i1] < nums[i2] ? nums[i1++] : nums[i2++];while(i1 <= mid) tmp[i++] = nums[i1++];while(i2 <= r) tmp[i++] = nums[i2++];// 重新賦值for(int j = l; j <= r; j++) nums[j] = tmp[j - l];}
}
  • 將tmp設置為全局減少每次合并兩個有序列表都要new所消耗的時間

2.利用歸并排序求解逆序對

逆序對
鏈接:https://leetcode.cn/problems/shu-zu-zhong-de-ni-xu-dui-lcof/description/
在這里插入圖片描述
分析

  • 最容易想到的思路就是暴力解法,每遍歷到一個數字就統計后面有多少個小于當前數字的數字,只要小于就滿足逆序對的條件,時間復雜度為O(N^2),顯而易見這種做法的時間復雜度過高,無法通過所有樣例

  • 可以這么想,每遍歷到一個數字,我就想"如果我知道我前面有多少個比我大的數字該多好",就不需要再去一個一個遍歷了,或者是每遍歷到一個數字,“如果我知道后面有多少個比我小的數字該多好”

  • 但是實際上不容易解決,不容易統計,比如9,7,8這樣的一個序列,你無法通過記錄最值的方式統計有多少個大的數字,這是行不通的,好像唯一的解法就是從開頭一直遍歷到當前數字?可這就是暴力解法啊,如果能對前面的區間進行排序,得到大于當前數字的所有數字中的最小值,就能根據下標快速得到有多少個比我大的數字.那難道每遍歷到一個數字就對前面的區間排一下序再找最小值嗎?時間復雜度好像更高了,且不穩定

  • 但是這個想法是好的,在遍歷的過程中如果前面的元素是有序的,那么就能降低時間復雜度,關于逆序對的求解,排序是一個很好的思路

  • 排序一般都是從小到大排序,無論哪種排序方式,對于一個亂序的數組,如果想讓他變成有序的,就必須要進行元素的移動,就會將較小的數字放到較大的數字之前,可見排序就是消除逆序對的過程,以下是幾種基于排序的求解逆序對的方法

01.冒泡排序

  • 對于冒泡排序,每次元素交換就可以看做一次消除逆序對;使用全局變量ret記錄元素交換的次數
  • 冒泡排序的本質是"每次移動,都通過元素比較和元素交換讓當前區間的最大值移動到區間末尾"

代碼:

class Solution {// 冒泡排序法int ret;public int reversePairs(int[] nums) {bubbleSort(nums);return ret;}private void bubbleSort(int[] nums) {int n = nums.length;for (int i = 0; i < n; i++) {boolean flg = false;for (int j = 0; j < n - 1 - i; j++) {if (nums[j] > nums[j + 1]) {swap(nums, j, j + 1);flg = true;}}if (!flg)return;}}private void swap(int[] nums, int i, int j) {int tmp = nums[i];nums[i] = nums[j];nums[j] = tmp;++ret;}
}
  • 時間復雜度:O(N^2)

02.插入排序

  • 對于插入排序,每次元素的移動都可以看做一次消除逆序對,使用一個全局變量ret記錄元素移動的次數即可,時間比冒泡排序略快
  • 插入排序就像是往你的已有的有序的撲克牌中插入一個新牌,每次都是從后往前進行比較,進行插入

代碼:

class Solution {// 插入排序法int ret;public int reversePairs(int[] nums) {for(int i = 1; i < nums.length; i++) {int tmp = nums[i], j = i - 1;while(j >= 0) {if(nums[j] > tmp) {nums[j + 1] = nums[j];++ret;}else break;--j;}nums[j + 1] = tmp;}return ret;}
}
  • 時間復雜度:O(N^2)

03.歸并排序(最快的解法)

代碼:

class Solution {int[] tmp;// 用于合并兩個有序數組的臨時數組int ret;// 記錄遞歸過程中的逆序對的個數public int reversePairs(int[] nums) {tmp = new int[nums.length];mergeSort(nums, 0, nums.length - 1);// 歸并排序return ret;}private void mergeSort(int[] nums, int start, int end) {if(start >= end) return;int mid = (start + end) >> 1;mergeSort(nums, start, mid);mergeSort(nums, mid + 1, end);merge(nums, start, mid, end);}private void merge(int[] nums, int l, int mid, int r) {// 在合并的過程中統計逆序對的個數// 關鍵步驟  合并兩個有序數組// 在合并的過程中統計逆序對的個數!!!// 此處采用 的邏輯是:固定cur2,找cur1中比當前cur2大的數// 在排序的過程中應該使用升序排序int i = 0, i1 = l, i2 = mid + 1;while(i1 <= mid && i2 <= r) {if(nums[i1] > nums[i2]) {// 如果大  則i1后面的數字都比當前數字大  都能構成逆序對ret += mid - i1 + 1;tmp[i++] = nums[i2++];}else {tmp[i++] = nums[i1++];}}while(i1 <= mid) tmp[i++] = nums[i1++];while(i2 <= r) tmp[i++] = nums[i2++];for(int j = l; j <= r; j++) nums[j] = tmp[j - l];}
}
  • 時間復雜度:O(N*logN)

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


3.計算右側小于當前數的個數

鏈接:https://leetcode.cn/problems/count-of-smaller-numbers-after-self/description/
分析

  • 是上一題逆序對的拓展版本
  • 利用上面快速求解逆序對的思想,能夠快速得到小于當前數字的數字的個數,需要注意的是,在歸并排序的過程中原數組的元素會發生移動,但是只有歸并完整個數組才能得到最終的結果,這個過程中我們要將數組元素和數組下標進行綁定,可以考慮使用index數組
  • 當元素發生移動時,下標也跟著移動;

代碼:

class Solution {int[] tmpNum;int[] tmpIndex;int[] index;int[] cnt;public List<Integer> countSmaller(int[] nums) {int n = nums.length;tmpNum = new int[n];tmpIndex = new int[n];index = new int[n];cnt = new int[n];for(int i = 0; i < n; i++) index[i] = i;// 綁定索引mergeSort(nums, 0, n - 1);List<Integer> ret = new ArrayList<>();for(int x : cnt) ret.add(x);return ret;}private void mergeSort(int[] nums, int start, int end) {if(start >= end) return;int mid = (start + end) >> 1;mergeSort(nums, start, mid);mergeSort(nums, mid + 1, end);merge(nums, start, mid, end);}private void merge(int[] nums, int l, int mid, int r) {// 合并兩個有序列表(降序排序)// 可以快速得到有多少個數字小于當前數字int i = 0, i1 = l, i2 = mid + 1;while(i1 <= mid && i2 <= r) {if(nums[i1] > nums[i2]) {cnt[index[i1]] += r - i2 + 1;tmpIndex[i] = index[i1];tmpNum[i++] = nums[i1++];}else  {tmpIndex[i] = index[i2];tmpNum[i++] = nums[i2++];}}// 處理未解決的元素while(i1 <= mid) {tmpIndex[i] = index[i1];tmpNum[i++] = nums[i1++];}while(i2 <= r) {tmpIndex[i] = index[i2];tmpNum[i++] = nums[i2++];}// 重新賦值  原數組和下標數組都要更改for(int j = l; j <= r; j++) {nums[j] = tmpNum[j - l];index[j] = tmpIndex[j - l];}}
}

4.反轉對的個數

鏈接:

代碼:

class Solution {int[] tmp;int ret;public int reversePairs(int[] nums) {int n = nums.length;tmp = new int[n];mergeSort(nums, 0, n - 1);return ret;}private void mergeSort(int[] nums, int start, int end) {if(start >= end) return;int mid = (start + end) >> 1;mergeSort(nums, start, mid);mergeSort(nums, mid + 1, end);merge(nums, start, mid, end);}private void merge(int[] nums, int l, int mid, int r) {// 先統計當前有多少個翻轉對int i = 0, i1 = l, i2 = mid + 1;while(i1 <= mid) {while(i2 <= r && nums[i2] >= nums[i1] / 2.0) i2++;// 注意此處的除法需要存在小數位if(i2 > r) break;ret += r - i2 + 1;i1++;}i1 = l; i2 = mid + 1;while(i1 <= mid && i2 <= r) {if(nums[i1] > nums[i2]) tmp[i++] = nums[i1++];else tmp[i++] = nums[i2++];}while(i1 <= mid) tmp[i++] = nums[i1++];while(i2 <= r) tmp[i++] = nums[i2++];for(int j = l; j <= r; j++) nums[j] = tmp[j - l];}
}

注意:

  1. 使用除法進行判斷是為了防止越界
  2. /2和/2.0的結果是不同的
  3. 本題在合并兩個有序列表之前需要先統計翻轉對的個數,具體做法就是暴力遍歷兩個數組,不能在合并的時候統計翻轉對的個數

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

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

相關文章

第一節 網絡安全概述

一.網絡空間安全 網絡空間&#xff1a;一個由信息基礎設施組成相互依賴的網絡。 ---- 海陸空天&#xff08;大海、陸 地、天空、航天&#xff09; 通信保密階段 ---- 計算機安全 ----- 信息系統安全 ----- 網絡空間安全 計算機安全&#xff1a;開始秉持著“嚴于律己&#x…

C語言 指針和數組—指針數組及其在字符串處理中的應用

目錄 問題的提出 問題的解決 回頭看——指針、數組及其他類型的混合 指針數組與指向數組的指針 字符串的排序 問題的提出 問題的解決 回頭看——指針、數組及其他類型的混合 ? 基本數據類型 ? int 、 long 、 char 、 short 、 float 、 double…… ? 數組是一種從…

os.makedirs

官方說明文檔&#x1f517;&#xff1a;Link 解釋下面的代碼&#xff1a; os.makedirs(os.path.join(args.output_dir,sample_images), exist_okTrue)os.makedirs()&#xff1a;這是一個用于遞歸創建目錄的Python函數。如果中間級目錄&#xff08;目錄鏈中的所有目錄&#xff…

The IsA relationship and HasA relationship

Why you should worry about that? or not. Is-A (Inheritance) Represents an “is-a-kind-of” hierarchy between classes. A subclass (child class) inherits attributes and methods from its superclass (parent class). Subclasses can specialize or override inh…

設計模式之模版方法

模版方法介紹 模版方法&#xff08;Template Method&#xff09;模式是一種行為型設計模式&#xff0c;它定義了一個操作&#xff08;模板方法&#xff09;的基本組合與控制流程&#xff0c;將一些步驟&#xff08;抽象方法&#xff09;推遲到子類中&#xff0c;使得子類可以在…

旅游 | 西岳華山

得到了再失去&#xff0c; 總比從來沒有得到更傷人。 ——胡賽尼《追風箏的人》 目錄 旅游 | 西岳華山00 | 旅游導圖01 | 旅游路線02 | 必帶行李03 | 旅游費用3.1 門票3.2 索道價格3.2.1 北峰索道&#xff08;單程&#xff09;3.2.1 西峰索道&#xff08;單程&#xff09; 3.3 …

掌握 IPython 歷史的藝術:%dhist 命令的深度指南

掌握 IPython 歷史的藝術&#xff1a;%dhist 命令的深度指南 在 IPython 的交互式探索中&#xff0c;歷史命令是我們最寶貴的資源之一。%dhist 命令是 IPython 提供的一個強大工具&#xff0c;它允許用戶瀏覽、搜索和重新執行歷史中的命令。本文將深入探討 %dhist 命令的使用方…

【UE5.1】Chaos物理系統基礎——03 炸開幾何體集

目錄 步驟 一、通過徑向向量將幾何體集炸開 二、優化炸開效果——讓破裂的碎塊自然下落 三、優化炸開效果——讓碎塊旋轉起來 四、優化炸開效果——讓碎塊旋轉的越來越慢 步驟 一、通過徑向向量將幾何體集炸開 1. 打開上一篇中&#xff08;【UE5.1】Chaos物理系統基礎—…

Spring IOC基于XML和注解管理Bean

IoC 是 Inversion of Control 的簡寫&#xff0c;譯為“ 控制反轉 ”&#xff0c;它不是一門技術&#xff0c;而是一種設計思想&#xff0c;是一個重要的面向對象編程法則&#xff0c;能夠指導我們如何設計出 松耦合、更優良的程序。 Spring 通過 IoC 容器來管理所有 Java 對象…

如何從 Windows 11/10/8.1/8/7 恢復已刪除的視頻

意外刪除了視頻或格式化了 SD 卡/硬盤&#xff1f;沒有備份已刪除的視頻&#xff1f;別擔心&#xff0c;我們有解決方案來恢復 Windows 11、10 中已刪除的視頻并處理這種糟糕的情況。 但在了解如何恢復已刪除的視頻和視頻恢復應用程序之前&#xff0c;請知道 Windows 會為您提…

ARMv8寄存器詳解

文章目錄 一、ARMv8寄存器介紹二、通用寄存器三、 PSTAE寄存器四、特殊寄存器五、系統寄存器 一、ARMv8寄存器介紹 本文我來給大家介紹一下ARMv8的寄存器部分&#xff0c;ARMv8中有34個寄存器&#xff0c;包括31個通用寄存器、一個棧指針寄存器SP(X31),一個程序計數器寄存器PC…

Apache Drill 2萬字面試題及參考答案

目錄 什么是Apache Drill? Apache Drill的主要特點是什么? Apache Drill如何實現對復雜數據的查詢? 描述Apache Drill的數據存儲模型。 為什么Apache Drill被稱為自服務的SQL查詢引擎? Apache Drill支持哪些類型的數據源? 解釋Apache Drill中的“schema discovery”…

Transformer前置知識:Seq2Seq模型

Seq2Seq model Seq2Seq&#xff08;Sequence to Sequence&#xff09;模型是一類用于將一個序列轉換為另一個序列的深度學習模型&#xff0c;廣泛應用于自然語言處理&#xff08;NLP&#xff09;任務&#xff0c;如機器翻譯、文本摘要、對話生成等。Seq2Seq模型由編碼器&#…

《框架封裝 · 統一異常處理和返回值包裝》

&#x1f4e2; 大家好&#xff0c;我是 【戰神劉玉棟】&#xff0c;有10多年的研發經驗&#xff0c;致力于前后端技術棧的知識沉淀和傳播。 &#x1f497; &#x1f33b; CSDN入駐不久&#xff0c;希望大家多多支持&#xff0c;后續會繼續提升文章質量&#xff0c;絕不濫竽充數…

貪心算法-以高校科研管理系統為例

1.貪心算法介紹 1.算法思路 貪心算法的基本思路是從問題的某一個初始解出發一步一步地進行&#xff0c;根據某個優化測度&#xff0c;每一 步都要確保能獲得局部最優解。每一步只考慮一 個數據&#xff0c;其選取應該滿足局部優化的條件。若下 一個數據和部分最優解連在一起…

JavaEE初階-網絡原理1

文章目錄 前言一、UDP報頭二、UDP校驗和2.1 CRC2.2 md5 前言 學習一個網絡協議&#xff0c;最主要就是學習的報文格式&#xff0c;對于UDP來說&#xff0c;應用層數據到達UDP之后&#xff0c;會給應用層數據報前面加上UDP報頭。 UDP數據報UDP包頭載荷 一、UDP報頭 如上圖UDP的…

Kubernetes(K8s) kubectl 常用命令

文章目錄 一、常用命令1.1 kubectl describe 命令 二、kubectl 命令中的簡寫三、Helm3.1 常用命令&#xff1a;3.2 遇到的問題3.2.1 cannot re-use a name that is still in use 四、Containerd 一、常用命令 檢查 k8s 各節點狀態&#xff0c;確保k8s集群各節點狀態正常&#x…

概率基礎——矩陣正態分布matrix normal distribution

矩陣正態分布-matrix normal distribution 定義性質應用 最近碰到了這個概念&#xff0c;記錄一下 矩陣正態分布是一種推廣的正態分布&#xff0c;它應用于矩陣形式的數據。矩陣正態分布在多維數據分析、貝葉斯統計和機器學習中有廣泛的應用。其定義和性質如下&#xff1a; 定…

Emacs之解決:java-mode占用C-c C-c問題(一百四十六)

簡介&#xff1a; CSDN博客專家&#xff0c;專注Android/Linux系統&#xff0c;分享多mic語音方案、音視頻、編解碼等技術&#xff0c;與大家一起成長&#xff01; 優質專欄&#xff1a;Audio工程師進階系列【原創干貨持續更新中……】&#x1f680; 優質專欄&#xff1a;多媒…

【django項目使用easycython編譯】Cannot convert Unicode string to ‘str‘ implicitly.

django項目編譯遇到的問題 報錯條件 需要編譯的python源碼里面的函數寫了type hint&#xff0c;尤其是return的type hint&#xff0c; 當type hint是str時&#xff0c;但是變量確實f-string格式化后得到的&#xff0c;編譯時會報錯 報錯原因 easycython會檢查變量類型&…