数组小和问题:用递归思想和归并排序求数组arr的数组小和

数组小和问题:用递归思想和归并排序求数组arr的数组小和

提示:之前讲过的归并排序,可不仅仅是归并排序这么简单,递归思想和归并的思想,它有大用处!
看本文之前,必看之前的基础知识:
(1)10大排序算法之四:归并排序【稳定的】,复杂度中,系统常用归并排序


题目

数组arr中,某个i位置左边所有比[i]小的数的和,叫小和
从i=1–N-1所有i位置的小和的累加和,叫数组小和
请你求数组arr的数组小和是多少?


一、审题

示例:arr=1,3,2,4,5
0位置不看
1位置的3,左边有1小于3,故小和为1
2位置的2,左边只有1小于2,故小和为1
3位置的4,左边有3个都小于4,故小和为1+3+2=6
4位置的5,左边全部都小于5,故小和为6+4=10
所以数组小和为1+1+6+10=18


二、解题

暴力解,面试0分!!

来到i位置,都去索引0–i-1上有几个小于[i],然后将他们都累加好
最后所有位置i的小和累加就是数组小和
这样的时间复杂度
外层n次遍历
内层对比每一个位置也是n次遍历
故复杂度o(n^2)
面试时肯定0分。
暴力解好说:

//暴力方法——这种方法在笔试过程中会被直接AC,不过面试只能得0分,不过我们可以拿来当对数器
    public static int smallSum1(int[] arr){
        int sum = 0;
        int[] num = new int[arr.length];//每一个arri,比它大的数有多少个
        for (int i = 0; i < arr.length; i++) {
            for(int j = i + 1; j < arr.length; j++){
                if(arr[j] > arr[i]) num[i]++;//去i右边暴力找,找到一个num+一次
            }
        }
        //统计完以后直接累加
        for (int i = 0; i < arr.length; i++) {
            sum += arr[i] * num[i];//值*它出现的次数
        }
        return sum;
        //这种方法:空间复杂度还需要另外申请O(N),时间复杂度是O(N^2),耗时耗空间。
    }

当然,你可以在笔试用一下试试,复杂度通不过,即使能AC,也是一部分
当然了,你啥招都想不到,是可以这么做得

最优解:用递归思想和归并的思想

这里不得不说,递归和归并排序的思想,是非常好非常好的基础知识
(1)10大排序算法之四:归并排序【稳定的】,复杂度中,系统常用归并排序

在解大厂的面试题的时候,你要敏感地想到,你学过什么数据结构与算法的知识,咱们能用哪些数据结构解?
咱们能利用哪些见过的题目解决这个问题?
这很难,但是唯独多练习,多思考,多理解,才可能培养出来敏感度。

这个题,求一个位置i的小和,你要看它左边有几个比它小,这事还就真的很难办……
我们很多数据结构与算法中的题目,一定要在原来题目的基础上转化它的意思,才能解!

我们来观察示例:arr=1,3,2,4,5
0位置不看
1位置的3,左边有1小于3,故小和为1
2位置的2,左边只有1小于2,故小和为1
3位置的4,左边有3个都小于4,故小和为1+3+2=6
4位置的5,左边全部都小于5,故小和为1+3+2+4=10
所以数组小和为1+1+6+10=18
在求数组小和的过程中,你看看那个0位置的1,它被加了多少次?
凭什么每一次1能被加入数组小和?
因为右边有数比它大!这就是为什么!
所以,由于0位置1,右边有4个数,比1大,故在求小和的过程中,1被加了4次!!!

那么,同理,我们思考,如果能在每一个位置i,发现它右边有几个数比其大(x个),那我们就可以加多少个[i](x * [i])
这样,我们就能把问题转换出来了,不再局限到题目原来的意思,当然,我们的理解,是数组小和的本质


好,那我们如何能在i位置,快速知道,右边有几个数大于我呢???? ——这就是优化的地方!!!

咱们单独就看上面那个案例:
arr=1,3,2,4,5
曾经我们学习归并排序时,这时候,要把这个数组归并排序
mid=0+4/2=2
——先左边递归排序,右边递归排序
然后再这样的情况下,从0–2,3–4内部已经排序好了为:1,2,3 4,5
——然后该归并为1一个数组了吧,merge:1,2,3 4,5–>1 2 3 4 5的过程如下
我们说从p1和p2开始,谁小copy谁到help数组,此时大家观察一下:
在merge的过程中,我们产生小和,累加到数组小和上

(1)如果[p1]<[p2],自然是满足左边部分的小于右边部分的值,右边部分有几个比p1位置大?
有R-p2+1个:2个
所以数组小和整体就可以累加21了,之后p1++,把1copy到help中。
(2)此时:[p1]<[p2],自然是满足左边部分的小于右边部分的值,右边部分有几个比p1位置大?
有R-p2+1个:2个
所以数组小和整体就可以累加2
2了,之后p1++,把1copy到help中。
(3)此时:[p1]<[p2],自然是满足左边部分的小于右边部分的值,右边部分有几个比p1位置大?
有R-p2+1个:2个
所以数组小和整体就可以累加23了,之后p1++,把1copy到help中。
(4)此时p1已经越界,还剩下右边部分没有完事,直接copy右边所有元素给help
然后转移到arr中,完成merge操作
在这里插入图片描述
注意,如果左边部分有比右边部分大的呢?
比如:1,4 2,5
咱们对比4和2时,左边>=右边,所以不需要产生小和,直接copy右边的2就行,继续merge
在这里插入图片描述
总结一下:我们如何利用归并排序的merge来产生x个小和,累加到结果上呢?
数组arr,划分为L–mid–R两部分:
(0)咱们定义一个
递归排序的主函数mergeSortAndGetSmallSum

