1. 问题描述
给定一个数组array,和一个正整数p,数组的长度len是$2^p$,再给定一个数组reverseGroup,他的数组元素的范围是$[0,p]$。我们需要返回一个答案数组ans,ans[i]表示把原数组按照$2^{reverseGroup[i]}$个元素为一组反转后,数组中逆序对的个数。
p的范围$[1,10]$,reverseGroup[i]的范围$[1,100]$。
例如:
array=[1,2,3,4,5,6,7,8], reverseGroup = [0,1,2], p = 3;
- 首先把原数组按照$2^0 = 1$个元素一组反转,此时相当于没有反转,逆序对个数0,因此ans[0] = 0;
- 然后把数组按照$2^1 = 2$个元素为一组进行反转,反转后的数组array=[2,1,4,3,6,5,8,7],逆序对个数是4,ans[1] =4,
- 然后把数组按照$2^2 = 4$个元素为一组进行反转,反转后的数组array=[3,4,1,2,7,8,5,6],逆序对的个数是4,ans[2] = 4,
于是返回ans=[0,4,4];
2. 暴力解法
直接模拟即可,代码如下:
| 12
 3
 4
 5
 6
 7
 8
 9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 
 | public class ReversePairCount2 {
 public static int[] count(int[] array, int[] reverseGroup, int p) {
 int rLen = reverseGroup.length, pre = -1;
 int[] ans = new int[rLen];
 for(int i = 0; i < rLen; ++i) {
 if(reverseGroup[i] == 0) {
 if(pre == -1) pre = getReversePairCount(array);
 }else {
 reverseByGroup(array, 1 << reverseGroup[i]);
 pre = getReversePairCount(array);
 }
 ans[i] = pre;
 }
 return ans;
 }
 
 private static int getReversePairCount(int[] array) {
 int len = array.length, ans = 0;
 for(int i = 0; i < len; ++i) {
 for(int j = i + 1; j < len; ++j) {
 if(array[i] > array[j]) ++ans;
 }
 }
 return ans;
 }
 
 private static void reverseByGroup(int[] array, int group) {
 int i = 0, len = array.length, j;
 while(i < len) {
 j = i + group - 1;
 reverseRange(array, i, j);
 i = j + 1;
 }
 }
 
 private static void reverseRange(int[] array, int i, int j) {
 while(i < j) {
 int t = array[i];
 array[i++] = array[j];
 array[j--] = t;
 }
 }
 }
 
 | 
