本片文章旨在记录求逆序对数目的算法题,但涉及到之前写过另一道求逆序数的Leetcode算法题,则第一部分为之前写过的LeetCode [315],第二部分为求逆序对数目题目。(第二部分其实为第一部分改动而来,原理在第一部分介绍)
一、LeetCode [315] 计算右侧小于当前元素的个数(求逆序数)
题目描述
给定一个整数数组 nums,按要求返回一个新数组 counts。数组 counts 有该性质: counts[i] 的值是 nums[i] 右侧小于 nums[i] 的元素的数量。
示例
输入:nums = [5,2,6,1]
输出:[2,1,1,0]
解释:
5 的右侧有 2 个更小的元素 (2 和 1)
2 的右侧仅有 1 个更小的元素 (1)
6 的右侧有 1 个更小的元素 (1)
1 的右侧有 0 个更小的元素
算法思路
分治法的归并排序
这道题的重要思路是由归并排序扩展开的,先补充一下归并排序
#include<iostream>
#include<vector>
using namespace std;
// 把两个已排序的数组有序合成
void merge_sort_two_vec(vector<int>& sub_vec1, vector<int>& sub_vec2, vector<int>& vec) {
int i = 0;
int j = 0;
while (i < sub_vec1.size() && j < sub_vec2.size())
{
if (sub_vec1[i] <= sub_vec2[j]) {
vec.push_back(sub_vec1[i]);
i++;
}
else
{
vec.push_back(sub_vec2[j]);
j++;
}
}
for (; i < sub_vec1.size(); i++)
{
vec.push_back(sub_vec1[i]);
}
for (; j < sub_vec2.size(); j++)
{
vec.push_back(sub_vec2[j]);
}
}
// 归并排序
void merge_sort(vector<int>& vec) {
// 只有一个数则直接返回
// 即在递归中把问题分解到足够小时
if (vec.size() < 2) {
return;
}
int mid = vec.size() / 2;
vector<int> sub_vec1;
vector<int> sub_vec2;
for (int i = 0; i < mid; i++)
{
sub_vec1.push_back(vec[i]);
}
for (int i = mid; i < vec.size(); i++) {
sub_vec2.push_back(vec[i]);
}
merge_sort(sub_vec1);
merge_sort(sub_vec2);
vec.clear();
merge_sort_two_vec(sub_vec1, sub_vec2, vec);
}
int main()
{
int nums[] = { -3,9,4,10,23,5,11,-6,21,-7 };
vector<int> vec;
for (int i = 0; i < 10; i++)
{
vec.push_back(nums[i]);
}
merge_sort(vec);
for (int i = 0; i < 10; i++)
{
cout << vec[i] << " ";
}
return 0;
}
算法思路
图片来自小象学院教程
每次有两个已经排序好的两个数组,按上面归并排序第一个函数(把两个已排序的数组有序合成)的算法加入新的数组,而其中第一个数组中的count即为j的值(i为第一个数组的索引,j为第二个数组的索引)。
算法代码
class Solution {
public:
vector<int> countSmaller(vector<int>& nums) {
vector<pair<int, int>> vec;
vector<int> count;
for (int i = 0; i < nums.size(); i++)
{
vec.push_back(make_pair(nums[i], i));
count.push_back(0); // 将nums[i]与 i 绑定为 pair
}
merge_sort(vec, count);
return count;
}
private:
void merge_sort_two_vec(
vector<pair<int, int>>& sub_vec1,
vector<pair<int, int>>& sub_vec2,
vector<pair<int, int>>& vec,
vector<int>& count
) {
int i = 0;
int j = 0;
while (i < sub_vec1.size() && j < sub_vec2.size())
{
if (sub_vec1[i].first <= sub_vec2[j].first) { // 比较用pair的第一个元素
count[sub_vec1[i].second] += j; // 第二个元素缩影用来记录count
vec.push_back(sub_vec1[i]);
i++;
}
else
{
vec.push_back(sub_vec2[j]);
j++;
}
}
for (; i < sub_vec1.size(); i++)
{
count[sub_vec1[i].second] += j;
vec.push_back(sub_vec1[i]);
}
for (; j < sub_vec2.size(); j++)
{
vec.push_back(sub_vec2[j]);
}
}
void merge_sort(vector<pair<int, int>>& vec, vector<int>& count) {
// 只有一个数则直接返回
// 即在递归中把问题分解到足够小时
if (vec.size() < 2) {
return;
}
int mid = vec.size() / 2;
vector<pair<int, int>> sub_vec1;
vector<pair<int, int>> sub_vec2;
for (int i = 0; i < mid; i++)
{
sub_vec1.push_back(vec[i]);
}
for (int i = mid; i < vec.size(); i++) {
sub_vec2.push_back(vec[i]);
}
merge_sort(sub_vec1,count);
merge_sort(sub_vec2, count);
vec.clear();
merge_sort_two_vec(sub_vec1, sub_vec2, vec, count);
}
};
ps: 小象学院教程 https://www.bilibili.com/video/BV1GW411Q77S?t=7029&p=2
二、求逆序对数目
统计一个数组中的“逆序对”的数目
给定一个数组,使用分治法,统计数组内的逆序对的数目。
逆序对是指,数组内序号较小位置的元素数值大于序号较大位置的元素数值
例如输入 3 5 2 4 6
输出 3
因为 3 2 构成了一个逆序对; 5 2构成一个逆序对; 5 4 构成一个逆序对
所以总共有3个。
分治算法思路
基于归并排序算法,
普通的归并排序中设计两个函数,第一个是对两个有序的数组进行有序合并;第二个是对整个数组做排序。大致操作是将一个数组分为两部分,然后把这两部分进行调用自身的排序,然后将这两个数组有序合并,即为分治的递归算法。
求逆序对数目算法思路如下:
- 在普通的归并排序算法中,在合并两个有序数组(vec1,vec2)时,我们设定两个索引值i和j,i索引第一个数组,j索引第二个数组,定义一个count记录逆序对个数;
- 在我们的合并数组函数中,有一个操作是将vec1[i] 和 vec2[j] 中将小的数放入一个新的合并最终数组里,在这个时候如果是vec1[i] <= vec2[j],下一步要把vec的元素加入新合并数组时,此时的索引j即为比vec1[i]这个数小但排在其后面的数的个数,即我们所求的逆序对个数,将其累积到count,归并完全后就能得到总的逆序对个数count。
代码:
#include<iostream>
#include<vector>
using namespace std;
class Solution {
public:
int reversePairs(vector<int>& nums) {
int count = 0;
merge_sort(nums, count);
return count;
}
private:
// 有序合并两个有序数组
void merge_sort_two_vec(
vector<int>& sub_vec1,
vector<int>& sub_vec2,
vector<int>& vec,
int& count
) {
int i = 0;
int j = 0;
while (i < sub_vec1.size() && j < sub_vec2.size())
{
if (sub_vec1[i] <= sub_vec2[j]) {
count += j; // 每次放入 sub_vec1[i] 记录count
vec.push_back(sub_vec1[i]);
i++;
}
else
{
vec.push_back(sub_vec2[j]);
j++;
}
}
for (; i < sub_vec1.size(); i++)
{
count += j;
vec.push_back(sub_vec1[i]);
}
for (; j < sub_vec2.size(); j++)
{
vec.push_back(sub_vec2[j]);
}
}
void merge_sort(vector<int>& vec, int& count) {
// 只有一个数则直接返回
// 即在递归中把问题分解到足够小时
if (vec.size() < 2) {
return;
}
int mid = vec.size() / 2;
vector<int> sub_vec1;
vector<int> sub_vec2;
for (int i = 0; i < mid; i++)
{
sub_vec1.push_back(vec[i]);
}
for (int i = mid; i < vec.size(); i++) {
sub_vec2.push_back(vec[i]);
}
merge_sort(sub_vec1, count);
merge_sort(sub_vec2, count);
vec.clear();
merge_sort_two_vec(sub_vec1, sub_vec2, vec, count);
}
};
int main()
{
Solution so;
vector<int> nums;
int temp;
do
{
cin >> temp;
nums.push_back(temp);
} while (getchar() != '\n');
cout << so.reversePairs(nums);
}