暂无图片
暂无图片
暂无图片
暂无图片
暂无图片

数据结构和算法【16】可见的山峰对数

皮皮克克 2023-07-21
50

点击关注公众号,干货第一时间送达



今天来点不一样的!


一、各位,请听题


题意要是没懂的话,看下面的示意图。

示意图:

(1)相邻的山峰有 {1,2}、{2,4}、{4,5}、{3,5}、{1,3} 这些山峰相互可见

(2)山峰3,沿着逆时针,经过1,2两座山峰,因为1,2山峰都比3低,所以可以经过然后到达山峰4。即表示山峰3在逆时针方向上可以看到山峰4;相反,山峰4在顺时针方向可以看到山峰3。

    但是山峰3沿着顺时钟方向呢? 下一个是山峰5,是过不去的,挡住了后面的视线,所以顺时针方向山峰3和山峰4不可见。

这就是题意了。

怎么求?

小编我刚开始看的时候也是一脸懵,

无从下手啊!

小编曾有一瞬间,想用穷举法穷举出来答案

可是,如果山峰很多呢?

让你穷举


二、解题

用单调栈!

(之前的文章:数据结构和算法【7】单调栈 2 有详细介绍单调栈,可以点击翻看一下)

我们可以先遍历给定数组arr,数组肯定有最大值,并且最大值可能不止一个,我们任意选取一个即可。

然后开始沿着一个方向上,找下一个山峰,如果下一个山峰的高度小于当前山峰,我们就把下一个山峰记录到栈中;如果下一个山峰的高度大于当前山峰,我们就弹出栈顶山峰。

重点来了!

弹出的山峰,离它最近且比它大的山峰,是不是就是下一个山峰?

此时,会弹出山峰1,压入山峰2,而对于山峰1来说,它可见的只有山峰2和山峰3,山峰2是当前遍历的元素,山峰3是栈顶元素,都是直接可得的。

最后清算栈即可,单调栈方法的详解在之前的文章:数据结构和算法【7】单调栈 2里有的,这里不再赘述。

对于本题,我们再加一个变量,用来记录山峰出现的次数,因为可能有山峰高度一样。

比如下面这个


如果有山峰高度一样,这些山峰内部是相互可见。

此时,弹出山峰3,山峰3可见的,只有当前的山峰5,和栈顶的山峰4。再弹出山峰4,山峰4可见的,只有当前的山峰5,和栈顶的山峰5。然后山峰5,次数加1。

但是!如果弹出的时候,弹出山峰的次数不是1呢?

两座山峰4相互可见,出现次数一共是2,两座山峰4相互可见,也可以看见外部的,则总数是:2*2 + C(2,2) = 5对。

如果是3座山峰4呢?  2*3 + C(2,3) = 9对。

最后,清算栈的时候注意分3个阶段,代码里面有注释。

去看看吧。



完整代码:

public class CodingDemo {

    /**
     * TODO: 计算可见山峰对数
     * @param arr
     * @return
     */

    private static int getVisibleMount(int[] arr){
        if (arr == null || arr.length < 2){
            return 0;
        }

        //1,找到任意最大值节点
        int N = arr.length;
        int maxIndex = 0;
        for (int i = 0; i < N; i++) {
            maxIndex = arr[maxIndex] < arr[i] ? i : maxIndex;
        }

        //2, 创建栈,先把找到的最大值节点存入
        Stack<Record> stack = new Stack<Record>();
        stack.push(new Record(arr[maxIndex]));

        //3, 从最大值节点,开始逆时针旋转到下一个节点
        int index = getNextIndex(maxIndex, N);

        int res = 0;//结果值
        //4, 开始遍历
        while (index != maxIndex){

            //4.1 比较遍历的节点,和栈顶val大小
            while (stack.peek().val < arr[index]){
                //弹出栈顶,获取这个元素出现的次数
                //通过排列组合计算可见山峰对数
                int k = stack.pop().times;
                res += 2*k + combine(k);
            }

            //4.2 如果当前元素等于栈顶val,则栈顶times加1,否则新建记录Record
            if (stack.peek().val == arr[index]){
                stack.peek().times++;
            } else {
                stack.push(new Record(arr[index]));
            }

            //4.3 继续下一个遍历
            index = getNextIndex(index, N);
        }

        //5,开始清算栈
        //5.1 第1阶段,
        while (stack.size() > 2){
            int k = stack.pop().times;
            //栈顶元素顺时针,逆时钟分别可以预见较高的山峰,然后栈顶元素val内部相互可见
            res += 2*k + combine(k);
        }

        //5.2 第2阶段,需要判断弹出栈顶后,栈中剩下的山峰出现了几次,
        // 如果是1次,则表示从顺时钟和逆时针看到是同一个山峰,则取 k
        // 如果不是1次,则表示从顺时针和逆时针看到的不是同一个山峰,取 2*k
        if (stack.size() == 2){
            int k = stack.pop().times;
            res += combine(k) + (stack.peek().times == 1 ? k : 2*k);
        }

        //5.3 阶段3,处理栈最后一个元素
        res += combine(stack.pop().times);
        return res;
    }

    //排列组合公式C(x,n) 的简化
    private static int combine(int k){
        return k == 1 ? 0 : (k * (k-1)/2);
    }

    //获取当前i位置节点的下一个位置,因为是环,需要考虑超出数组边界的情况
    private static int getNextIndex(int i, int size){
        return i < (size-1) ? (i+1) : 0;
    }


    private static class Record{
        int val;
        int times;

        public Record(int val) {
            this.val = val;
            this.times = 1;
        }
    }

    public static void main(String[] args) {

//        int[] arr = {5,4,3,5,4,2,4,4,5,3,2};
        int[] arr = {3,1,2,4,5};
        System.out.println(getVisibleMount(arr));
    }
}

输出:

D:\java\bin\java.exe
7



结束语:
Ok,到此为止,就是本篇文章的全部内容了。
该文章主要讲了 数据结构和算法: 可见的山峰对数。
如果各位有不懂的地方,欢迎发消息给小编,小编会进行详细地解答。
最后,请屏幕前的各位小可爱,动动你们的小手,给小编一个“在看”吧!

文章转载自皮皮克克,如果涉嫌侵权,请发送邮件至:contact@modb.pro进行举报,并提供相关证据,一经查实,墨天轮将立刻删除相关内容。

评论