时间复杂度$O(N^2*rLen)$,空间复杂度$O(1)$。
3. 最优解
3.1. 算法分析
使用归并排序的merge过程,先按照两个数为一组求解所有组中的逆序对;然后按照4个数为一组求解所有组的逆序对(求解4个数为一组的逆序对的时候,需要把一组的4个数,分成左右两组,左右两组每组两个数,只考虑左右两组之间能产生多逆序对,而不要考虑左组内部有多少逆序对,右组内部有多少逆序对,这是因为左右两组内部的逆序对在上一步的时候已经求解过了),然后按照8个数为一组求解所有组的逆序对(同样,求解8个数为一组的时候,每一组分成左右两组,左右两组每组4个数,我们只需要求解左右两组之间可以产生多少逆序对),…,一直求解到按照$2^p$个数为一组的时候,逆序对的个数。
我们把每一步的答案都记录下来,根据数组长度是$2^p$,我们一共可以得到p个答案,因此数组中总的逆序对的个数就是这p个答案的加和。
为什么要这么做呢?
这是因为当反转的时候,我们没有必要去真的反转数组,只需要调整我们上面求得的答案,就可以快速获取反转后的答案。当我们要求4个数为一组反转后的答案的时候,可以知道4个数为一组反转后,它不影响我们上面求解的8个数为一组的时候的答案(因0为8个数为一组的答案是左右两组之间形成的逆序对的个数,左右组反转了(实际上左右组内的数据无论被洗牌成什么样,只会影响左右组自己组内的逆序对的个数,而不会影响左右两组之间形成的逆序对的个数),那么反转会使得4个数为一组,2个数为一组的逆序对的个数发生变化,变成了什么呢?其实反转以后,他们的逆序对的个数,就是反转之前正序对的个数,因此我们只需要求得2个数为一组,4个数为一组,…,$2^p$个数为一组的正序对,逆序对的个数就可以了。这样当发生反转的时候,例如$2^i$个数为一组进行反转,我们只需要从$2^1,2^2,…,2^i$,这些组的逆序对个数和正序对个数交换,那么就可以修正原来逆序对的答案数组,每一步的答案就是修正后的逆序对的答案数组的元素加和。
于是我们有如下算法:
设置两个答案数组reverseCountByGroup和normalCountByGroup,分别代表数组各个分组的逆序对个数和正序对个数。然后使用归并排序的方法,对数组求解逆序对和正序对的个数,把答案写在数组中。
设置答案数组ans,然后遍历reverseGroup数组,对每一个反转分组rGroup,进行如下步骤:
- 如果rGroup>0,那么对所有的i从0到rGroup-1,把reverseCountByGroup[i] 和 normalCountByGroup[i],互换。
- i从0到p-1,把reverseCountByGroup[i]累加起来,作为当前这次的答案放入ans中。
例如,array=[1,3,5,7,2,4,6,8], reverseGroup=[1,2,0,3],
array长度len = 8; p = 3。
因此reverseCountByGroup和normalCountByGroup的长度是3
- reverseCountByGroup[0]的值是按照两个数为一组的时候,逆序对的总个数,因为两个数一组的时候,没有逆序对,因此reverseCountByGroup[0]=0;
- reverseCountByGroup[1]的值是按照4个数为一组的时候,因此第一组[1,3,5,7],分成左右两组[1,3],[5,7],这两组之间没有逆序对,同样[2,4,6,8]也没有逆序对。因此reverseCountByGroup[1]=0;
- reverseCountByGroup[2]的值是按照8个数为一组的时候,第一祖[1,3,5,7,2,4,6,8],分成左右两组,左组[1,3,5,7],右组:[2,4,6,8],这两组之间可以产生逆序对(3,2),(5,2),(5,4),(7,2),(7,4),(7,6)。因此一共有6个,因此reverseCountByGroup[2]=6;
求解normalCountByGroup是类似的方法。因此
normalCountByGroup=[4,4,10]
reverseCountByGroup=[0,0,6]
reverseGroup[0] = 1, 即两个数一组进行反转,因此我们需要把normalCountByGroup[0]和reverseCountByGroup[0]进行互换,因此normalCountByGroup=[0,4,10],reverseCountByGroup=[4,0,6],于是反转后,逆序对的总个数是4+0+6=10
reverseGroup[1] = 2, 即4个数一组进行反转,因此我们需要把normalCountByGroup[0…1]和reverseCountByGroup[0…1]进行互换,因此normalCountByGroup=[4,0,10],reverseCountByGroup=[0,4,6],于是反转后,逆序对的总个数是0+4+6=10
…
剩下的也是类似的做法。
3.2. 代码
| 12
 3
 4
 5
 6
 7
 8
 9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 
 | public class ReversePairCount22 {
 public static int[] count(int[] array, int[] reverseGroup, int p) {
 int[] reverseAns = new int[p],
 normalAns = new int[p],
 arrayCopy = new int[array.length],
 h = new int[array.length],
 ans = new int[reverseGroup.length];
 for(int i = 0; i < array.length; ++i) arrayCopy[array.length - 1 - i] = array[i];
 getReversePairCntAns(array, reverseAns, h);
 getReversePairCntAns(arrayCopy, normalAns, h);
 int pre = -1;
 for(int i = 0; i < reverseGroup.length; ++i) {
 if(reverseGroup[i] > 0) {
 for(int j = 0; j < reverseGroup[i]; ++j) {
 int t = reverseAns[j];
 reverseAns[j] = normalAns[j];
 normalAns[j] = t;
 }
 pre = -1;
 }
 if(pre == -1) {
 pre = 0;
 for(int j = 0; j < p; ++j) pre += reverseAns[j];
 }
 ans[i] = pre;
 }
 return ans;
 }
 
 private static void getReversePairCntAns(int[] array, int[] ans, int[] h) {
 int l, m, r, i = 0, g = 1, len = array.length, t;
 while(g < len) {
 l = 0;
 t = 0;
 while(l < len - 1) {
 if((m = l + g - 1) >= len - 1) break;
 r = Math.min(len - 1, m + g);
 t += merge(array, l, m, r, h);
 l = r + 1;
 }
 ans[i++] = t;
 if(g > (len >> 1)) break;
 g <<= 1;
 }
 }
 
 private static int merge(int[] array, int l, int m, int r, int[] h) {
 int i = l, j = m + 1, k = 0, ans = 0;
 while(i <= m) {
 while(j <= r && array[i] > array[j]) ++j;
 ans += j - m - 1;
 ++i;
 }
 i = l;
 j = m + 1;
 while(i <= m && j <= r) h[k++] = array[i] <= array[j] ? array[i++] : array[j++];
 while(i <= m) h[k++] = array[i++];
 while(j <= r) h[k++] = array[j++];
 System.arraycopy(h, 0, array, l, k);
 return ans;
 }
 }
 
 | 
数组array长度N,reverseGroup数组长度是R,那么时间复杂度$O(max{p*R,N\log_2N})$,空间复杂度$O(N)$。
4. 测试程序
| 12
 3
 4
 5
 6
 7
 8
 9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 
 | import java.util.*;
 public class ReversePairCount2Test {
 
 public static void main(String[] args) {
 int time = 100_0000, maxP = 6, maxReverseGroupSize = 10;
 while(time-- > 0) {
 Random r = new Random();
 int p = r.nextInt(maxP) + 1, size = 1 << p, groupSize = r.nextInt(maxReverseGroupSize) + 1;
 int[] array1 = new int[size], array2 = new int[size], array3 = new int[size],
 group1 = new int[groupSize], group2 = new int[groupSize], group3 = new int[groupSize];
 for(int i = 0; i < size; ++i) array1[i] = array2[i] = array3[i] = r.nextInt(201) - 100;
 for(int i = 0; i < groupSize; ++i) group1[i] = group2[i] = group3[i] = r.nextInt(p + 1);
 int[] ans1 = ReversePairCount2.count(array1, group1, p), ans2 = ReversePairCount22.count(array2, group2, p);
 for(int i = 0; i < groupSize; ++i) {
 if(ans1[i] != ans2[i]) {
 System.out.println("Oops! answer incorrect!!, ans1[" + i + "] = " + ans1[i] + ", ans2[" + i + "] = " + ans2[i]);
 System.out.println(Arrays.toString(ans1));
 System.out.println(Arrays.toString(ans2));
 System.out.println(Arrays.toString(array3));
 System.out.println(Arrays.toString(group3));
 System.out.println(p);
 System.out.println(groupSize);
 return;
 }
 }
 System.out.println("Testcase: " + time + " done!");
 }
 System.out.println("Test done successfully !!");
 }
 }
 
 |