剑指OFFER 数组中的逆序对
剑指OFFER 数组中的逆序对
题目描述
在数组中的两个数字,如果前面一个数字大于后面的数字,则这两个数字组成一个逆序对。输入一个数组,求出这个数组中的逆序对的总数P。并将P对1000000007取模的结果输出。
即输出P%1000000007
解题思考记录
初看这道题,马上想到了暴力解法,就是一个一个去比较(类似冒泡排序),复杂度O(n^2)
然后第二次思考,想到了动态规划,每次找逆序对的时候利用上次的结果,后面贴有代码,可以感觉到比暴力解法稍微好了一点,但复杂度仍然是O(n^2)的,卡在了75%通不过了,测试数据比较大
然后实在没办法了,上网看题解,决定使用归并的方法来解决,
归并解法
在写归并的接法之前,我先把归并排序手写了一遍,很久没有写归并排序了,先热一下身,回顾一下
归并排序
#include <iostream>
#include <vector>
using namespace std;
vector<int> data;
void merge(int left1,int right1,int left2,int right2)
{
vector<int> tmp;
int cur1=left1,cur2=left2;
//类似这种比较头结点的方式,都先同步后异步,易于理解和编写
while(cur1<=right1 && cur2<=right2)
{
if(data[cur1]<data[cur2])
{
tmp.push_back(data[cur1++]);
}else{
tmp.push_back(data[cur2++]);
}
}
while(cur1<=right1){
tmp.push_back(data[cur1++]);
}
while(cur2<=right2){
tmp.push_back(data[cur2++]);
}
int size = tmp.size();
for(int i=0;i<size;i++){
data[left1+i] = tmp[i];
}
}
void recur(int left,int right)
{
if(left>=right)return;
int mid = (left+right)/2;
recur(left,mid);
recur(mid+1,right);
merge(left,mid,mid+1,right);
}
int main() {
data = vector<int> {3,4,5,2,1,8,9,10,0,-1,-2};
int size = data.size();
recur(0,size-1);
for(int i=0;i<size;i++){
cout<<data[i]<<" ";
}
cout<<endl;
return 0;
}
题解
根据归并排序的代码稍加修改,就可以应用到本题中
class Solution {
public:
int count = 0;
void merge(vector<int>& data,int left1,int right1,int left2,int right2)
{
vector<int> tmp;
int cur1=left1,cur2=left2;
//类似这种比较头结点的方式,都先同步后异步,易于理解和编写
while(cur1<=right1 && cur2<=right2)
{
if(data[cur1]<=data[cur2])
{
tmp.push_back(data[cur1++]);
}else{
count += right1-cur1+1;//核心是要理解这一句
count %= 1000000007;
tmp.push_back(data[cur2++]);
}
}
while(cur1<=right1){
tmp.push_back(data[cur1++]);
}
while(cur2<=right2){
tmp.push_back(data[cur2++]);
}
int size = tmp.size();
for(int i=0;i<size;i++){
data[left1+i] = tmp[i];
}
}
void recur(vector<int> &data,int left,int right)
{
if(left>=right)return;
int mid = (left+right)/2;
recur(data,left,mid);
recur(data,mid+1,right);
merge(data,left,mid,mid+1,right);
}
int InversePairs(vector<int> data) {
if(data.size()==0)return 0;
recur(data,0,data.size()-1);
return count;
}
};
代码分析
那行重点强调的代码如何理解呢?
归并排序的时候一定是如下图所示,数组1和数组2都已排好序,然后再对它们进行合并,并在合并的过程中计算逆序对
下标0时,逆序对3
下标1时,逆序对0
下标2时,逆序对0
下标3时,逆序对1
把这些加起来就能得到一次合并的逆序对数,再把所有的合并过程总全部累积,那么就得到所有的逆序对数
动态规划解法(时间复杂度达不到要求)
class Solution {
public:
int InversePairs(vector<int> data) {
int size = data.size();
if(size == 0)return 0;
vector<int> dp;
dp.resize(size+1);
dp[0] = 0;//无意义
dp[1] = 0;
for(int i=2;i<=size;i++){
int per = 0;
for(int j=0;j<i-1;j++)
{
if(data[j]>data[i-1])per++;
}
dp[i] = dp[i-1] + per;
dp[i] = dp[i]%1000000007;
}
return dp[size];
}
};
只通过了75%的用例,复杂度计算
\[1+2+3+...+n=\frac{n(n+1)}{2}
\]
仍然是O(n^2)等级的