归并排序应用之求数组小和

617 阅读2分钟

数组小和定义:
在一个数组中,一个数左边比它小的数的总和,叫数的小和,所有数的小和累加,叫数组小和。
例子:数组 [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
在归并排序的过程中,merge的时候,左组的数比右组的数小的时候产生小和,其余时候不产生小和。
对于每个数来说,右边有多少个数比当前数大,当前数产生多少个小和。

public class SmallSum {

    public static int smallSum(int[] arr) {
        if (arr == null || arr.length < 2) {
            return 0;
        }
        return process(arr, 0, arr.length -1);
    }

    //arr[L...R]既要排好序,也要求小和返回
    private 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的小和
    }

    //把L...mid和mid+1...R的数merge到一起,使之整体有序
    private static int merge(int[] arr, int L, int M, int R) {
        //辅助数组
        int[] help = new int[R - L + 1];
        //辅助数组下标
        int i = 0;
        //p1为左部分数组下标开始位置
        int p1 = L;
        //p2为右部分数组下标开始位置
        int p2 = M + 1;

        int res = 0;//L...R的小和
        //p1和p2都还没越界,谁小谁拷贝到help数组
        while (p1 <= M && p2 <= R) {
            res += arr[p1] < arr[p2] ? (R - p2 + 1) * arr[p1] : 0;
            //谁小谁拷贝到help数组,相等先拷贝右边
            help[i++] = arr[p1] < arr[p2] ? arr[p1++] : arr[p2++];
        }
        //下面两个while自会有一个执行,因为p1和p2不可能同时越界
        while (p1 <= M) {
            //把p1剩下的元素全部拷贝到help
            help[i++] = arr[p1++];
        }
        while (p2 <= R) {
            //把p2剩下的元素全部拷贝到help
            help[i++] = arr[p2++];
        }
        //把help数组元素刷回arr
        for (i = 0; i < help.length; i++) {
            arr[L + i] = help[i];
        }
        return res;
    }

    public static void main(String[] args) {
        int[] arr = new int[]{1,3,4,2,5};
        System.out.println(smallSum(arr));
    }
}