第二十三天:求逆序對

每日一道C++題:

問題:給定一個序列a1,a2,…,an,如果存在i<j并且ai>aj,那么我們稱之為逆序對,求逆序對的數目。

要求:輸入第一行為n,表示序列長度,接下來的n行,第i+1行表示序列中的第i個數;輸出所有逆序對總數。

  1. 暴力解法
    • 思路:通過兩層循環,外層循環遍歷序列中的每一個元素,內層循環遍歷當前元素之后的所有元素。每次內層循環中,如果當前外層循環元素大于內層循環元素,就找到了一個逆序對,計數加1。
#include <iostream>
using namespace std;int main() {int n;cin >> n;int *arr = new int[n];for (int i = 0; i < n; i++) {cin >> arr[i];}int count = 0;for (int i = 0; i < n - 1; i++) {for (int j = i + 1; j < n; j++) {if (arr[i] > arr[j]) {count++;}}}cout << count << endl;delete[] arr;return 0;
}
  • 首先讀取序列長度n,并動態分配一個大小為n的整數數組arr來存儲序列元素。
  • 通過一個for循環讀取序列中的每一個數并存儲到數組中。
  • 使用兩層嵌套的for循環來統計逆序對。外層循環從0到n - 2,內層循環從外層循環變量i的下一個位置到n - 1。如果arr[i] > arr[j],則找到一個逆序對,count加 1。
  • 最后輸出逆序對的總數,并釋放動態分配的數組內存。
  • 時間復雜度:O(n 2 ),因為有兩層嵌套循環,對于長度為n的序列,總的操作次數是1+2+?+(n?1)= n(n?1)/2。
    ?
  • 空間復雜度:O(n),主要用于存儲輸入的序列。
  1. 歸并排序優化解法
    • 思路:歸并排序是一種分治算法,在合并兩個有序子數組的過程中,可以統計跨越兩個子數組的逆序對。將序列不斷地分成兩半,分別對左右兩半進行排序,然后合并兩個有序子數組。在合并過程中,如果左邊子數組的當前元素大于右邊子數組的當前元素,那么左邊子數組中從當前位置到末尾的所有元素都與右邊子數組的當前元素構成逆序對,記錄逆序對的數量并進行合并操作。
