求逆序对 36

?
?

引言

?
?

一开始接触到这题还觉得挺有意思的,但后来发现其深层次的含义就是一个归并排序,只是在归并排序的过程中做了一点小动作而已,这也再次证明了很多东西都是万变不离其宗的

?
?

本文首先讲了一下归并排序的过程,用了自己比较喜欢的简洁的方式,然后对比归并排序与求逆序对之间的关系,发现需要稍微修改一下合并两个已排序数组的方法,要从两个数组的最后的数开始

?
?

最后考虑到如果求逆序对的字符是固定数目的话,比如4个,详情可以参考后面的,那么时间复杂度不用归并排序的nlogn了,如果我们可以建立辅助数组的话,那么就可以降到n的复杂度。这里又是一种空间换时间的方法

?
?

分析问题

?
?

最开始最直接的分析方法就是循环遍历每一个数,然后把每一个数都与后面所有的数进行比较,看是否逆序,但这种方法时间复杂度过高,需要n平方

?
?

所以我们需要再动动脑筋思考一下,一般这种复杂度高的问题都可以用二分法的思想去降低复杂度

?
?

我们同样可以利用二分的思想,比如7564统计逆序对的过程如下

?
?

?
?

有没有发现这个过程跟归并排序很像,或者其实就是归并排序,只是在排序的时候同时需要统计逆序对罢了

?
?

而统计逆序对的过程也有技巧可循,并不是一个一个统计,如果子数组A的当前数字a大于子数组B的当前数字b,那么a与b之前的数字,包括b,都构成逆序对

?
?

还是上面这个例子

?
?

a中p1指向的7大于p2指向的6,所以这里就有p2的长度个逆序对

?
?

解决问题

?
?

我们首先需要知道归并排序是怎样运作的

?
?

归并排序

?
?

归并排序其实主体只要四行就行了,我喜欢这种简洁的写法

?
?

static void mergeSort(int[] data, int[] temp, int start, int end) {

if (start < end) {

int mid = (start + end) / 2;

mergeSort(data, temp, start, mid);

mergeSort(data, temp, mid + 1, end);

mergeArray(data, temp, start, mid, end);

}

}

?
?

这里我们需要一个辅助数组来存储合并之后的数组,辅助数组要作为一个参数传到递归的深层次去,合并过程也用到递归的思想

?
?

也是根,左右的思想,当把左右都排序好了,就要用到合并两个已排好序的数组的思想,注意这里讨了巧的就是我只建立了一个辅助数组,这个辅助数组贯穿了始终

?
?

下面我们看看合并两个已排好序的数组的过程

?
?

private static void mergeArray(int[] data, int[] temp, int start, int mid,

int end) {

// TODO Auto-generated method stub

int leftPoint = start;

int leftEnd = mid;

int rightPoint = mid + 1;

int rightEnd = end;

int k = 0;

while (leftPoint <= leftEnd && rightPoint <= rightEnd) {

if (data[leftPoint] <= data[rightPoint]) {

temp[k++] = data[leftPoint++];

} else {

temp[k++] = data[rightPoint++];

}

}

while (leftPoint <= leftEnd) {

temp[k++] = data[leftPoint++];

}

while (rightPoint <= rightEnd) {

temp[k++] = data[rightPoint++];

}

}

?
?

最后得到的temp数组即是需要的排好序的数组

?
?

那么下面我们就可以在这个基础上加上我们需要统计的逆序数了

?
?

统计逆序对的数目

?
?

我们之前说过,统计逆序对的时候是从两个已排好序的数组的最后一个数开始比较,这样能够简单粗暴的统计出逆序对的数目

?
?

但是我们之前的归并排序算法是从两个已排好序的数组的第一个数开始比较走的

?
?

不过没关系,我们稍加修改一下代码即可,让合并两个已排好序的数组从后面开始比较也可以合并,注意在合并的时候已完成统计的工作,我们让count作为返回值

?
?

private static int mergeArray(int[] data, int[] temp, int start, int mid,

int end) {

// TODO Auto-generated method stub

int leftPoint = mid;

int leftEnd = start;

int rightPoint = end;

int rightEnd = mid+1;

int k = end;

int count=0;

while (leftPoint >= leftEnd && rightPoint >= rightEnd) {

if (data[leftPoint] > data[rightPoint]) {

temp[k--] = data[leftPoint--];

count+=rightPoint-mid;

} else {

temp[k--] = data[rightPoint--];

}

}

while (leftPoint >= leftEnd) {

temp[k--] = data[leftPoint--];

}

while (rightPoint >= rightEnd) {

temp[k--] = data[rightPoint--];

}

return count;

}

