归并排序用到了分治的思想,其基本步骤如下:
mid
,将原排序问题分解成两个子问题left
和right
left
和right
left
和right
合并,得到最终排好序的结果【归并排序求解的核心】合并过程:每次取左右区间当前最小的数进行比较,较小的数加入临时数组中。左右区间的数都遍历完毕,再将临时数组的元素按顺序拷贝回原数组。
#include
#include
using namespace std;
const int N = 100010;
int a[N], temp[N];
void mergeSort(int l, int r)
{
if(l >= r) return ;
// 分
int mid = l + r >> 1;
// 治
mergeSort(l, mid);
mergeSort(mid + 1, r);
// 合并
int i = l, j = mid + 1, k = 0;
// 合并结果先存在临时数组中
while(i <= mid && j <= r) {
if(a[i] <= a[j]) temp[k++] = a[i++];
else temp[k++] = a[j++];
}
while(i <= mid) temp[k++] = a[i++];
while(j <= r) temp[k++] = a[j++];
for(int i = l, j = 0; i <= r; i++, j++) {
a[i] = temp[j];
}
}
int main()
{
int n;
cin >> n;
for(int i = 0; i < n; i++) {
cin >> a[i];
}
mergeSort(0, n - 1);
for(int i = 0; i < n; i++) {
cout << a[i] << " ";
}
cout << endl;
return 0;
}
时间复杂度为 O ( n l o g n ) O(nlogn) O(nlogn) 级别
原题链接
猫猫 TOM 和小老鼠 JERRY 最近又较量上了,但是毕竟都是成年人,他们已经不喜欢再玩那种你追我赶的游戏,现在他们喜欢玩统计。
最近,TOM 老猫查阅到一个人类称之为“逆序对”的东西,这东西是这样定义的:对于给定的一段正整数序列,逆序对就是序列中 a i > a j a_i>a_j ai>aj 且 i < j i
第一行,一个数 n n n,表示序列中有 n n n个数。
第二行 n n n 个数,表示给定的序列。序列中每个数字不超过 1 0 9 10^9 109。
输出序列中逆序对的数目。
对于这道题,可以采用归并排序的分治思路进行求解:
i
遍历左区间,j
遍历右区间。若a[i] > a[j]
,则说明a[i...mid]
均与a[j]
构成逆序对,因此逆序对个数为res += (mid - i + 1)
。#include
using namespace std;
typedef long long LL;
const int N = 500010;
int n;
int a[N], tmp[N];
LL mergeSort(int l, int r)
{
if(l >= r) return 0;
// 分
int mid = l + r >> 1;
// 治:分别计算两个区间的逆序数并求和
LL res = mergeSort(l, mid) + mergeSort(mid + 1, r);
// 合并:计算跨区间的逆序数
int i = l, j = mid + 1, k = 0;
while( i <= mid && j <= r ) {
if(a[i] <= a[j]) tmp[k++] = a[i++];
else {
tmp[k++] = a[j++];
// 两个区间,与q[j]构成逆序对的个数
res += mid - i + 1;
}
}
while( i <= mid ) tmp[k++] = a[i++];
while( j <= r ) tmp[k++] = a[j++];
for(int i = l, j = 0; i <= r; i++, j++) {
a[i] = tmp[j];
}
return res;
}
int main()
{
cin >> n;
for(int i = 0; i < n; i++) {
cin >> a[i];
}
cout << mergeSort(0, n - 1) << endl;
return 0;
}
时间复杂度为 O ( n l o g n ) O(nlogn) O(nlogn) 级别
原题链接
给你一个整数数组 nums
,按要求返回一个新数组 counts
。数组 counts
有该性质: counts[i]
的值是 nums[i]
右侧小于 nums[i]
的元素的数量。
计算nums[i]
右侧小于nums[i]
的元素的数量,其实就是计算与nums[i]
构成逆序对的个数。
由于需要按照原数组的顺序,返回每个元素右侧小于该元素的数量,因此可以用一个pair
数据结构存储元素值和原下标。
i
遍历左区间,j
遍历右区间,用t
记录nums[i...mid] > nums[j]
的对数。
nums[i] <= nums[j]
,记nums[i]
的原位置为pos
,更新counts[pos]
的值,即counts[pos] += t
.nums[i] > nums[j]
,则说明 nums[i...mid]
均大于nums[j]
,因此t++
。class Solution {
private:
void mergeSort(vector<pair<int, int>>& a, vector<int>& res, int l, int r) {
if(l >= r) return ;
int mid = l + r >> 1;
mergeSort(a, res, l, mid);
mergeSort(a, res, mid + 1, r);
int i = l, j = mid + 1, k = 0;
vector<pair<int, int>> tmp(r - l + 1);
int t = 0;
while( i <= mid && j <= r ) {
if(a[i].first <= a[j].first) {
res[a[i].second] += t;
tmp[k++] = a[i++];
}
else {
t++;
tmp[k++] = a[j++];
}
}
while( i <= mid ) {
res[a[i].second] += t;
tmp[k++] = a[i++];
}
while( j <= r ) tmp[k++] = a[j++];
for(int i = l, j = 0; i <= r; i++, j++) {
a[i] = tmp[j];
}
}
public:
vector<int> countSmaller(vector<int>& nums) {
int n = nums.size();
vector<pair<int, int>> a(n);
for(int i = 0; i < n; i++) {
a[i] = {nums[i], i};
}
vector<int> res(n, 0);
mergeSort(a, res, 0, n - 1);
return res;
}
};
时间复杂度为 O ( n l o g n ) O(nlogn) O(nlogn) 级别