#include <iostream>
using namespace std;// 合并兩個有序數組并統計逆序對
long long merge(int arr[], int temp[], int left, int mid, int right) {int i = left; // 左子數組的起始索引int j = mid + 1; // 右子數組的起始索引int k = left; // 臨時數組的起始索引long long inv_count = 0;while (i <= mid && j <= right) {if (arr[i] <= arr[j]) {temp[k++] = arr[i++];} else {temp[k++] = arr[j++];inv_count += (mid - i + 1);}}// 復制左子數組剩余元素while (i <= mid) {temp[k++] = arr[i++];}// 復制右子數組剩余元素while (j <= right) {temp[k++] = arr[j++];}// 將臨時數組的內容復制回原數組for (i = left; i <= right; i++) {arr[i] = temp[i];}return inv_count;
}// 歸并排序主函數并統計逆序對
long long mergeSort(int arr[], int temp[], int left, int right) {long long inv_count = 0;if (left < right) {int mid = (left + right) / 2;// 遞歸地對左半部分和右半部分進行排序inv_count += mergeSort(arr, temp, left, mid);inv_count += mergeSort(arr, temp, mid + 1, right);// 合并兩個有序子數組并統計逆序對inv_count += merge(arr, temp, left, mid, right);}return inv_count;
}int main() {int n;cin >> n;int *arr = new int[n];int *temp = new int[n];for (int i = 0; i < n; i++) {cin >> arr[i];}long long result = mergeSort(arr, temp, 0, n - 1);cout << result << endl;delete[] arr;delete[] temp;return 0;
}
  • merge函數用于合并兩個有序子數組并統計逆序對。left和right分別是當前要合并的子數組的左右邊界,mid是中間位置。通過比較左右子數組的元素,將較小的元素放入臨時數組temp中,并統計逆序對。
  • mergeSort函數是歸并排序的主函數,通過遞歸地將數組分成兩半并排序,然后調用merge函數合并并統計逆序對。
  • 在main函數中,讀取序列長度n,動態分配兩個數組arr和temp,arr用于存儲輸入序列,temp用于輔助合并操作。讀取序列元素后,調用mergeSort函數進行排序并統計逆序對,最后輸出結果并釋放內存。
  • 時間復雜度:O(n log n),因為歸并排序每次將數組分成兩半,共需要(log n)層遞歸,每層遞歸中合并操作的時間復雜度是O(n)。
  • 空間復雜度:O(n),主要用于存儲臨時數組temp。
  1. 應用場景拓展
    -排序算法穩定性分析:逆序對的數量與排序算法的穩定性有關。例如,冒泡排序、插入排序等穩定排序算法在排序過程中可以通過計算逆序對的減少來分析其工作過程。對于不穩定排序算法,如快速排序,了解逆序對的分布有助于優化算法,使其在某些情況下更接近穩定排序的效果。
    • 數據相似性度量:在數據挖掘和機器學習領域,逆序對的概念可以用于衡量兩個序列的相似性。如果兩個序列的逆序對數量較少,說明它們在某種程度上具有相似的順序結構。例如,在推薦系統中,可以通過比較用戶對不同物品的排序(形成序列)之間的逆序對數量,來判斷用戶興趣的相似性,進而為用戶提供更精準的推薦。
  • 算法拓展
    • 樹狀數組解法:可以使用樹狀數組來解決逆序對問題。樹狀數組是一種支持高效單點更新和區間查詢的數據結構。具體做法是,從序列的最后一個元素開始,依次將每個元素插入樹狀數組中,并查詢當前元素之前小于它的元素個數,累加這些個數就得到逆序對的總數。樹狀數組解法的時間復雜度也是 (O(n \log n)),但在某些情況下,其實現可能比歸并排序更簡潔,并且可以靈活地支持動態更新序列并重新計算逆序對。
#include <iostream>
#include <vector>// 樹狀數組類
class FenwickTree {
private:std::vector<int> bit; // 樹狀數組int n; // 數組大小// 計算lowbitint lowbit(int x) {return x & (-x);}public:// 初始化樹狀數組FenwickTree(int size) : n(size), bit(size + 1, 0) {}// 更新操作void update(int idx, int val) {while (idx <= n) {bit[idx] += val;idx += lowbit(idx);}}// 查詢操作int query(int idx) {int sum = 0;while (idx > 0) {sum += bit[idx];idx -= lowbit(idx);}return sum;}
};// 計算逆序對
long long countInversions(const std::vector<int>& arr) {int n = arr.size();// 離散化數組std::vector<int> sortedArr = arr;std::sort(sortedArr.begin(), sortedArr.end());std::vector<int> ranks(n);for (int i = 0; i < n; ++i) {ranks[i] = std::lower_bound(sortedArr.begin(), sortedArr.end(), arr[i]) - sortedArr.begin() + 1;}FenwickTree ft(n);long long invCount = 0;for (int i = n - 1; i >= 0; --i) {invCount += ft.query(ranks[i] - 1);ft.update(ranks[i], 1);}return invCount;
}int main() {int n;std::cin >> n;std::vector<int> arr(n);for (int i = 0; i < n; ++i) {std::cin >> arr[i];}std::cout << countInversions(arr) << std::endl;return 0;
}
  • FenwickTree 類實現了樹狀數組的基本操作,包括初始化、更新和查詢。lowbit 函數用于計算一個數的二進制表示中最低位的 1 及其后面的 0 所構成的數值。

  • countInversions 函數用于計算逆序對。首先對輸入數組進行離散化處理,將數組中的元素映射到 1 到 n 的范圍內,以便樹狀數組處理。然后從數組末尾開始,依次將每個元素的排名插入樹狀數組,并查詢小于該排名的元素個數,累加這些個數得到逆序對的總數。

    • 線段樹解法:線段樹同樣可以用于解決逆序對問題。線段樹是一種二叉樹結構,每個節點代表一個區間。通過在線段樹中插入元素并查詢區間信息,可以統計逆序對。線段樹的優點在于它能夠更靈活地處理區間相關的操作,比如在序列動態變化時,能夠高效地更新逆序對的統計結果。但線段樹的實現相對復雜,需要對其原理有深入理解。
#include <iostream>
#include <vector>
#include <algorithm>// 線段樹節點
struct SegmentTreeNode {int left, right;int count;SegmentTreeNode* leftChild;SegmentTreeNode* rightChild;SegmentTreeNode(int l, int r) : left(l), right(r), count(0), leftChild(nullptr), rightChild(nullptr) {}
};// 構建線段樹
SegmentTreeNode* buildSegmentTree(int left, int right) {SegmentTreeNode* root = new SegmentTreeNode(left, right);if (left < right) {int mid = (left + right) / 2;root->leftChild = buildSegmentTree(left, mid);root->rightChild = buildSegmentTree(mid + 1, right);}return root;
}// 更新線段樹
void updateSegmentTree(SegmentTreeNode* root, int idx) {if (root->left == root->right) {root->count++;return;}int mid = (root->left + root->right) / 2;if (idx <= mid) {updateSegmentTree(root->leftChild, idx);} else {updateSegmentTree(root->rightChild, idx);}root->count = root->leftChild->count + root->rightChild->count;
}// 查詢線段樹
int querySegmentTree(SegmentTreeNode* root, int left, int right) {if (root->left >= left && root->right <= right) {return root->count;}int mid = (root->left + root->right) / 2;if (right <= mid) {return querySegmentTree(root->leftChild, left, right);} else if (left > mid) {return querySegmentTree(root->rightChild, left, right);} else {return querySegmentTree(root->leftChild, left, mid) + querySegmentTree(root->rightChild, mid + 1, right);}
}// 計算逆序對
long long countInversions(const std::vector<int>& arr) {int n = arr.size();// 離散化數組std::vector<int> sortedArr = arr;std::sort(sortedArr.begin(), sortedArr.end());std::vector<int> ranks(n);for (int i = 0; i < n; ++i) {ranks[i] = std::lower_bound(sortedArr.begin(), sortedArr.end(), arr[i]) - sortedArr.begin();}SegmentTreeNode* root = buildSegmentTree(0, n - 1);long long invCount = 0;for (int i = 0; i < n; ++i) {invCount += querySegmentTree(root, ranks[i] + 1, n - 1);updateSegmentTree(root, ranks[i]);}return invCount;
}int main() {int n;std::cin >> n;std::vector<int> arr(n);for (int i = 0; i < n; ++i) {std::cin >> arr[i];}std::cout << countInversions(arr) << std::endl;return 0;
}
  • SegmentTreeNode 結構體定義了線段樹的節點,每個節點包含區間范圍 left 和 right,以及該區間內元素的計數 count,還有左右子節點指針。
  • buildSegmentTree 函數用于構建線段樹,遞歸地將區間劃分為左右子區間,并創建相應的節點。
  • updateSegmentTree 函數用于更新線段樹,當插入一個新元素時,根據元素的位置更新相應節點的計數。
  • querySegmentTree 函數用于查詢線段樹中指定區間內的元素計數。
  • countInversions 函數首先對輸入數組進行離散化處理,然后從數組開頭開始,依次將每個元素插入線段樹,并查詢大于該元素的元素個數,累加這些個數得到逆序對的總數。

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

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

相關文章

Java—CompletableFuture 詳解

參考&#xff1a; CompletableFuture原理與實踐-外賣商家端API的異步化 - 美團技術團隊 CompletableFuture 詳解 | JavaGuide 1.CompletableFuture介紹 CompletableFuture是由Java 8引入的&#xff0c;在Java8之前我們一般通過Future實現異步。 Future用于表示異步計算的結…

大模型部署基礎設施搭建 - 向量數據庫milvus

一、docker方式安裝參考官網&#xff1a;https://milvus.io/docs/zh/install_standalone-docker.md#Install-Milvus-in-Docker1.1 安裝 curl -sfL https://raw.githubusercontent.com/milvus-io/milvus/master/scripts/standalone_embed.sh -o standalone_embed.shbash standal…

(25.08)Ubuntu20.04復現KISS-ICP

主頁&#xff1a;https://github.com/PRBonn/kiss-icp?tabreadme-ov-file 倉庫&#xff1a;https://github.com/PRBonn/kiss-icp.git 非 ROS 使用流程 1. 克隆倉庫 git clone https://github.com/PRBonn/kiss-icp.git cd kiss-icp 2. 使用 micromamba 創建 Python 虛擬環…

linux 軟硬鏈接詳解

一、核心區別總覽特性硬鏈接&#xff08;Hard Link&#xff09;軟鏈接&#xff08;Symbolic Link&#xff09;本質直接指向文件的 inode&#xff08;數據塊的入口地址&#xff09;指向文件的 路徑名&#xff08;相當于快捷方式&#xff09;跨文件系統支持? 僅限同一文件系統?…

基于SpringBoot+Vue的房屋匹配系統(WebSocket實時通訊、協同過濾算法、地圖API、Echarts圖形化分析)

&#x1f388;系統亮點&#xff1a;WebSocket實時通訊、協同過濾算法、地圖API、Echarts圖形化分析&#xff1b;一.系統開發工具與環境搭建1.系統設計開發工具后端使用Java編程語言的Spring boot框架 項目架構&#xff1a;B/S架構 運行環境&#xff1a;win10/win11、jdk17前端&…

第2節:多模態的核心問題(多模態大模型基礎教程)

前言 本節課我們聚焦多模態大模型最核心的問題&#xff1a;文本、圖像、語音這些“不同語言”的信息&#xff0c;是怎么被模型“翻譯”并互相理解的&#xff1f;我們從“差異”入手&#xff0c;一步步搞懂其中的邏輯。 一、先搞懂&#xff1a;什么是“模態差異”&#xff1f; 生…

Java stream distinct findAny anyMatch實現 :DistinctOp、FindOp、MatchOp

DistinctOpsDistinctOps 是一個專門用于實現 Stream.distinct() 操作的工廠類。正如它的名字所示&#xff0c;它的核心職責就是創建能夠去除流中重復元素的操作。distinct() 是一個有狀態的中間操作 (stateful intermediate operation)&#xff0c;這意味著它通常需要看到所有元…

鎖的基本介紹

鎖 并發編程的一個最基本問題就是原子性地執行一系列指令。鎖有助于直接解決這一問題。 鎖的基本思想 鎖就是一個變量。這個變量保存了鎖在某一時刻的狀態。它要么是可用的&#xff0c;表示沒有線程持有鎖&#xff0c;要么是被占用的&#xff0c;表示有線程持有鎖&#xff0c;正…

【讀代碼】開源流式語音編碼器SecoustiCodec

引言:從LLM到深度語義 在大型語言模型(LLM)驅動的語音交互時代,神經語音編解碼器 (Neural Speech Codec) 扮演著至關重要的角色。它如同 LLM 的“耳朵”和“嘴巴”,負責將連續的語音波形轉換為離散的、可供模型處理的 token,并將模型生成的 token 還原為自然的人聲。 一…

P5967 [POI 2016] Korale 題解

P5967 [POI 2016] Korale 題目描述 有 nnn 個帶標號的珠子&#xff0c;第 iii 個珠子的價值為 aia_iai?。 現在你可以選擇若干個珠子組成項鏈&#xff08;也可以一個都不選&#xff09;&#xff0c;項鏈的價值為所有珠子的價值和。 給出所有可能的項鏈排序&#xff0c;先按…

SwiftUI 頁面彈窗操作

SwiftUI 頁面彈窗操作指南一、基礎彈窗實現1. Alert 基礎警告框2. ActionSheet 操作菜單3. Sheet 模態視圖4. Popover 浮動視圖二、高級自定義彈窗1. 自定義彈窗組件2. 使用自定義彈窗三、彈窗狀態管理1. 使用環境對象管理彈窗2. 彈窗路由系統四、動畫與過渡效果1. 自定義彈窗動…

OpenCV圖像處理2:邊界填充與平滑濾波實戰

前面學了一些關于opencv圖像處理的內容&#xff0c;現在繼續。一 圖像填充邊界填充&#xff08;Border Padding&#xff09;?&#xff0c;即在圖像四周添加指定寬度的像素區域。其核心函數是cv2.copyMakeBorder()&#xff0c;通過不同的填充方式&#xff08;borderType&#x…

imx6ull-驅動開發篇22——Linux 時間管理和內核定時器

目錄 內核時間管理 系統節拍率 高/低節拍率的優缺點 jiffies 節拍數 時間繞回 時間轉換函數 內核定時器 timer_list 結構體 定時器API函數 init_timer 函數 add_timer 函數 del_timer 函數 del_timer_sync 函數 mod_timer 函數 Linux 內核短延時函數 內核時間管…

路由器數據控制管理層面安全

數據層面&#xff1a;FPM Flexible Packet MatchingFPM是CisCOIOS新一代的ACL根據任意條件&#xff0c;無無狀態的匹配數據包的頭部負載&#xff0c;或者全部分析協議&#xff0c;更易于規則的創建用于替代傳統ACL&#xff0c;對特定惡意流量的基礎架構過濾無狀態ipv4單播不支持…

Vue內置組件全解析:從入門到面試通關

文章目錄Vue內置組件全解析&#xff1a;從入門到面試通關引言&#xff1a;為什么需要內置組件&#xff1f;一、Vue內置組件全景圖二、核心內置組件詳解1. <component> - 動態組件2. <transition> - 過渡動畫3. <keep-alive> - 組件緩存4. <slot> - 內容…

VUE+SPRINGBOOT從0-1打造前后端-前后臺系統-會議記錄

在當今快節奏的工作環境中&#xff0c;會議記錄是每個職場人士都必須要面對的任務。傳統的手動記錄方式不僅效率低下&#xff0c;而且容易遺漏重要信息。隨著Web技術的發展&#xff0c;基于瀏覽器的實時語音轉寫技術為會議記錄提供了全新的解決方案。本文將詳細介紹如何利用Web…

WEB3——水龍頭,如何獲得開發用的測試幣、 Sepolia 測試幣?

注意&#xff1a; 有些水龍頭渠道&#xff0c;要求以太坊幣至少有0.01ETH,設有這個門檻&#xff0c;下面并不是所有渠道都能領取到測試幣&#xff0c;有些可能對領取測試幣有要求&#xff0c;如果想獲得獲取以太坊幣的方法&#xff0c;可以看我其他的文章。 本文整理了多個免費…

C++調試革命:時間旅行調試實戰指南

還在為C的懸垂指針、內存泄漏和并發競態抓狂&#xff1f;讓調試器學會“時光倒流” 凌晨三點&#xff0c;std::thread創建的六個線程中有一個突然吞掉了你的數據&#xff0c;valgrind只告訴你“Invalid read”&#xff0c;而時間旅行調試&#xff08;TTD&#xff09;?? 能讓你…

mysql8.0筆記

1.DDL數據定義語言 DDL是什么——————創建、修改、刪除 數據庫和表結構的命令。 基本語法 針對數據庫的操作 -- 創建數據庫 CREATE DATABASE 數據庫名; -- 比如 CREATE DATABASE myschool; --查看所有數據庫 SHOW DATABASES; --使用某個數據庫 USE myschool; -- 刪除數據庫…

大模型微調【1】之入門

文章目錄說明一 大模型微調技術1.1 微調基礎1.2 量化概念1.3 高效微調方法LoRA&QLoRA1.4 LoRA VS QLoRA1.5 高效微調的應用場景二 主流微調工具2.1 unsloth2.2 LLama-Factory2.3 ms-SWIFT2.4 ColossalAI2.5 底層微調框架推薦2.6 模型性能評估框架EvalScope三 微調所需軟硬件…