• LeetCode高频题:《逆水寒》在地图的制作中,美术在地图上刷一片连通区域,连通区域自动填充,请你判断给定几个点位置,他们是否属于被刷区域


    LeetCode高频题:《逆水寒》在地图的制作中,美术在地图上刷一片连通区域,连通区域自动填充,请你判断给定几个点位置,他们是否属于被刷区域?

    提示:本题是系列LeetCode的150道高频题,你未来遇到的互联网大厂的笔试和面试考题,基本都是从这上面改编而来的题目
    互联网大厂们在公司养了一大批ACM竞赛的大佬们,吃完饭就是设计考题,然后去考应聘人员,你要做的就是学基础树结构与算法,然后打通任督二脉,以应对波云诡谲的大厂笔试面试题!
    你要是不扎实学习数据结构与算法,好好动手手撕代码,锻炼解题能力,你可能会在笔试面试过程中,连题目都看不懂!比如华为,字节啥的,足够让你读不懂题
    在这里插入图片描述


    题目

    《逆水寒》在地图的制作中,有时候需要美术在地图上刷一片区域,用与特殊判定。地图是按照各自进行数据存储的,需要刷的区域很大的时候,美术的工作量就会比较大,美术找到你,希望你给她做一个工具,如果绘制的格子能形成一个封闭区域,就自动填充中间区域(如果存在多个封闭区域,都会自动填充)。

    如下图,其中一个封闭区域展示:
    在这里插入图片描述

    封闭的定义:每个格子周围8个点称为邻居,如果该格子的邻居也被刷了,说明这俩格子是相邻连通的。如果被刷格子能通过相邻连通形成一个封闭区域,所有封闭区域都需要进行自动填充,如果不存在封闭区域,不进行自动填充(注意,此时,美术刷的格子还是属于被刷的区域)。

    输入描述:
    N M,N和M是整数,分别表示地图的长和宽,范围是100–100000
    P,美术一共刷了P个格子,P范围是0–100000,后续跟P个个格子的位置xy,范围是不超过N,M,每个数据用空格分开,中间是逗号
    X1,Y1 X2,Y2 等等
    Q,Q是需要查询的点数,你要判定点xy是否在被刷区域内,Q范围是0–100000,后续跟P个个格子的位置xy,范围是不超过N,M,每个数据用空格分开,中间是逗号
    X1,Y1 X2,Y2 等等

    输出描述:
    R1R2R3等等
    Q个检测结果,1表示属于被刷区,0表示不属于被刷区


    一、审题

    示例:
    100 100
    14
    1,1 1,2 1,3 1,4 1,5 2,1 2,5 3,1 3,4 3,5 4,1 4,2 4,3 4,4
    10
    0,0 1,1 2,2 3,3, 4,4 5,5 60,60 70,70 80,80 90,90

    输出:
    011100000


    二、解题

    这题目是不是读起来就挺麻烦的?

    没错,这就是互联网大厂的出题风格,你需要足够的功底,学透数据结构与算法,才能解决这种问题

    本题,说白了就干一件事

    美术那边,给你一个二维数组arr,arr格子里面不是0就是1,1时美术给你刷好的形状(有P个点),
    在这里插入图片描述

    你需要干嘛?

    设计算法,判断哪些点属于这个形状里面,然后将其自动填充好

    在这里插入图片描述

    现在给你Q个位置点(x,y),问你,这些位置到底是属于被刷的区域,还是没被刷的区域?

    比如,下图红色点就是属于被刷的区域(1),蓝色点就是没杯刷的区域(0)

    在这里插入图片描述
    懂???

    那你咋表示我P个点,有没有组成了封闭区域呢???这是本题的难点

    你到处想什么标记,都没有用的,这形状不规则,你是没法记录那些地方合成了一个封闭形状的

    这个题,如果之前你没有见过类似的连通湖的思想,是很难想到解决方案的


    连通性的问题,要敏锐地想到连通湖思想

    我们怎么着能确定一个区域可以构成一片湖?那就是水桶短板理论

    比如下图:
    在这里插入图片描述
    外围都是1,板子高,中间都是0,板子低,你可以认为整体构成了一个湖泊,中间矮,外围高,可以盛水——它就是一个连通湖

    这就是连通湖的思想,也是大自然自然的构造

    你看下图
    在这里插入图片描述
    外围有一块短板,板子高度为0,显然,这个湖泊的水都从豁口溜出去了,绝对没法盛水
    它也就不可能放一点水,那自然就不是连通湖

    现在你知道啥叫连通湖了吧?

    okay,我咋知道我来到一个点,就知道它在不在湖泊里面呢???

    用小根堆来模拟,先遍历那些板子最低的非湖泊区域,再遍历各个湖泊
    用小根堆来模拟,先遍历那些板子最低的非湖泊区域,再遍历各个湖泊
    用小根堆来模拟,先遍历那些板子最低的非湖泊区域,再遍历各个湖泊

    这句话你可记住了,小根堆干的就是这件事,
    小根堆heap放啥呢?放节点cur,内部有节点的位置xy,还有板子的高度(这里刷了颜色就认为是高度1,没刷就是0)

    (1)先把arr四周的节点放入小根堆heap,heap的排序规则就是板子的高度,高度低的在堆顶,先弹出,arr四周因为都没刷颜色,所以他们必然是最低的短板(你可以刷,问题不大,好多都是0)
    在这里插入图片描述
    (2)然后heap开始循环弹出cur,cur必然是当前最低的板子,也就是瓶颈,也就是短板,最开始出来的一定是0
    然后咱们结算cur的邻居(上下左右四个点),越界的不管,经过heap的不结算
    结算规则是什么?
    邻居neighbor的高度height,如果小于cur的高度,就认为邻居在湖泊里面,可以把它刷为1
    然后把没进过堆的邻居放入堆
    邻居neighbor的高度height,如果大于等于cur的高度,就认为邻居不在湖泊里面,忽略不管

    当你不断循环(2),直到heap空,此时,连通区域内的湖泊已经全部自动填充好了,不是连通的湖泊一定都是0

    为啥呢???你怎么就知道cur的邻居它在湖泊里面呢????

    是因为我小根堆模拟的时候,非连通湖泊区域,一定先弹出来,因为它高度都是0,小,所以先弹出来
    也就是小根堆弹出的一定都是非湖泊区域

    你看看下图,cur高度为0,左右邻居已经进过堆heap,不管
    上面那个0,没进过,结算
    因为上面那个邻居neighbor的高度height=0,确实大于等于cur的高度0,就认为邻居不在湖泊里面
    确实啊,两个都是0,构不成湖泊的
    在这里插入图片描述
    现在假如四周都弹出来了

    heap下一次会弹出谁呢?一定是上面那个圈0

    因为它小啊【小根堆小根堆嘛】
    在这里插入图片描述
    结算cur的邻居,左右1,上面0,因为10都是>=cur.height=0的,所以他们都没法构成湖泊的,不染色

    heap下一次会弹出谁?小的那个呗,上图cur上面的那个0

    在这里插入图片描述
    这一次cur是红圈那个0,结算邻居,左边0,右边,上面1,因为他们都>=cur.height=0,所以都不能构成湖泊

    下一次弹出谁?cur左边那个0【因为它小】

    在这里插入图片描述
    结算邻居左边,上面俩1,都>=cur.height=0,所以他们也不会在湖泊里面

    你发现了吗???
    cur一直处于非湖泊区域,heap,一定让你先遍历完非湖泊区域
    如果没有封闭湖泊,我们heap弹出来的cur,他的邻居高度,永远>=cur.height的,没法形成湖泊

    懂??

    我们来看一个可以构成湖泊的例子

    不妨设一圈红色的点都弹出了,现在最后一个弹出cur
    在这里插入图片描述
    结算cur的邻居
    上面那个1没法构成湖泊,因为它大,所以 直接进堆就行

    在这里插入图片描述
    这时候你就会发现,非湖泊区域,通过小根堆,已经都遍历过了
    所以呢?
    下一次开始弹出cur=1了
    在这里插入图片描述
    结算cur=1的左右为1,不会构成湖泊,但是上面那个0,显然0

    你想说,万一对面有缺口呢??不可能!!!
    因为有缺口的话,一定轮不到你先做cur=1弹出来,你对面要是有豁口,一定先弹出那边的0,非湖泊区域会被小根堆先遍历完的哦!!

    懂了吧

    所以这个时候咱们把cur上面那个邻居0,刷成1!并让它进堆heap

    在这里插入图片描述

    继续弹出一个,比如就是现在红色这个1,cur=1

    在这里插入图片描述
    结算cur的邻居,上面右边不能形成湖泊,但是左边那个0,0 之前非湖泊的0早遍历过了,所以现在的0一定是湖泊里面的0

    okay,整个过程就是连通湖的思想

    剩下的就是手撕代码

    (1)现将arr四周的点封装为Node,放入heap

    节点封装

        public static class Node{
            public int value;
            public int row;
            public int col;
    
            public Node(int v, int r, int c){
                value = v;
                row = r;
                col = c;
            }
        }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11

    小根堆heap排序规则:
    高度value=height,小的排上面

        //小根堆的比较器
        public static class heapComaprator implements Comparator<Node> {
            @Override
            public int compare(Node o1, Node o2){
                return o1.value - o2.value;
            }
        }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7

    (2)heap循环弹出节点cur,结算cur,没进过堆的那些节点,他们高度小于cur高度,就可以算湖泊,染色,放入堆
    如果他们高度>=cur高度,就不能算湖泊,不染色,放入堆
    循环(2)搞定自动填充

        //开始计算填充
        public static int[][] draw(int[][] arr){
            if (arr == null || arr.length == 0 || arr[0] == null || arr[0].length == 0) return null;
    
            //把小根堆,标记数组准备好
            int N = arr.length;
            int M = arr[0].length;
            PriorityQueue<Node> heap = new PriorityQueue<>(new heapComaprator());
            
            boolean[][] enter = new boolean[N][M];
    //        int[][] arr = new int[N][M];//这是我要的染色结果
            
            //将四周全放入heap
            for (int col = 0; col < M - 1; col++) {
                //最后一个元素不需要放,0行
                enter[0][col] = true;
                heap.add(new Node(arr[0][col], 0, col));
            }
            for (int row = 0; row < N - 1; row++) {
                //最后一列
                enter[row][M - 1] = true;
                heap.add(new Node(arr[row][M - 1], row, M -1));
            }
            for (int col = M - 1; col > 0; col--) {
                //最后一行
                enter[N - 1][col] = true;
                heap.add(new Node(arr[N - 1][col], N - 1, col));
            }
            for (int row = N - 1; row > 0; row--) {
                //第一列
                enter[row][0] = true;
                heap.add(new Node(arr[row][0], row, 0));
            }
    
            //统计水量——也就是开始遍历染色
            int max = Integer.MIN_VALUE;//中途记录所有点的染色数值,0就是低桶,1是高桶
            while (!heap.isEmpty()){
                Node cur = heap.poll();//弹出
                max = Math.max(max, cur.value);//更新
    
                //四个邻居结算,并加入heap,————————保证不越界,没进过堆————————
                int row = cur.row;
                int col = cur.col;//行列
                //cur的板子是1,必然染1
                if (arr[row][col] == 1) arr[row][col] = 1;//这是美术本人给你画好的,没的说
    
                if (row > 0 && !enter[row - 1][col]){
                    //cur上一个
                    if (arr[row - 1][col] < arr[row][col]) arr[row - 1][col] = 1;//邻居板子高度比我cur小,才能算湖泊,它染1,否则没法染
                    enter[row - 1][col] = true;
                    heap.add(new Node(arr[row - 1][col], row - 1, col));//加入
                }
                if (row < N - 1 && !enter[row + 1][col]){
                    //cur下一个
                    if (arr[row + 1][col] < arr[row][col]) arr[row + 1][col] = 1;//邻居板子高度比我cur小,才能算湖泊,它染1,否则没法染
                    enter[row + 1][col] = true;
                    heap.add(new Node(arr[row + 1][col], row + 1, col));//加入
                }
                if (col > 0 && !enter[row][col - 1]){
                    //cur左一个
                    if (arr[row][col - 1] < arr[row][col]) arr[row][col - 1] = 1;//邻居板子高度比我cur小,才能算湖泊,它染1,否则没法染
                    enter[row][col - 1] = true;
                    heap.add(new Node(arr[row][col - 1], row, col - 1));//加入
                }
                if (col < M - 1 && !enter[row][col + 1]){
                    //cur右一个
                    if (arr[row][col + 1] < arr[row][col]) arr[row][col + 1] = 1;//邻居板子高度比我cur小,才能算湖泊,它染1,否则没法染
                    enter[row][col + 1] = true;
                    heap.add(new Node(arr[row][col + 1], row, col + 1));//加入
                }
            }
    
            return arr;//返回染好色的数组,直接就可以o(1)知道点xy有没有被染色
        }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67
    • 68
    • 69
    • 70
    • 71
    • 72
    • 73
    • 74

    越界的不要搞了
    中途拿enter记录进过堆的,不要再进去了
    判断是湖泊可以刷色

    测试一把:

        //题目查询的结果:
        public static void query(int[][] arr){
            int[][] ans = draw(arr);
    
            int[][] positions = {
                    {0,0},
                    {1,1},
                    {2,2},
                    {3,3},
                    {4,4}
            };
            String s = "";//结果字符串
            for (int i = 0; i < positions.length; i++) {
                int x = positions[i][0];
                int y = positions[i][1];
                s += String.valueOf(ans[x][y]);
            }
    
            System.out.println(s);
        }
    
        public static void test2(){
            int[][] arr = {
                    {0,0,0,0,0,0,0,0},
                    {0,1,1,1,1,1,1,0},
                    {0,1,0,0,0,0,1,0},
                    {0,1,1,1,1,1,1,0},
                    {0,0,0,0,0,0,0,0}};//到时候中间那几个0,变1了就
    
            query(arr);
    
    //        int[][] ans = draw(arr);
    //        for (int i = 0; i < ans.length; i++) {
    //            for (int j = 0; j < ans[0].length; j++) {
    //                System.out.print(ans[i][j] +" ");
    //            }
    //            System.out.println();
    //        }
        }
    
        public static void main(String[] args) {
            test2();
        }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43

    结果是很OK的

    01110
    0 0 0 0 0 0 0 0 
    0 1 1 1 1 1 1 0 
    0 1 1 1 1 1 1 0 
    0 1 1 1 1 1 1 0 
    0 0 0 0 0 0 0 0 
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6

    你就是有多个封闭区域,也是这么玩的,非湖泊一定先进堆
    最后开始弹出封边边的时候,中间那些在湖泊内的一定可以被刷上颜色

    这个连通湖的思想,恐怕不是一般标记可以搞定的,这就是思想决定破解题目的办法

    注意:这是网易秋招笔试原题题目4,难,
    网易就是全国最难的题,没办法,还有华为和字节,都是全国最难的,你必须要有扎实的功底才能搞定它


    总结

    提示:重要经验:

    1)连通性的问题,往往考虑连通湖,用小根堆模拟非湖泊区域先遍历,然后遍历湖泊区域,弹出节点cur,结算cur的邻居,满足条件才能算湖泊内部的邻居,可以做操作,或者统计结果
    2)全国最难的三家公司:网易,华为,字节,你要是想进他们,就要脚踏实地学习数据结构与算法,否则你题目都看不懂
    3)笔试求AC,可以不考虑空间复杂度,但是面试既要考虑时间复杂度最优,也要考虑空间复杂度最优。

  • 相关阅读:
    【优化器】BGD、SGD、MSGD、Momentum、Adagrad、RMSPprop、Adam
    ASEMI代理艾赛斯DSP25-12A,整流二极管DSP25-12A
    有哪些前端可以做的性能优化点
    SpringBoot集成MyBatis-Plus实现增删改查
    【MindSpore易点通】MindSpore Data经验解析
    C/C++每日一练:实现选择排序
    XML API
    实验五 定时器
    23种设计模型
    谷歌Chrome 100正式版发布:启用全新图标,修复28个安全漏洞
  • 原文地址:https://blog.csdn.net/weixin_46838716/article/details/126336298