小和问题

在一个数组中,每一个数左边的数比当前数小的累加起来,叫做这个数组的小和

举例:

[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

 

解题思路:分治思想、归并

将一个数组分成等份的子数组,分别为左数组和右数组。由题意可以将题意转换为:统计右边的数比当前数大的数量 * 当前数 = 有多少个数比当前数大

所以可以将左数组和右数组分别进行升序排列,如果右数组的第一个元素大于左数组的第一个元素,说明右数组的所有数都大于左数组。小和就等于 当前值*比当前值大的数量。

 

/*
    在一个数组中,每一个数左边比当前数小的数累加起来,叫做这个数组的小和。求一个数组的小和。
 */
public class SmallSum {
    public static int smallSum(int[] arr) {
        if (arr == null || arr.length < 2) {
            return 0;
        }
        //在数组长度0-arr.length-1范围上产生小和
        return process(arr, 0, arr.length - 1);
    }

    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);
    }

    public static int merge(int[] arr, int L, int mid, int R) {
        int[] help = new int[R - L + 1];
        int res = 0;
        int i = 0;
        int p1 = L;
        int p2 = mid + 1;
        while (p1 <= mid && 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 <= mid) {
            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;
    }
}

 

通过代码进行理解更容易

L:数组第一个元素

R:数组最后一个元素

P1:指向左数组第一个元素的指针

P2:指向右数组第一个元素的指针

mid:数组中点

help数组:辅助数组,归并排序的合并过程,将排序好的数组拷贝回原数组。

关键点1:如何确定右数组比当前值大的数量:(R - P2 +1))

关键点2:当左数组或右数组越界时,将另一个数组放入辅助数组

关键点3:每一个递归过程都会产生小和

最重要的一点:当左右两个数组有相等元素时,要先将右数组放入辅助数组,因为不确定右数组相等元素的下一个元素是否比左数组的值大,只有完全严格大于左数组的元素时才拷贝。

 

posted @ 2020-07-10 16:01  硬盘红了  阅读(260)  评论(0编辑  收藏  举报