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.height=1,这是必然0是在湖泊的

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

懂了吧

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

在这里插入图片描述

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

在这里插入图片描述
结算cur的邻居,上面右边不能形成湖泊,但是左边那个0,0<cur.height=1,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;
        }
    }

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

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

(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有没有被染色
    }

越界的不要搞了
中途拿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();
    }

结果是很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 

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

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

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


总结

提示:重要经验:

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

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

weixin_46838716

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

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

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

打赏作者

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

抵扣说明:

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

余额充值