「这是我参与2022首次更文挑战的第29天,活动详情查看:2022首次更文挑战」。
一、小和问题
在一个数组中,一个数左边比它小的数的总和,叫数的小和,所有数的小和累加起来,叫数组小和。求数组小和。 例子: [1,3,4,2,5] 1左边比1小的数:没有 3左边比3小的数:1 4左边比4小的数:1、3 2左边比2小的数:1 5左边比5小的数:1、3、4、 2 所以数组的小和为1+1+3+1+1+3+4+2=16
1、分析
小和问题:每个数左边比自己小的数都累加起来,最后整体累加起来
方法一:当来到每个位置,遍历前边所有的数,比自己小的累加起来,左后全部累计起来,时间复杂度:O(N²)
方法二:换个思路,当前i来到a位置,右边有多少个比a大,就产生多少个a(x个a),当前i来到b位置,右边有多少个比b大,就产生多少个b(y个b),当前i来到c位置,右边有多少个比c大,就产生多少个c(z个c),最后小和累加 = x*a + y*b + z*c
左边每个数找一遍比自己小的数 = 右边有多少个数比自己大
2、实现
public static int smallSum(int[] arr) {
if (arr == null || arr.length < 2) {
return 0;
}
return process(arr, 0, arr.length - 1);
}
// arr[L..R]既要排好序,也要求小和返回
// 所有merge时,产生的小和,累加
// 左 排序 merge
// 右 排序 merge
// merge
public static int process(int[] arr, int L, int R) {
if (L == R) {
return 0;
}
// l < r
int mid = L + ((R - L) >> 1);
return
process(arr, L, mid)
+
process(arr, mid + 1, R)
+
merge(arr, L, mid, R);
}
public static int merge(int[] arr, int L, int M, int R) {
int[] help = new int[R - L + 1];
int i = 0;
int p1 = L;
int p2 = M + 1;
int res = 0; // 核心代码
while (p1 <= M && p2 <= R) {
res += arr[p1] < arr[p2] ? (R - p2 + 1) * arr[p1] : 0; // 核心代码
help[i++] = arr[p1] < arr[p2] ? arr[p1++] : arr[p2++];
}
while (p1 <= M) {
help[i++] = arr[p1++];
}
while (p2 <= R) {
help[i++] = arr[p2++];
}
for (i = 0; i < help.length; i++) {
arr[L + i] = help[i];
}
return res; // 核心代码
}
二、逆序对问题
在一个数组中, 任何一个前面的数a,和任何一个后面的数b, 如果(a,b)是降序的,就称为逆序对 返回数组中所有的逆序对
1、分析
任何一个左边的数和任何一个右边的数是降序的,就称为逆序对
可以转化为右边有多少个数比它小问题,求x,右边有多少个比x小的逆序对,只有x作为左手姿态,右边才有可能产生逆序对,若x作为右手姿态,不可能有逆序对
核心思路:merge过程,从右往左拷贝,两个指针开始分别指向最右位置,谁大拷贝谁,相等右边指针向前移动
2、实现
public static int reversePairNumber(int[] arr) {
if (arr == null || arr.length < 2) {
return 0;
}
return process(arr, 0, arr.length - 1);
}
// arr[L..R]既要排好序,也要求逆序对数量返回
// 所有merge时,产生的逆序对数量,累加,返回
// 左 排序 merge并产生逆序对数量
// 右 排序 merge并产生逆序对数量
public static int process(int[] arr, int L, int R) {
if (L == R) {
return 0;
}
// l < r
int mid = L + ((R - L) >> 1);
return process(arr, L, mid) + process(arr, mid + 1, R) + merge(arr, L, mid, R);
}
public static int merge(int[] arr, int L, int M, int R) {
int[] help = new int[R - L + 1];
int i = help.length - 1;
int p1 = M;
int p2 = R;
int res = 0;
while (p1 >= L && p2 > M) {
res += arr[p1] > arr[p2] ? (p2 - M) : 0;
help[i--] = arr[p1] > arr[p2] ? arr[p1--] : arr[p2--];
}
while (p1 >= L) {
help[i--] = arr[p1--];
}
while (p2 > M) {
help[i--] = arr[p2--];
}
for (i = 0; i < help.length; i++) {
arr[L + i] = help[i];
}
return res;
}
三、总结
小和问题:
- 在归并排序中的merge过程中求小和
- 转换思路
逆序对问题:
- 归并排序merge过程进一步体验
两个题都用到了归并排序,前提需要掌握归并排序的过程,这两道题都是在mergeSort过程中做手脚。
友情链接:归并排序