算法入门(七):归并排序扩展

174 阅读3分钟

一起养成写作习惯!这是我参与「掘金日新计划 · 4 月更文挑战」的第17天,点击查看活动详情

image.png

小和问题

这个题暴力的解法其实很直观,就是每到一个i就左边遍历一遍计算小和,最终是一个N^2的算法。

这里能不能做到NlogN复杂度?

可以的,基本思想和归并排序也是类似的,不过需要底层逻辑改一下

首先我们需要转换一下我们的思维,求小和可以先看左边1,得到有4个1的小和(右侧4个值比1大),接着有2个3的小和....按照这种思路最后拿num * value 累加得到小和数,例如2个3小和其实意思就是右侧有两个数字比3大,算小和时候会带上两个3 - > 2*3

image.png

然后上述这个就可以基于归并思想来解决:

image.png

一开始1与3处理,得到1个1的小和结果,辅助数组返回为[1 3]

接着处理13与4,两个下标分别比较,首先1<4,辅助数组新增1,且小和新增1个1,然后3<4,辅助数组新增3,且小和新增1个3(左侧小产生小和)...

image.png

左侧完成了进行右边的处理,得出两边的小和,最后再进行一轮处理就行了

image.png

上述其实不一定要把一开始的思想反过来,也可以让二叉树右侧和左侧比较,大的来计数小和也是可行的,并且之前小和结果信息一样利用

image.png

这里主要要思考一下为啥小和数字信息保留了,没有遗漏和重算:

例如3再第一次归并时候和4进行了比较,留下了小和信息

然后下一次归并时候又和右侧的5进行了比较,留下了小和信息 -> 3*2 没有遗漏和重算也复用了结果

为什么没有遗漏和重算也是需要思考的(不遗漏:只有和右组merge时候才会处理,都会merge的所有不遗漏。不重算:计算小河后以就merge了,merge后不会再计算):

image.png

为什么递归时需要数组排序

这样能够很快直接知道后续有多少个数字比你自己大,这样其实就是通过排序把原先的遍历过程代替了,这样可以直接知道小数个数

image.png

和标准mergeSort的差别

另外还要提一下如果左右相同,必须先拷贝右组的并且不产生小和,这样才能知道有多少个准确的小和数量,下图现在1明确小于2,然后可以直接得到5个1的小和,然后连着3个5个1的小和,然后左侧到2继续...

image.png

上面辅助数组最后是7个1....

代码实现

public static int smallSum(int[] arr){

    if(arr == null || arr.length < 2){

        return;

    }

    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+ ((L+R) >> 1);

    // 注意:这里是左侧的排序和小和数量 + 右侧的排序小和数量 + 左右侧排好后merge小和数量 = 小和的总数

     return 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){

    // 定义辅助数组和指向L和M+1的指针

    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存储小和 - 如果左侧小于右侧,那么得到小和为左侧Value * 右侧数下标到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++];

    }

    // 辅助数组拷贝至目标 - 完成arr的L - R区间排序

    for(i=0;i < arr.length;i++){

        arr[L + i] = help[i];

    } 

    return res;

}

逆序对问题

image.png

也是有点类似的问题,解决思路是基本一致的,之前是求左边比自己小的,这里是求左边比自己大的,区别就是这个,其他解题思路一致。

我们可以同样反转一下思路,求每个数右侧比自己小的,这就是这个数对应的所有逆序对:

image.png

接着通过归并递归思路来求右边有多少数比左边小,然后回升记录逆序对即可