——递归排序左边部分,会有左边部分的小和smallLeft
——递归排序右边部分,会有右边部分的小和smallRight
——然后咱们merge L–mid–R,使得arr在L–R上整体有序,与此同时产生这个整体上贡献的小和smallCurrent
(1)merge本身常规操作不动,但是,只需要在左边<右边时,产生R-p2+1个数(这个数就是[p1]),然后累加到结果中即可,这可不就是我们要优化的方法吗?o(1)速度知道右边有x个数,大于p1位置,这样直接就把x
[p1]累加到小和中。
(2)当左边>=右边时,咱不产生小和,直接完成merge的copy右边p2的数去help
然后我们L–R上整体的小和是多少呢?
smallLeft+smallRight+smallCurrent=ans
返回ans
在这里插入图片描述
理解了吗?我们在做啥
我们递归左边,或者递归右边的时候,左右2部分的数,不会乱
所以左边产生左边的小和left,右边产生右边的小和right
最后整体merge,只看右边部分,有多少个大于左边每个位置p1的?
整体产生一个小和current,累加起来就是我们统计的数组小和。

好,手撕代码,看一遍就能理解上面的图:

//复习数组小和
    //归并过程:
    public static int mergeGenerateSmallSum(int[] arr, int L, int mid, int R){
        if (L == R) return 0;//就剩一个数,不需要哦merge,小和也不会有

        //至少2个数
        int p1 = L;
        int p2 = mid + 1;
        int i = 0;
        int[] help = new int[R - L + 1];
        int ans = 0;//merge中产生的小和

        while (p1 <= mid && p2 <= R){
            //现在只有小于的时候,咱们才copy左边哦,因为,左>右时,我们才copy右边
            ans += arr[p1] < arr[p2] ? (R - p2 +1) * arr[p1] : 0;//这一步得放前面哦!!!!
            //目的就是这样产生小和,左>=右时不需要产生小和,加0就行。
            //正常该merge的merge,但是小和是要产生与左边小于右边的情况下
            help[i++] = arr[p1] < arr[p2] ? arr[p1++] : arr[p2++];//注意,之前的merge比较是<=,这样稳定
        }
        //没copy的继续
        while (p1 <= mid) help[i++] = arr[p1++];
        while (p2 <= R) help[i++] = arr[p2++];

        //然后转移回去
        for (int j = 0; j < help.length; j++) {
            arr[L + j] = help[j];
        }

        //返回我们这个过程中收集到的小和
        return ans;
    }
    //归并排序,并产生数组小和left,right,cur
    public static int mergeSortAndGetSmallSum(int[] arr, int L, int R){
        if (L >= R) return 0;//1个数没法归并
        int mid = L + ((R - L) >> 1);

        int left = mergeSortAndGetSmallSum(arr, L, mid);
        int right = mergeSortAndGetSmallSum(arr, mid + 1, R);
        int cur = mergeGenerateSmallSum(arr, L, mid, R);//整体归并产生小和

        return left + right + cur;
    }

    //测试
    public static void test2(){
        int[] arr = {1,3,2,4,5};
        int ans = mergeSortAndGetSmallSum(arr, 0, arr.length - 1);
        System.out.println(ans);//18
    }

public static void main(String[] args) {
//        test();
//        checker();
        test2();
    }

结果的确是:18

之前我们说过了,归并排序时间复杂度也就o(n*log(n))
自然是低于暴力解

如何验证你这个优化的方法对不对呢?
对数器:

//对数器之构建随机数组
    public static int[] createArray(int arrSize, int maxValue){
        int[] arr = new int[arrSize];
        for (int i = 0; i < arrSize; i++) {
            arr[i] = (int)(maxValue * Math.random());//0-N-1的随机数
        }
        return arr;
    }

    public static void checker(){
        //生成检验数组
        int[] arr = createArray(100,10);
        int[] arr2 = new int[arr.length];//赋值同样一个数组arr
        for (int i = 0; i < arr.length; i++) {
            arr2[i] = arr[i];//copy即可
        }
        int[] arr3 = new int[arr.length];//赋值同样一个数组arr
        for (int i = 0; i < arr.length; i++) {
            arr3[i] = arr[i];//copy即可
        }

        //绝对的正确方法——暴力方法,或系统函数,操作arr
        int sum1 = smallSum1(arr);
        //优化方法,操作arr2
        int sum2 = smallSum2(arr2);
        //复习手撕的
        int sum3 = mergeSortAndGetSmallSum(arr3, 0, arr3.length - 1);
        System.out.println(sum1);
        System.out.println(sum2);
        System.out.println(sum3);

        //然后校验
        System.out.println( (sum2 != sum1 || sum1 != sum3) ? "oops,wrong!" : "right!");
    }
public static void main(String[] args) {
//        test();
        checker();
//        test2();
    }

结果:

5274
5274
5274
right!

全部通过


总结

提示:重要经验:

1)当要求的题目,暗示,依赖:比某个位置i左边或者右边大,或者小,的那些数,你要敏感地想到,归并的思想
2)因为归并思想,可以统计左边和右边的大小关系,找到有几个数比p1大,有几个数比p1小
3)数组小和问题,相信你见过之后,熟悉了,今后就不在面试场上怕了。

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

冰露可乐

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值