master公式 T(N)=a*T(N/b)+O(N^d)
T(N):问题的规模是N个数据
N/b:子过程的规模
a:调用的次数
O(N^d) :除子问题的调用之外,剩余的代码的时间复杂度
使用条件:满足子问题等规模的递归
arr[L,R]范围上求最大值
public class GetMax {
@Test
public void test() {
int[] arr = new int[]{3,4,1};//StackOverflowError栈溢出
System.out.println(getMax(arr));
}
public static int getMax(int[] arr) {
return process(arr, 0, arr.length - 1);
}
// arr[L,R]范围上求最大值
public static int process(int[] arr, int L, int R) {
if (L == R) { //数组元素只有一个时,直接返回
return arr[L];
}
int mid = L + (R - L) >> 1;//中点
int leftMax = process(arr, L, mid);
int rightMax = process(arr, mid + 1, R);
return Math.max(leftMax, rightMax);
}
}
归并排序
将整组数组分为左右两组分别排序,两边分别排好序之后整合成一整个有序数组
整合左右两侧有序数组
指针先指向两数组第一个位置,指针所在的数比较大小
较小值拷贝到新开辟的数组中,当前数组指针后移
较大值指针保持不变,等待另一指针移动后与新的值进行比较
直到有一边的指针越界,将另一数组的剩下所有的数拷贝到新数组中
public class MergeSort {
@Test
public void test(){
int[] arr = new int[]{3,1,2};
MergeSort.mergeSort(arr);
for (int i = 0; i < arr.length; i++) {
System.out.print(arr[i]+" ");
}
}
public static int[] mergeSort(int[] arr) {
if (arr == null || arr.length < 2) {
return arr;
}
MergeSort.process(arr, 0, arr.length - 1);
return arr;
}
public static void process(int[] arr, int L, int R) {
if (L == R) {
return;
}
int mid = L + (R - L) >> 1;
MergeSort.process(arr, L, mid);//左数组排序
MergeSort.process(arr, mid + 1, R);//右数组排序
MergeSort.merge(arr, L, mid, R);//整合
}
//整合
public static void merge(int[] arr, int L, int mid, int R) {
int[] help = new int[R - L + 1];//开辟一块等区域的新数组,用来拷贝
int i = 0;//新数组的指针
int p1 = L;//左数组的指针
int p2 = mid + 1;//右数组的指针
while (p1 <= mid && p2 <= R) {//左右两边的数组都没有越界
if (arr[p1] <= arr[p2]) {//将较小的数拷贝至help数组
help[i++] = arr[p1++];
} else {
help[i++] = arr[p2++];
}
}
while (p1 > mid && p2 <= mid) {//p1指针首先越界,将p2所在数组剩余的数拷贝到help数组
help[i++] = arr[p2++];
}
while (p2 > R && p1 <= mid) {//p2首先越界,将p1所在数组剩余的数拷贝到help数组
help[i++] = arr[p1++];
}
for (int j = 0; j < help.length; j++) {
arr[L + j] = help[j];
}
}
}
归并排序的时间空间复杂度
小和问题 —— 可以利用归并排序边排序边计算小和,可以将O(n^2)转化为O(NlogN)的复杂度
暴力求解:每经过一个数遍历左边的数,比它本身小就加上 --> 时间复杂度:O(N^2)
左边比n小的数相加 等效于 右边比n大的个数*n
用归并排序求解的时候,左侧的数小才产生小和,右侧的数小不产生小和
合并的时候只看右侧是否有比左侧的数大,有则计算此次合并时所要加的小和值,没有则左侧的指针右移
在两侧指针指向的数相等时,一定是右侧的指针右移
如果左侧的指针右移,左侧指针指向的数大,右侧指针指向的数小,不产生小和,此时就会导致小和漏算
public class SmallSum {
@Test
public void test() {
int[] arr = new int[]{2, 1, 3};//3
System.out.println(SmallSum.smallSum(arr));
}
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 mid = L + (R - L) >> 1;
return process(arr, L, mid) + process(arr, mid + 1, R) + merge(arr, L, mid, R);
//左数组的小和+右数组的小和+两数组merge后的小和
}
public static int merge(int[] arr, int L, int mid, int R) {
int help[] = new int[R - L + 1];
int i = 0, p1 = L, p2 = mid + 1;
int smallnum = 0;
while (p1 <= mid && p2 <= R) {//都没越界
if (arr[p1] < arr[p2]) {
smallnum = smallnum + arr[p1] * (R - p2 + 1);
//整合时两数组都有序,当arr[p1]<arr[p2]时,那么arr[p1]一定也小于arr[p2]后的所有数,计算出小和需要加多少次arr[p1]即可
help[i++] = arr[p1++];
} else {//如果左右两数组指针指向的数值相同时,一定是右数组的指针右移,拷贝右数组的数并且不产生小和
//相等时如果拷贝左数组,可能会导致漏算
//右侧小的时候时候不产生小和
help[i++] = arr[p2++];
}
}
//小和算完,拷贝剩余数组(不拷贝也可以)
// while (p2 > R && p1 <= mid) {
// help[i++] = arr[p1++];
// }
// while (p1 > mid && p2 <= R) {
// help[i++] = arr[p2++];
// }
return smallnum;
}
}