小和问题与逆序对问题(用递归的归并排序思想解决)

小和问题:
在一个数组中,每一个数左边比当前数小的数累加起来,叫做这个数组的小和。求一个数组的小和。
例如:1,3,4,2
3左边1比3小,4左边1、3比4小,2左边1比2小,因此1+1+3+1=6
逆序对问题:
在一个数组中,左边的数如果比右边的数大,则这两个数构成一个逆序对,请打印一个数组的所有逆序对。

思路:
明明是小和问题,怎么会和递归实现的归并排序扯上关系呢?下面就先来看一下递归实现归并排序的过程。归并排序,就是将两个有序数组合并。那么归并排序中的为什么用递归?就是因为将数组分为两部分后,这两部分并不一定是有序的,此时,基于分而治之的思想,可以将这两部分继续各自划成两部分,当划分到只剩一个数时,这个数肯定本身是有序的,因此,递归的出口找到了。
所谓递归,其实就是系统帮我们做的一个压栈操作。当某个运算缺乏参数时,就先把这个运算及其数据压栈,然后一层层压栈,当栈顶获取到参数时,就出栈栈顶的运算及其数据,并将运算结果作为新参数再次从栈顶出栈一个运算,这就是递归的实质。
递归实现的归并排序代码:

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

    //递归函数
    public static void sort(int[] arr,int left,int right){
        if(left==right){
            return;
        }
        int mid=left+((right-left)>>1);
        sort(arr,left,mid);
        sort(arr,mid+1,right);
        merge(arr,left,mid,right);
    }

    //归并
    public static void merge(int[] arr,int left,int mid,int right){
        //因为没有返回值,所以需要一个辅助数组保存归并结果,最后再赋值给arr
        int[] help=new int[right-left+1];
        //这里p1和p2是归并时的索引,为什么不直接用left是因为最后help赋值给arr是需要用到left
        int p1=left;
        int p2=mid+1;
        int index=0;
        while (p1<=mid&&p2<=right){
            if(arr[p1]<arr[p2]){
                help[index++]=arr[p1++];
            }else {
                help[index++]=arr[p2++];
            }
        }
        while (p1<=mid){
            help[index++]=arr[p1++];
        }
        while (p2<=right){
            help[index++]=arr[p2++];
        }
        for (int i = 0; i < help.length; i++) {
            arr[left++]=help[i];
        }
    }

研究完递归实现的归并排序,那么,在小和问题中,归并排序究竟如何体现?
假设,有一个数组长度为10,可以看到,在上面的递归过程中,先一分为二,前5个是一组,再一分为二,直到剩一个数,这个数就是第一个数,因为每次递归都是先递归的左边。然后是第一个数和第二个数归并,然后是34归并,然后12的结果和34的结果再归并,就是这样一个过程。仔细观察,在这个过程中,总是从最左边开始的,归并时,因为归并是已经有序的,因此,当左边某个数小于右边某个数时,那么这个数就小于右边那个数的右边归并区间内的所有数 ,此时,用左边这个数的值乘以右边比他大的数的个数,依次累加,就能求出小和。
我当时看到这个思路时就有一个问题,那就是,这样计算的小和,不会重复吗?其实是不会,因为右边比他大的数只会被计算一次。这就是因为归并排序的牛逼之处了,已经有序的数之间并不会比对第二次。比如,12位置归并后,再和34的结果归并,12之间就不会再归并了,当1234位置归并后,在和后面归并时,他们之间也不会再重复比较了,只会和外面没比较过的进行归并。

小和问题的代码只需要加个全局变量,然后在归并比对时添加就行了:

while (p1<=mid&&p2<=right){
            if(arr[p1]<arr[p2]){
           	//只需要在这里添加一句话,就能用归并代码实现小和问题
                sum+=arr[p1]*(right-p2+1);
                help[index++]=arr[p1++];
            }else {
                help[index++]=arr[p2++];
            }
        }
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值