统计可见山峰对的数量

可见山峰对的数量有多少对?

提示:不要怕难题,难题考验人的能力


题目

可见山峰对是这么定义的:
环形山,很多山排在一起,一座山高度为x,顺时针看(或者逆时针看都行),x的邻居直接可见,叫可见山峰对,非邻居的话:可以看见有另一座山高y,而且xy之间,途中任意山峰z,均z<=ming(x,y),则 称xy为可见山峰对 。可以想象一下,反正,我在x山,往那边看(有一条通路),x能看到的山,与x组合起来都叫可见山峰对。
看图:
图1
现在给你一个数组arr,代表山峰的高度,0位置和N-1位置是相邻的环,请问你在这个还型山中,可见山峰对的数量有多少对?


一、审题

示例:比如:
1 3 4 5 2
首先,N=5,邻居相互就有N对=5对,然后非邻居,满足可见山峰对的定义23,35,因,23之间任意山1<=2,35之间任意山1,2<=3。而其他的都不满足这个条件,故,总共有7对。
图2

题意明白了吧。


二、解题

本题的方法要熟悉:
分为2种情况:

一、当arr无重复值,o(1)解决,2N-3个可见山峰对

为啥呢?
看图,用特定的方法推导即可:
数组中最大值为arr,次大值为arr2,他们俩是邻居就算1对
然后,剩下的N-2个山中,每个x,从x出发,顺时针来遇到某个y,xy之间所有山<=x,y最多找到max,只能有1对;
逆时针也一样,某个z,xz之间所有山<=x,z最多找到max2,只能有1对;
因此,规定max和max2之后,x顺时针,和逆时针就能找到2对,总的2*(N-2)对
加上max和max2邻居,一共2N-4+1=2N-3对。

图3
代码:

public static int mountainPairsNum(int[] arr){
        if(arr == null || arr.length < 2) return 0;
        int N = arr.length;
        if (N == 2) return 1;
        if (N == 3) return 3;

        return 2 * N - 3;
    }

二、当arr有重复值时

比如:3个重复的2,那任意2个2之间都能组成可见山峰对,这3个2与5,与7也都组成了可见山峰对,就比较复杂了
图4
我们需要设计o(n)复杂度的算法
一次遍历数组就能找到任意x左右最大值max和max2来,这里用几个很重要的知识点:单调栈
然后在单调栈弹出的时候清算结果
在这里插入图片描述

大流程:

(1)找打arr中的最大值max放栈底,做最大,因此栈stack是由底到顶部,是从大到小的单调栈,用来干嘛?
max> x <y统计由x组成的山峰对有多少?
(2)从max下一个位置(不论顺时针还是逆时针找),当山x小于max就往栈里面压,保持降序压
(3)遇到新的山y>栈顶高度,这时候就出现了max> x <y,需要将x弹出,结算x的可见山峰对,如果x的个数为k,
如果k==1个,那就是2个山峰对,x见max,x见y
如果k>1个,对外首先是2k(因为k个2,每一个2与max和y都组成了山峰对),对内(222之间)有Ck2组合数个,k个2任选俩有多少组合,他们内部都是可见的山峰对;
(4)当arr遍历到maxindex处,停止,看stack的情况:
——当stack的size>2跟(3)一样,挨个弹出,弹出的时候,y<x<max,也是对外2k个,对内Ck2个
——当stack的size=2时,看下面压了几个max:比如t个,则不管压了t几个,而对内部首先是Ck2对(222之间)
然后看压了t几个
当t=1时,就k对(因为k=1,或k>1)都一个max就k对
当t>1时,俩边max就不一样了,那就是2k对
——当stack的size=1时,就剩下max了,看max的k有几个
k=1时,0对,不成对,Ck2=C12=0
k=2时,1对,相互一对,Ck2=C22=1
k>2时,就是Ck2算组合

由于这里经常用Ck2(k):
在这里插入图片描述

代码规整一下:

