本篇文章主要介绍归并排序,以及与之相关的改写问题,将该类问题总结归纳,便于后续遇见类似题目时候的解答。
欢迎点赞 收藏 ⭐留言评论 私信必回哟
博主将持续更新学习记录收获,友友们有任何问题可以在评论区留言
博客中涉及源码及博主日常练习代码均已上传GitHub
思想:
其中心思想就是合并两个有序数组。先将序列拆解使得每个子序列有序,再二路归并使子序列段间有序,最后得到有序序列。
public class MergeSort {
public static void mergeSort(int[] arr) {
if (arr == null || arr.length < 2) {
return;
}
process(arr,0,arr.length-1);
}
public static void process(int[] arr, int L, int R) {
if (L == R) {
return;
}
int mid = L + ((R - L) >> 1);
process(arr, L, mid);
process(arr, mid + 1, R);
merge(arr, L, mid, R);
}
public static void merge(int[] arr, int L, int M, int R) {
int[] help = new int[R - L + 1];
int index = 0;
int p1 = L;
int p2 = M + 1;
while (p1 <= M && p2 <= R) {
help[index++] = arr[p1] <= arr[p2] ? arr[p1++] : arr[p2++];
}
//要么p1越界,要么p2越界,两个while只会进去一个
while (p1 <= M) {
help[index++] = arr[p1++];
}
while (p2 <= R) {
help[index++] = arr[p2++];
}
for (int i = 0; i < help.length; i++) {
arr[L + i] = help[i];
}
}
}
题目:
在一个数组中,一个数左边比它小的数的总和,叫该数的小和
所有数的小和累加起来,叫数组小和
例子: [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
给定一个数组arr,求数组小和
题解思路:
如果使用两层for循环的暴力方法,操作结果为等差数列次,所以时间复杂度为O(N^2),可以利用归并排序的方法来降低时间复杂度。
求数组小和,就是需要求出数组中每个数的左边比该数小的所有数的和,这些和加起来就是数组小和;
同样地,这个数组小和的求法可以转换为数组中每个数的右边,有几个数比该数大,有几个则数组小和中加N个该数。
然后就可以利用归并排序的方法,在按区域进行归并排序的时候,也就按区域求出了比该数大的数的个数,也就间接求出了数组小和。
代码实现:
public class SmallSum {
public static int smallSum(int[] arr) {
if(arr==null||arr.length<2) {
return 0;
}
return process(arr,0,arr.length-1);
}
public static int process(int[] arr,int L,int R) {
if(L==R) {
return 0;
}
int M=L+((R-L)>>1);
return process(arr,L,M)
+process(arr,M+1,R)
+merge(arr,L,M,R);
}
public static int merge(int[] arr,int L,int M,int R) {
int[] help=new int[R-L+1];
int index=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[index++]=arr[p1]<arr[p2]?arr[p1++]:arr[p2++];
}
while (p1<=M) {
help[index++]=arr[p1++];
}
while (p2<=R) {
help[index++]=arr[p2++];
}
for (int i = 0; i < help.length; i++) {
arr[L+i]=help[i];
}
return res;
}
}
题目:
在一个数组中,任何一个前面的数a,和任何一个后面的数b,如果(a,b)是降序的,就称为降序对
给定一个数组arr,求数组的降序对总数量
题解思路:
类似这种左边比右边的数大或者小的问题,都可以往归并排序上来想,在上道题9.3中求的是右边比该数大的数的个数,这道题求的就是右边比该数小的个数,可以通过使得归并排序时排逆序就可转化成和上一道题一样的解法了。
代码实现:
public class ReversePair {
public static int reversePair(int[] arr) {
if(arr==null||arr.length<2) {
return 0;
}
return process(arr,0,arr.length-1);
}
public static int process(int[] arr,int L,int R) {
if(L==R) {
return 0;
}
int M=L+((R-L)>>1);
return process(arr,L,M)
+process(arr,M+1,R)
+merge(arr,L,M,R);
}
public static int merge(int[] arr,int L,int M,int R) {
int[] help=new int[R-L+1];
int index=0;
int p1=L;
int p2=M+1;
int res=0;
while (p1<=M&&p2<=R) {
res+=arr[p1]>arr[p2]?R-p2+1:0;
help[index++]=arr[p1]>arr[p2]?arr[p1++]:arr[p2++];
}
while (p1<=M) {
help[index++]=arr[p1++];
}
while (p2<=R) {
help[index++]=arr[p2++];
}
for (int i = 0; i < help.length; i++) {
arr[L+i]=help[i];
}
return res;
}
}
题目:493. 翻转对 - 力扣(Leetcode)
在一个数组中,对于任何一个数num,求有多少个(后面的数*2)依然
3的后面有:1,0
1的后面有:0
7的后面有:0,2
0的后面没有
2的后面没有
所以总共有5个
题解思路:
仍然是利用归并排序的方法,需要在归并之前,把符合要求的数统计下来,因为即使是在二路合并之前,左组中和右组中的数都是分别有序的,所以可以直接完成统计。
代码实现:
public class BiggerThanRightTwice {
public int reversePairs(int[] arr) {
if(arr==null||arr.length<2) {
return 0;
}
return process(arr,0,arr.length-1);
}
public int process(int[] arr, int L, int R) {
if(L==R) {
return 0;
}
int M=L+((R-L)>>1);
return process(arr,L,M)
+process(arr,M+1,R)
+merge(arr,L,M,R);
}
public int merge(int[] arr,int L,int M,int R) {
int ans=0;
int windowR=M+1;
for (int i = L; i <=M ; i++) {
//因为左右数组都是有序的,所以指针windowR不需要回退
while (windowR<=R&&(long)arr[i]>(long)2*arr[windowR]) {
windowR++;
}
ans+=windowR-M-1;
}
int[] help=new int[R-L+1];
int index=0;
int p1=L;
int p2=M+1;
while (p1<=M&&p2<=R) {
help[index++]=arr[p1]<arr[p2]?arr[p1++]:arr[p2++];
}
while (p1<=M) {
help[index++]=arr[p1++];
}
while (p2<=R) {
help[index++]=arr[p2++];
}
for (int i = 0; i < help.length; i++) {
arr[L+i]=help[i];
}
return ans;
}
}
题目:
给定一个数组arr,两个整数lower和upper,
返回arr中有多少个子数组的累加和在[lower,upper]范围上
Leetcode题目:https://leetcode.cn/problems/count-of-range-sum/
题解思路:
1.i到j的子数组和位于范围内,可转换为两个前缀和数组的差位于范围内,所以可以先将原数组转换为前缀和数组,再通过前缀和数组差求解,以降低时间复杂度。
2.假设i位置的前缀和为X,求必须以i位置结尾的子数组,有多少个在[Lower,Upper]范围内,等同于去求i之前的所有前缀和数组中,有多少个前缀和在[X-Upper,X-Lower]上。
3.利用归并排序的有序分组,分别针对右组的每个数,来左组查看多少个数符合要求,并且因为数组的有序性,可以保证指针不回退。
代码实现:
class Solution {
public int countRangeSum(int[] nums, int lower, int upper) {
if(nums==null||nums.length==0) {
return 0;
}
long[] sum=new long[nums.length];
sum[0]=nums[0];
for(int i=1;i<sum.length;i++) {
sum[i]=sum[i-1]+nums[i];
}
return process(sum,0,sum.length-1,lower,upper);
}
public int process(long[] sum,int L,int R,int lower,int upper) {
if(L==R) {
return sum[L]>=lower&&sum[L]<=upper?1:0;
}
int M=L+((R-L)>>1);
return process(sum,L,M,lower,upper)+
process(sum,M+1,R,lower,upper)+
merge(sum,L,M,R,lower,upper);
}
public int merge(long[] sum,int L,int M,int R,int lower,int upper) {
int ans=0;
int windowL=L;
int windowR=L;
// [windowL,windowR)
for(int i=M+1;i<=R;i++) {
long min=sum[i]-upper;
long max=sum[i]-lower;
while(windowR<=M&&sum[windowR]<=max) {
windowR++;
}
while(windowL<=M&&sum[windowL]<min) {
windowL++;
}
ans+=windowR-windowL;
}
long[] help=new long[R-L+1];
int index=0;
int p1=L;
int p2=M+1;
while(p1<=M&&p2<=R) {
help[index++]=sum[p1]<=sum[p2]?sum[p1++]:sum[p2++];
}
while(p1<=M) {
help[index++]=sum[p1++];
}
while(p2<=R) {
help[index++]=sum[p2++];
}
for(int i=0;i<help.length;i++) {
sum[L+i]=help[i];
}
return ans;
}
}
后边的几道题都是归并排序的改写,其本质都是把比较信息变成了有序的东西,这个有序的东西可以帮助我们很快的求很多事。
当再遇见某些数的左组与右组相比符合某些条件,或者右组与左组相比符合某些条件时,可以把问题往归并排序上靠拢,并且借助归并排序的组内数据有序性,可以实现指针的不回退。
⭐️最后的话⭐️
总结不易,希望uu们不要吝啬你们的哟(^U^)ノ~YO!!如有问题,欢迎评论区批评指正