?
?

?
?

另外我们的merge函数也要修改,我们递归的时候要返回左半边和右半边的count数

?
?

static int mergeSort(int[] data, int[] temp, int start, int end) {

int left=0;

int right=0;

int mergeCount=0;

if (start < end) {

int mid = (start + end) / 2;

left=mergeSort(data, temp, start, mid);

right=mergeSort(data, temp, mid + 1, end);

mergeCount=mergeArray(data, temp, start, mid, end);

}

return left+right+mergeCount;

}

?
?

测试代码

?
?

public static void main(String[] args) {

int[] data = { 1, 3, 5, 2, 4, 6 };

int[] temp = data.clone();

System.out.println(mergeSort(data, temp, 0, data.length - 1));

for (int k : temp)

System.out.println(k);

?
?

}

?
?

?
?

如果字符有范围

?
?

比如字符只能是ACGT,相当于只能是1234,那么可以在O(n)的时间复杂度中完成,而上面的归并方法是nlogn的时间复杂度

?
?

解决问题

?
?

首先我们定义一个int数组用于存放逆序对第一个数字是A还是C还是G还是T。

?
?

我们从字符数组的末尾开始,如果这个数是A,那么说明之前如果有数,必然构成逆序对,所以我们在C的那个位上++,在G的那个位上++,在T的那个位上++

?
?

如果这个数是C,那么只能在G的那个位上++,以及T的那个位上++

?
?

如果这个数十G,那么只能在T的那个位上++

?
?

另外要注意的是count++必须在每个case里去加,而不能说最后把left 123全加起来,相当于碰到哪个数就清算一下该数组成的逆序对

?
?

其原因可以看实例说明

?
?

static int inversePairsATCG(String string) {

int count = 0;

char[] chars = string.toCharArray();

int[] left = new int[4];

for (int i = chars.length-1; i >=0; i--) {

?
?

char a = chars[i];

System.out.println(a);

switch (a) {

case ‘A‘:

left[1]++;

left[2]++;

left[3]++;

break;

case ‘C‘:

left[2]++;

left[3]++;

count += left[1];

break;

case ‘G‘:

left[3]++;

count += left[2];

break;

case ‘T‘:

count += left[3];

break;

default:

break;

}

}

return count;

?
?

}

?
?

?
?

实例说明

?
?

public class InversePairsATCG {

public static void main(String[] args) {

String string="ATAGC";

System.out.println(inversePairsATCG(string));

}

}

?
?

首先最后一个数十C,那么在G位++,T位++,由于没有碰到T和G,这时候count还等于0

?
?

下一个数是G,那么在T位++,count要把G为的数加上来,也就是现在逆序对有1个了

?
?

下一个数是A,那么在C位,G位,T位都++,加完之后C位1,G位2,T位3,由于A是最小的不用清算A位能形成的逆序对

?
?

下一个数是T,不用++,但需要清算T位能形成的逆序对,count要加上T位的3,此时count位4

?
?

下一个数是A,C位,G位,T位都++,但由于没有下一位了,不用清算,这时候count返回4

?
?

?
?

时间: 2024-07-31 20:30:05

求逆序对 36的相关文章

树状数组求逆序对:POJ 2299、3067

前几天开始看树状数组了,然后开始找题来刷. 首先是 POJ 2299 Ultra-QuickSort: http://poj.org/problem?id=2299 这题是指给你一个无序序列,只能交换相邻的两数使它有序,要你求出交换的次数.实质上就是求逆序对,网上有很多人说它的原理是冒泡排序,可以用归并排序来求出,但我一时间想不出它是如何和归并排序搭上边的(当初排序没学好啊~),只好用刚学过的树状数组来解决了.在POJ 1990中学到了如何在实际中应用上树状数组,没错,就是用个特殊的数组来记录即

HDU 3743 Frosh Week(归并排序求逆序对)