//从k个相同的元素内部,随意找俩元素组成一个相互可见的山峰对,叫CK2(k),排列组合
    public static int CK2(int k){
        //组合公式C(n,m)==A(n,m)/A(m,m),现在n==k,m==2
        //C(k,2)==A(k,2)/A(2,2)==k(k-1)/2
        return k == 1 ? 0 : (k *(k - 1)/2);
    }

从maxindex的下一个位置开始寻找:循环坐标方便从N-1找到0位置:

//首先搞定循环坐标
    //来到i位置,山总数,就是arr长N=arr.len
    //则i的逆时针,下一个坐标是
    public static int nextIndex(int i, int N){
        return i == N - 1 ? 0 : i + 1;//没到终点,+1,否则就回到0
    }
    //来到i位置,逆时针上一个坐标是
    public static int lastIndex(int i, int N){
        return i == 0 ? N - 1 : i - 1;//往顺时针,左走
    }

往栈里面放的时候,还要统计山x出现了k>=1次,那放入栈就得是一个对象:

//咋记录次数呢,用栈放一个对象
    public static class Record{
        public int value;
        public int count;

        public Record(int v){
            value = v;
            count = 1;//默认一次,进来就是
        }
    }

算法大流程,单调栈统计可见山峰对的个数:

public static int mountainPairsNumRepeat(int[] arr){
        if (arr == null || arr.length < 2) return 0;

        int N = arr.length;
        //先找最大值的位置
        int maxIndex = 0;
        for (int i = 0; i < N; i++) {
            maxIndex = arr[i] > arr[maxIndex] ? i : maxIndex;//有更大的更新
        }

        //压入最大那个
        Stack<Record> stack = new Stack<>();
        stack.push(new Record(arr[maxIndex]));//最大值作为栈底,永远不出栈的
        int i = nextIndex(maxIndex, N);//从max下一个位置开始压栈
        int ans = 0;//结果

        //只要没到max处,继续压栈
        while (i != maxIndex){
            //但凡图中遇到相等,count++,小于栈顶,新加,大于栈顶结算
            if (arr[i] == stack.peek().value) stack.peek().count++;
            else if (arr[i] < stack.peek().value) stack.push(new Record(arr[i]));//新加
            while (arr[i] > stack.peek().value){
                int k = stack.pop().count;//只需要栈顶的次数,不需要值的
                ans += 2 * k + CK2(k);//对外+对内
            }

            //处理完继续下一个
            i = nextIndex(i, N);
        }

        //栈压完以后,开始清理,分三种情况
        //stack.size()>2,==2,<2
        while (stack.size() > 2){
            int k = stack.pop().count;
            ans += 2 * k + CK2(k);//和压栈那个一样,对外+对内都OK
        }
        if (stack.size() == 2){
            int k = stack.pop().count;
            //得参考现在这个栈顶了
            ans += CK2(k) + stack.peek().count == 1 ? k : 2 * k;//对内肯定不变的,对外不一定,可能只有k,也可能是2k个
        }
        //自然就是1个最后了
        ans += CK2(stack.peek().count);//对外是没有的,对内可能有,不过看情况,1个0对,2个,1对,3个及以上,可能多对,组合

        return ans;
    }

测试代码:

public static void test(){
        int[] arr2 = {5,5,5,5};
        int[] arr1 = {1,2,3,4};
        System.out.println(mountainPairsNum(arr1));
        System.out.println(mountainPairsNumRepeat(arr2));
    }

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

总结

提示:重要经验:

1)单调栈解决的问题,寻找x最近的左边/右边,比x大的/小的值,然后弹出x统计两头的信息,这个经常用的。
2)可见山峰对的重复,与不重复的情况,不重复的也可以用重复的解,只不过不重复的时候o(1)一个公式更简单,当然我们要熟悉重复的解法。

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

冰露可乐

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

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

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

打赏作者

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

抵扣说明:

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

余额充值