目录
1、912. 排序数组
2、LCR 170. 交易逆序对的总数
3、315. 计算右侧小于当前元素的个数
4、493. 翻转对
1、912. 排序数组
思路:本次使用归并排序 ,快速排序相当于二叉树的前序遍历,而归并排序相当于后序遍历。
- 归并排序是一种有效的排序算法,采用分治策略,将问题分解成一系列更小的问题,然后解决这些小问题,并将解决方案合并以产生原始问题的解决方案。下面是对这个过程的逐步解释。
归并排序的工作原理
归并排序通过递归地将数组分成越来越小的部分,直到每个部分只有一个元素(自然是有序的),然后将这些有序的部分合并成较大的有序部分,直至整个数组排序完成。这个过程中,合并操作是通过比较来自两个不同部分的元素并选择较小的那个来实现的,这保证了合并后的数组也是有序的。
性能
归并排序的时间复杂度为O(n log n),其中n是数组的长度。这是因为每一层递归操作都需要遍历整个数组(O(n)),而递归树的深度为log n。归并排序需要额外的存储空间来存储临时数组,因此它的空间复杂度为O(n)。
class Solution {
public:
vector<int> tmp;
vector<int> sortArray(vector<int>& nums) {
tmp.resize(nums.size());
mergeSort(nums, 0, nums.size() - 1);
return nums;
}
void mergeSort(vector<int>& nums, int left, int right) {
if (left >= right)
return;
int mid = left + ((right - left) >> 1);
mergeSort(nums, left, mid);
mergeSort(nums, mid + 1, right);
int cur1 = left, cur2 = mid + 1, i = 0;
while (cur1 <= mid && cur2 <= right)
tmp[i++] = nums[cur1] <= nums[cur2] ? nums[cur1++] : 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 - left];
}
};
vector<int> tmp;
:一个临时向量,用于归并排序过程中的合并操作。-
vector<int> sortArray(vector<int>& nums)
:- 接受一个整数向量
nums
作为输入,并返回排序后的向量。 tmp.resize(nums.size());
:首先,将临时向量tmp
的大小调整为与输入向量nums
相同,确保有足够的空间进行合并操作。mergeSort(nums, 0, nums.size() - 1);
:调用mergeSort
方法对整个数组进行排序,传入的参数是数组的左右边界索引。
- 接受一个整数向量
-
void mergeSort(vector<int>& nums, int left, int right)
:- 这是一个递归方法,用于对数组
nums
从索引left
到right
进行排序。 - 如果
left
大于等于right
,表示区间内最多只有一个元素,不需要排序,直接返回。 - 计算中点
mid
,将数组分为两部分:left
到mid
和mid + 1
到right
。 - 递归调用
mergeSort
分别对这两部分进行排序。 - 使用三个指针
cur1
、cur2
和i
,分别指向左半部分的起始位置、右半部分的起始位置和临时数组tmp
的起始位置。 - 通过比较
cur1
和cur2
所指向的元素,选择较小的元素放入tmp
中,并移动相应的指针和i
。 - 如果左半部分或右半部分有剩余元素,将它们复制到
tmp
中。 - 将临时数组
tmp
中的元素复制回原数组nums
的相应位置。
- 这是一个递归方法,用于对数组
2、LCR 170. 交易逆序对的总数
思路:采用分支策略,使用归并排序处理。利用归并排序先在左区间统计逆序对个数,再统计右区间,左右区间统计完回到上层,左右区间作为左区间继续递归统计,递归排序既可以排升序也可以排降序,都可以解决本题。
升序: 找出该数之前有多少个比它大的
class Solution {
public:
int tmp[50010];
int reversePairs(vector<int>& record) {
return mergeSort(record, 0, record.size() - 1);
}
int mergeSort(vector<int>& record, int left, int right) {
if (left >= right)
return 0;
int mid = left + ((right - left) >> 1);
int ret = 0;
ret += mergeSort(record, left, mid);
ret += mergeSort(record, mid + 1, right);
int cur1 = left, cur2 = mid + 1, i = 0;
while (cur1 <= mid && cur2 <= right) {
if (record[cur1] <= record[cur2]) {
tmp[i++] = record[cur1++];
} else {
ret += mid - cur1 + 1;
tmp[i++] = record[cur2++];
}
}
while (cur1 <= mid) {
tmp[i++] = record[cur1++];
}
while (cur2 <= right) {
tmp[i++] = record[cur2++];
}
for (int i = left; i <= right; i++) {
record[i] = tmp[i - left];
}
return ret;
}
};
降序:找出该数之前有多少个比它小的
class Solution {
public:
int tmp[50010];
int reversePairs(vector<int>& record) {
return mergeSort(record, 0, record.size() - 1);
}
int mergeSort(vector<int>& record, int left, int right) {
if (left >= right)
return 0;
int mid = left + ((right - left) >> 1);
int ret = 0;
ret += mergeSort(record, left, mid);
ret += mergeSort(record, mid + 1, right);
int cur1 = left, cur2 = mid + 1, i = 0;
while (cur1 <= mid && cur2 <= right) {
if (record[cur1] <= record[cur2]) {
tmp[i++] = record[cur2++];
} else {
ret += right - cur2 + 1;
tmp[i++] = record[cur1++];
}
}
while (cur1 <= mid) {
tmp[i++] = record[cur1++];
}
while (cur2 <= right) {
tmp[i++] = record[cur2++];
}
for (int i = left; i <= right; i++) {
record[i] = tmp[i - left];
}
return ret;
}
};
3、 315. 计算右侧小于当前元素的个数
思路:采用分支策略,使用归并排序处理。但是在我们归并排序的过程中,元素的下标是会跟着变化的,因此我们需要⼀个辅助数组,来将数组元素和对应的下标绑定在⼀起归并,也就是再归并元素的时候,顺势将下标也转移到对应的位置上。
class Solution {
public:
vector<int> index;
vector<int> ret;
int tmpNums[500000];
int tmpIndex[500000];
vector<int> countSmaller(vector<int>& nums) {
int n = nums.size();
ret.resize(n);
index.resize(n);
for (int i = 0; i < n; i++) {
index[i] = i;
}
mergeSort(nums, 0, n - 1);
return ret;
}
void mergeSort(vector<int>& nums, int left, int right) {
if (left >= right)
return;
int mid = left + ((right - left) >> 1);
mergeSort(nums, left, mid);
mergeSort(nums, mid + 1, right);
int cur1 = left, cur2 = mid + 1, i = 0;
while (cur1 <= mid && cur2 <= right) {
if (nums[cur1] <= nums[cur2]) {
tmpNums[i] = nums[cur2];
tmpIndex[i++] = index[cur2++];
} else {
ret[index[cur1]] += right - cur2 + 1;
tmpNums[i] = nums[cur1];
tmpIndex[i++] = index[cur1++];
}
}
while (cur1 <= mid) {
tmpNums[i] = nums[cur1];
tmpIndex[i++] = index[cur1++];
}
while (cur2 <= right) {
tmpNums[i] = nums[cur2];
tmpIndex[i++] = index[cur2++];
}
for (int j = left; j <= right; j++) {
nums[j] = tmpNums[j - left];
index[j] = tmpIndex[j - left];
}
}
};
类成员解释
vector<int> index;
:用于存储每个元素在原数组中的索引。vector<int> ret;
:用于存储最终的结果,即每个元素右侧小于它的元素数量。int tmpNums[500000];
:临时数组,用于归并排序过程中存储中间结果。int tmpIndex[500000];
:临时数组,用于存储归并排序过程中元素的索引。
方法解释
-
vector<int> countSmaller(vector<int>& nums)
:这是主方法,用于初始化并开始归并排序过程。- 初始化
ret
和index
数组。 - 调用
mergeSort
方法进行归并排序。
- 初始化
-
void mergeSort(vector<int>& nums, int left, int right)
:这是归并排序的实现方法。- 如果
left
大于等于right
,则不需要排序,直接返回。 - 计算中点
mid
,然后递归地对左半部分和右半部分进行归并排序。 - 接下来是归并过程,其中比较关键的是在比较左右两部分的元素时,如果左边的元素大于右边的元素,那么意味着左边的这个元素大于右边所有未被合并的元素,因此
ret[index[cur1]]
需要加上右边剩余元素的数量(right - cur2 + 1
)。 - 最后,将排序好的部分复制回原数组
nums
和索引数组index
。
- 如果
4、493. 翻转对
思路:采用分支策略,使用归并排序处理。
降序—找元素后面小的
class Solution {
public:
int tmp[50001];
int reversePairs(vector<int>& nums) {
return mergeSort(nums, 0, nums.size() - 1);
}
int mergeSort(vector<int>& nums, int left, int right) {
if (left >= right)
return 0;
int ret = 0;
int mid = left + ((right - left) >> 1);
ret += mergeSort(nums, left, mid);
ret += mergeSort(nums, mid + 1, right);
int cur1 = left, cur2 = mid + 1, i = left;
while (cur1 <= mid) {
while (cur2 <= right && nums[cur2] >= nums[cur1] / 2.0) {
cur2++;
}
if (cur2 > right)
break;
ret += right - cur2 + 1;
cur1++;
}
cur1 = left, cur2 = mid + 1;
while (cur1 <= mid && cur2 <= right) {
tmp[i++] = nums[cur1] < nums[cur2] ? nums[cur2++] : nums[cur1++];
}
while (cur1 <= mid) {
tmp[i++] = nums[cur1++];
}
while (cur2 <= right) {
tmp[i++] = nums[cur2++];
}
for (int j = left; j <= right; j++) {
nums[j] = tmp[j];
}
return ret;
}
};
int reversePairs(vector<int>& nums)
:这是主方法,用于开始归并排序过程,并返回重要翻转对的数量。int mergeSort(vector<int>& nums, int left, int right)
:这是归并排序的实现方法,同时也是计算重要翻转对的核心方法。- 如果
left
大于等于right
,则不需要处理,直接返回0。 - 通过递归调用
mergeSort
方法,分别对数组的左半部分和右半部分进行排序,并计算左半部分和右半部分内部各自的重要翻转对数量,累加到ret
。 - 在合并两个已排序的部分之前,先计算跨越左右部分的重要翻转对数量。这是通过两个指针
cur1
和cur2
实现的,cur1
遍历左半部分,cur2
遍历右半部分,当nums[cur2] >= nums[cur1] / 2.0
时,cur2
向右移动,直到找到第一个使得nums[i] > 2*nums[j]
的j
,此时right - cur2 + 1
即为对于当前cur1
指向的元素,满足条件的翻转对数量。 - 然后,进行正常的归并排序合并操作,将两个部分合并为一个有序数组。
- 最后,将临时数组
tmp
中的排序结果复制回原数组nums
。
- 如果
升序—找元素前面大的
class Solution {
public:
int tmp[50001];
int reversePairs(vector<int>& nums) {
return mergeSort(nums, 0, nums.size() - 1);
}
int mergeSort(vector<int>& nums, int left, int right) {
if (left >= right)
return 0;
int ret = 0;
int mid = left + ((right - left) >> 1);
ret += mergeSort(nums, left, mid);
ret += mergeSort(nums, mid + 1, right);
int cur1 = left, cur2 = mid + 1, i = left;
while (cur2 <= right) {
while (cur1 <= mid && nums[cur1] / 2.0 <= nums[cur2]) {
cur1++;
}
if (cur1 > right)
break;
ret += mid - cur1 + 1;
cur2++;
}
cur1 = left, cur2 = mid + 1;
while (cur1 <= mid && cur2 <= right) {
tmp[i++] = nums[cur1] < nums[cur2] ? nums[cur1++] : nums[cur2++];
}
while (cur1 <= mid) {
tmp[i++] = nums[cur1++];
}
while (cur2 <= right) {
tmp[i++] = nums[cur2++];
}
for (int j = left; j <= right; j++) {
nums[j] = tmp[j];
}
return ret;
}
};