题目链接:http://acm.hdu.edu.cn/showproblem.php?pid=3743 题目意思就是给你一个长为n的序列,让你求逆序对.我用的是归并排序来求的.归并排序有一个合并的过程,分前后两段,当a[i] > a[j]时,说明a[j]比前面那段啊[i],a[i+1],a[i+2]....,a[mid],比这些都要小,所以总逆序对数要加上mid-i+1. 1 // File Name: HDU3743.cpp 2 // Author: xiaxiaosheng 3 // Cre

求逆序对(inversion)的个数

2-4 逆序对 设A[1...n]是一个包含n个不同数的数组,如果在i<j的情况下,有A[i]>A[j],则(i,j)就称为A中的一个逆序对(inversion). a)列出数组<2, 3, 8, 6, 1>的5个逆序对 b)如果数组的元素取自集合{1,2,...,n}, 那么, 怎样的数组含有最多的逆序对?它包含多少个逆序对? c)插入排序的运行时间与输入数组中逆序对的数量之间有怎样的关系?说明你的理由. d)给出一个算法,它能用Θ(nlgn)的最坏情况运行时间,确定n个元素的任

[Codevs] 1688 求逆序对

1688 求逆序对 时间限制: 1 s 空间限制: 128000 KB 题目等级 : 黄金 Gold 题目描述 Description 给定一个序列a1,a2,…,an,如果存在i<j并且ai>aj,那么我们称之为逆序对,求逆序对的数目 数据范围:N<=105.Ai<=105.时间限制为1s. 输入描述 Input Description 第一行为n,表示序列长度,接下来的n行,第i+1行表示序列中的第i个数. 输出描述 Output Description 所有逆序对总数. 样例

ZJNU 1247 归并排序求逆序对

逆序对——高级 Time Limit: 1000MS   Memory Limit: 65536K Total Submissions: 118   Accepted: 28 Description 对于一个包含N个非负整数的数组A[1..n],如果有i < j,且A[ i ]>A[ j ],则称(A[ i] ,A[ j] )为数组A中的一个逆序对.  例如,数组(3,1,4,5,2)的逆序对有(3,1),(3,2),(4,2),(5,2),共4个. 求n个数中的逆序对个数. Input 第一

算法笔记_065:分治法求逆序对(Java)

目录 1 问题描述 2 解决方案 2.1 蛮力法 2.2 分治法(归并排序)   1 问题描述 给定一个随机数数组,求取这个数组中的逆序对总个数.要求时间效率尽可能高. 那么,何为逆序对? 引用自百度百科: 设 A 为一个有 n 个数字的有序集 (n>1),其中所有数字各不相同. 如果存在正整数 i, j 使得 1 ≤ i < j ≤ n 而且 A[i] > A[j],则 <A[i], A[j]> 这个有序对称为 A 的一个逆序对,也称作逆序数. 例如,数组(3,1,4,5,

2014 HDU多校弟五场A题 【归并排序求逆序对】

这题是2Y,第一次WA贡献给了没有long long 的答案QAQ 题意不难理解,解题方法不难. 先用归并排序求出原串中逆序对的个数然后拿来减去k即可,如果答案小于0,则取0 学习了归并排序求逆序对的方法,可以拿来当模板 TVT 贴代码了: 1 #include <stdio.h> 2 #include <string.h> 3 #include <stdlib.h> 4 #include <math.h> 5 #include <iostream&g

HDU 1394 Minimum Inversion Number(线段树求逆序对)

题目链接: http://acm.hdu.edu.cn/showproblem.php?pid=1394 解题报告:给出一个序列,求出这个序列的逆序数,然后依次将第一个数移动到最后一位,求在这个过程中,逆序数最小的序列的逆序数是多少? 这题有一个好处是输入的序列保证是0 到 n-1,所以不许要离散化,还有一个好处就是在计算在这个序列中比每个数大和小的数一共有多少个的时候可以在O(1)时间计算出来,一开始我没有意识到,还傻傻的用了两层for循环来每次都计算,当然这样果断TLE了.把一个数从第一个移

权值线段树求逆序对问题

我们都知道,求逆序对数量可以用归并排序解决.但是用归并排序只能解决静态的序列问题,没有扩展的区间.因此就有了用权值线段树求逆序对的方法. 1 #include<iostream> 2 #include<iomanip> 3 #include<ctime> 4 #include<climits> 5 #include<algorithm> 6 #include<queue> 7 #include<vector> 8 #inc