0x01.问题

在一个 10^6 x 10^6 的网格中,每个网格块的坐标为 (x, y),其中 0 <= x, y < 10^6。

我们从源方格 source 开始出发,意图赶往目标方格 target。每次移动,我们都可以走到网格中在四个方向上相邻的方格,只要该方格不在给出的封锁列表 blocked 上。

只有在可以通过一系列的移动到达目标方格时才返回 true。否则,返回 false。

输入示例:blocked = [[0,1],[1,0]], source = [0,0], target = [0,2]

输出示例:false

解释:从源方格无法到达目标方格,因为我们无法在网格中移动。

提示:

   0 <= blocked.length <= 200

2     blocked[i].length == 2

3    0 <= blocked[i][j] < 10^6

4    source.length == target.length == 2

  0 <= source[i][j], target[i][j] < 10^6

6    source != target​​​​​​​

C++函数形式为      bool isEscapePossible(vector<vector<int>>& blocked, vector<int>& source, vector<int>& target) 


0x02.问题分析

这个问题耗费四个多小时,发现了一个真理:思路很清晰,细节是魔鬼

先大概了解一下这个问题,我们大致可以得到下列信息:

  1. 这是一个迷宫问题(废话,题目就看出来了)。
  2. 这是一个比较大的迷宫(1000000*1000000的迷宫,着实吓人)。
  3. 迷宫中会以给定的二维数组设置障碍。
  4. 题目给出起点,终点,并且起点终点不相同。
  5. 障碍数最多是200。

既然这是一个迷宫问题,所以大致思路其实已经出来了,无非就是DFS,BFS,在这里发现BFS会好一些,于是我一直选择的是BFS,确定了方向,就毫不犹豫的写出了代码,于是有了我的第一次失败尝试。。。。

0x03.失败的尝试1--循规蹈矩

class Solution {
public:bool Blocked(vector<vector<int>>& blocked, int x, int y) {for (int i = 0; i < blocked.size(); i++) {if (blocked[i][0] == x && blocked[i][1] == y) return false;}return true;}bool isEscapePossible(vector<vector<int>>& blocked, vector<int>& source, vector<int>& target) {if (blocked.empty())return true;int flag = 0;int x1 = source[0];int y1 = source[1];int x2 = target[0];int y2 = target[1];queue<int> queuei;queue<int> queuej;queuei.push(x1);queuej.push(y1);while (!queuei.empty()) {int x3 = queuei.front();int y3 = queuej.front();queuei.pop();queuej.pop();if (x3 < 0 || y3 < 0 || x3 == 1000000 || y3 == 1000000 || !Blocked(blocked, x3, y3))continue;if (x3 == x2 && y3 == y2) {queue<int> emptyi;queue<int> emptyj;swap(emptyi, queuei);swap(emptyj, queuej);flag = 1;break;}int dx[4] = { 0,0,1,-1 };int dy[4] = { 1,-1,0,0 };for (int index = 0; index < 4; index++) {int next_x = x3 + dx[index];int next_y = y3 + dy[index];queuei.push(next_x);queuej.push(next_y);}}return (flag == 1) ? true : false;}
};

其实这段代码真的很差,只不过利用了常规的BFS思路,而且还犯了一个严重的错误,就是---没有设置相应的标志,导致会重复访问已访问过的点,于是,我就使用了一个set表,为string类型的,每次把x:y以字符串的形式记录进去,访问下一个结点的时候,就拿出来判断一下,存在就不访问了。于是写出了我的第二段代码。

0x04.失败的尝试2--盲目搜索

class Solution {
public:bool Blocked(vector<vector<int>>& blocked, int x, int y) {for (int i = 0; i < blocked.size(); i++) {if (blocked[i][0] == x && blocked[i][1] == y) return false;}return true;}bool isEscapePossible(vector<vector<int>>& blocked, vector<int>& source, vector<int>& target) {if (blocked.empty()) return true;int x1 = source[0];int y1 = source[1];int x2 = target[0];int y2 = target[1];queue<int> queuei;queue<int> queuej;queuei.push(x1);queuej.push(y1);set<string> seen;seen.insert(to_string(x1)+":"+to_string(y1));//加入的代码while (!queuei.empty()) {int x3 = queuei.front();int y3 = queuej.front();queuei.pop();queuej.pop();int dx[4] = { 0,0,1,-1 };int dy[4] = { 1,-1,0,0 };for (int index = 0; index < 4; index++) {int next_x = x3 + dx[index];int next_y = y3 + dy[index];if (next_x < 0 || next_y < 0 || next_x == 1000000 || next_y == 1000000 || !Blocked(blocked, next_x, next_y)) continue;if (seen.count(to_string(next_x) + ":" + to_string(next_y))) continue;//加入的代码if (next_x == x2 && next_y == y2) return true;queuei.push(next_x);queuej.push(next_y);seen.insert(to_string(next_x) + ":" + to_string(next_y));//加入的代码}}return false;}
};

将判断的代码加入后,自我感觉良好,于是赶紧的测试了一组数据,发现,咦,超时,我再仔细看一下数据的时候,发现blocked里的值并不多,这到底是为什么呢?我模仿着计算机运行了一下代码,发现,存在一个非常大的漏洞。

这是一个巨大无比的迷宫,我要这样盲目的搜索的话,想都不用想,肯定会超时,那么我们就得优化一下,到底怎么优化呢?

起初我想的是把大地图变成小地图,通过离散,这样应该是有可能的,但是,这样我几乎要重写代码,不是很甘心,于是我又看到了提示,blocked的最大值为200,我开始围绕着这个200开始思考。

整个迷宫如此巨大,但是障碍物才200,这200障碍物,最多可以堵住多少个点呢?

单纯看这个障碍物,能堵住的不多,但是如过和边界搭配起来,就有点多了,容易想到,当这些障碍物形成一条直线,且成为等腰直角三角形的斜边的时候,能堵住最多的点。如下图:

                 0      _________________|O O O O O O O X|O O O O O O X|O O O O O X|O O O O X|O O O X|O O X|O X200     |X

这样200个障碍物,最多能堵住1+2+3+...+199,也就是19990个,那么我们可以这样想,是不是只要它走了19900个点,还可以继续走,是不是就没有被堵住,可以到终点呢,于是,我写出了第三段代码。

0x05.失败的尝试3--单向思维的局限

class Solution {
public:bool Blocked(vector<vector<int>>& blocked, int x, int y) {for (int i = 0; i < blocked.size(); i++) {if (blocked[i][0] == x && blocked[i][1] == y) return false;}return true;}bool isEscapePossible1(vector<vector<int>>& blocked, vector<int>& source, vector<int>& target) {if (blocked.empty()) return true;int m = blocked.size();//加入的代码int maxcount = m * (m - 1) / 2;//加入的代码int x1 = source[0];int y1 = source[1];int x2 = target[0];int y2 = target[1];queue<int> queuei;queue<int> queuej;queuei.push(x1);queuej.push(y1);set<string> seen;seen.insert(to_string(x1) + ":" + to_string(y1));while (!queuei.empty()) {int x3 = queuei.front();int y3 = queuej.front();queuei.pop();queuej.pop();int dx[4] = { 0,0,1,-1 };int dy[4] = { 1,-1,0,0 };for (int index = 0; index < 4; index++) {int next_x = x3 + dx[index];int next_y = y3 + dy[index];if (next_x < 0 || next_y < 0 || next_x == 1000000 || next_y == 1000000 || !Blocked(blocked, next_x, next_y)) continue;if (seen.count(to_string(next_x) + ":" + to_string(next_y))) continue;if (next_x == x2 && next_y == y2) return true;queuei.push(next_x);queuej.push(next_y);seen.insert(to_string(next_x) + ":" + to_string(next_y));}if (seen.size() > maxcount) return true;//加入的代码}return false;}
};

于是赶紧的又试了一下,发现,又有一个非常简单的例子过不了,就是终点的四周都被障碍物包围住,一想,确实,为什么我的代码会输出true呢,毫无疑问,这个true肯定是达到了最大的次数输出的,可是这四个点已经把终点围起来了,它还到处走有什么用呢?

难道是我上一次的思想错了??不能这样理解???

于是我假设起点也也被围起来了,模拟着运行了一下,发现,很快就会返回false,因为没有路可以走了,假如这个起点和终点换一下呢??

就是这个换一下,我发现,原来我之前假设的障碍物最多能包围的点是对于一个点而言的,也就是说,没有同时把起点终点考虑进去。

逆向思考一下,是不是只要我们把终点和起点互换一下再执行一遍,就可以了呢??

确实,这样是可行的。

0x06.失败的尝试4--忽略效率,滥用数据结构

class Solution {
public:bool Blocked(vector<vector<int>>& blocked, int x, int y) {for (int i = 0; i < blocked.size(); i++) {if (blocked[i][0] == x && blocked[i][1] == y) return false;}return true;}bool isEscapePossible(vector<vector<int>>& blocked, vector<int>& source, vector<int>& target) {//加入的代码return isEscapePossible1(blocked, source, target) && isEscapePossible1(blocked, target, source);}bool isEscapePossible1(vector<vector<int>>& blocked, vector<int>& source, vector<int>& target) {if (blocked.empty()) return true;int m = blocked.size();int maxcount = m * (m - 1) / 2;int x1 = source[0];int y1 = source[1];int x2 = target[0];int y2 = target[1];queue<int> queuei;queue<int> queuej;queuei.push(x1);queuej.push(y1);set<string> seen;seen.insert(to_string(x1) + ":" + to_string(y1));while (!queuei.empty()) {int x3 = queuei.front();int y3 = queuej.front();queuei.pop();queuej.pop();int dx[4] = { 0,0,1,-1 };int dy[4] = { 1,-1,0,0 };for (int index = 0; index < 4; index++) {int next_x = x3 + dx[index];int next_y = y3 + dy[index];if (next_x < 0 || next_y < 0 || next_x == 1000000 || next_y == 1000000 || !Blocked(blocked, next_x, next_y)) continue;if (seen.count(to_string(next_x) + ":" + to_string(next_y))) continue;if (next_x == x2 && next_y == y2) return true;queuei.push(next_x);queuej.push(next_y);seen.insert(to_string(next_x) + ":" + to_string(next_y));}if (seen.size() > maxcount) return true;}return false;}
};

我想,这次应该可以了,赶紧提交了,结果,超时了,我就纳闷了,为什么会超时呢?不是最多搜索20000步吗,为什么会超时,我再三思考,并没有理论错误,于是,用java写了类似的代码:

class Solution {static int dirs[][] = new int[][]{ {0,1}, {1,0}, {-1,0}, {0,-1} };static int limit = (int)1e6;public boolean isEscapePossible(int[][] blocked, int[] source, int[] target) {Set<String> blocks = new HashSet<>();for (int block[] : blocked)blocks.add(block[0] + ":" + block[1]);return BFS(source, target, blocks) && BFS(target, source, blocks);}public boolean BFS(int[] source, int[] target, Set<String> blocks) {Set<String> seen = new HashSet<>();seen.add(source[0] + ":" + source[1]);Queue<int[]> queue = new LinkedList<>();queue.offer(source);while (!queue.isEmpty()) {int cur[] = queue.poll();for (int dir[] : dirs) {int nextX = cur[0] + dir[0];int nextY = cur[1] + dir[1];if (nextX < 0 || nextY < 0 || nextX >= limit || nextY >= limit) continue;String key = nextX + ":" + nextY;if (seen.contains(key) || blocks.contains(key)) continue;if (nextX == target[0] && nextY == target[1]) return true;queue.offer(new int[] {nextX, nextY});seen.add(key);}if (seen.size() == 20000) return true;}return false;}
}

一提交,竟然过了,只不过时间长一点而已,我就更加纳闷了,代码思路一模一样,为什么在C++里面就超时了??

我再三比较,发现,只有一个地方是不同的,就是,java里面用来记录的是hashset,在C++里面用来记录的是set,按道理没多少差别啊。

后来查找资料发现c++的set是红黑树,而Java的是哈希表,这样就好解释了,说明C++的set在查找的时候,因为使用的是字符串类型,所以效率很低,那我怎么改进呢??

首先我想的是用vector,但是一写代码,发现,可能效率更低了,因为红黑树毕竟是人家封装好的,比我这个二维数组肯定要高效,那怎么办呢?

我就采取了一点小机灵,就是模仿哈希表,我每次存数据的时候,就把10*x+y存进去,之后,只要继续判断10*x+y存不存在就行了。

0x07.C++的最终提交代码

class Solution {
public:bool Blocked(vector<vector<int>>& blocked, int x, int y) {for (int i = 0; i < blocked.size(); i++) {if (blocked[i][0] == x && blocked[i][1] == y) return false;}return true;}bool isEscapePossible(vector<vector<int>>& blocked, vector<int>& source, vector<int>& target) {return isEscapePossible1(blocked, source, target) && isEscapePossible1(blocked, target, source);}bool isEscapePossible1(vector<vector<int>>& blocked, vector<int>& source, vector<int>& target) {if (blocked.empty()) return true;int m = blocked.size();int maxcount = m * (m - 1) / 2;int x1 = source[0];int y1 = source[1];int x2 = target[0];int y2 = target[1];queue<int> queuei;queue<int> queuej;queuei.push(x1);queuej.push(y1);set<int> seen;seen.insert(10 * x1 + y1);while (!queuei.empty()) {int x3 = queuei.front();int y3 = queuej.front();queuei.pop();queuej.pop();int dx[4] = { 0,0,1,-1 };int dy[4] = { 1,-1,0,0 };for (int index = 0; index < 4; index++) {int next_x = x3 + dx[index];int next_y = y3 + dy[index];if (next_x < 0 || next_y < 0 || next_x == 1000000 || next_y == 1000000 || !Blocked(blocked, next_x, next_y)) continue;if (seen.count(10 * next_x + next_y)) continue;if (next_x == x2 && next_y == y2) return true;queuei.push(next_x);queuej.push(next_y);seen.insert(10 * next_x + next_y);}if (seen.size() > maxcount) return true;}return false;}
};

这次提交竟然过了,哈哈,说明这个方法还是可以提升效率的。

注意:

这段代码从理论上来说,应该是错误的,因为我把10*x+y存进去的时候,并没有考虑散列冲突的问题,其实,是存在很多对数字可以满足10*x+y是相等的,数据不唯一,就肯定会有数据使得这段代码出错。

我利用的就是这种判题的机制,它测试的数据毕竟有穷,而且出题人也无法猜出我的散列函数是什么,所以,能通过所有测试点的几率非常大,如果不能通过,就继续换一个散列函数,总会有全部通过的。

这样的方法,成功的把巨大的二维的标志点,变成了一维,效率提高非常明显。

严格意义上讲,失败4的第一段C++代码是正确的,不过效率太低,题目超时了。

也可以通过其它的数据结构进行改进。

0x08.最终感悟

细节很重要!!!

明白常用数据结构的底层实现方法也很重要!!!

ATFWUS  --Writing  By 2020--03--16

逃离大迷宫--不断思考的BFS相关推荐

  1. LeetCode1036. 逃离大迷宫(BFS)

    1036. 逃离大迷宫 在一个 106 x 106 的网格中,每个网格上方格的坐标为 (x, y) . 现在从源方格 source = [sx, sy] 开始出发,意图赶往目标方格 target = ...

  2. 【力扣时间】【1036】【困难】逃离大迷宫

    逃离大迷宫 1.读题 2.审题 3.思路 4.开工! 5.解读 6.提交 7.学习大牛们 8.总结 困难题来了! 1.读题 题目 今天的困难题属于从来没有接触过的类型. 虽然解法还是基于bfs的,但思 ...

  3. CF_329_B----AcWing_3825_逃离大森林(BFS究极模板)

    原题链接:https://www.acwing.com/problem/content/3828/ 你是一个宝可梦饲养员,你正在进行你的冒险之旅. 当前,你的目标是逃离飞鸟森林. 飞鸟森林可以表示为一 ...

  4. 3825. 逃离大森林

    3825. 逃离大森林 题目链接 #include <bits/stdc++.h> #define x first #define y second using namespace std ...

  5. 星之卡比镜之迷宫机器人_星之卡比 镜之大迷宫

    该游戏资源是<星之卡比 镜之大迷宫>的rom包,所以手机.电脑都需要安装相应平台的模拟器才能玩. 星之卡比系列在2004年3月25日发售的一个在GBA平台上的游戏,属于星之卡比系列巅峰之作 ...

  6. 计蒜客:迷宫(二)---bfs

    计蒜客:bfs求解迷宫游戏 题目描述: 蒜头君在你的帮助下终于逃出了迷宫,但是蒜头君并没有沉浸于喜悦之中,而是很快的又陷入了思考,从这个迷宫逃出的最少步数是多少呢? 输入格式 第一行输入两个整数 nn ...

  7. 6264:走出迷宫(DFS和BFS)

    描述 当你站在一个迷宫里的时候,往往会被错综复杂的道路弄得失去方向感,如果你能得到迷宫地图,事情就会变得非常简单. 假设你已经得到了一个n*m的迷宫的图纸,请你找出从起点到出口的最短路. 输入 第一行 ...

  8. java迷宫队列实现_Creator 迷宫生成: DFS 与 BFS 算法实现

    前言: 我的迷宫代码的实现受到 [liuyubobobo] 的影响. liuyubobobo 迷宫的实现: GUI 部分使用 java Swing,编程语言是 Java. **我的迷宫代码实现: ** ...

  9. 【HRBUST - 1621】迷宫问题II (bfs)

    题干: 小z身处在一个迷宫中,小z每分钟可以走到上下左右四个方向的相邻格之一.迷宫中有一些墙和障碍物. 同时迷宫中也有一些怪兽,当小z碰到任意一个怪兽时,小z需要将怪兽消灭掉才可以离开此方格.但消灭 ...

最新文章

  1. R语言之斐波那契数列
  2. Ajax 通过 Request Payload 体发送 JSON 数据体
  3. 011 吃药call功能分析和代码编写
  4. Python工程师具备哪些技能才能提升求职机率?
  5. Spring MVC Controller中返回json数据中文乱码处理
  6. 练习:----点击按钮文字变颜色
  7. [Node.js] mySQL数据库 -- 数据库的基本操作
  8. 实验1 查看cpu和内存,用机器指令和汇编指令编程
  9. android 按钮换行_自定义Android自动换行的布局
  10. Flash研究(一)——本地通讯
  11. Docker网络基础---Docker跨主机容器访问通信
  12. paip.java 注解的详细使用代码
  13. Asymptotic statistics
  14. 怎么创建电脑的无线网络连接服务器,怎么开通无线网络
  15. 我为什么放弃百词斩?
  16. 不容错过的 能源logo设计灵感 标志设计
  17. Mac电脑使用:您的安全性偏好设置仅允许安装来自App Store和被认可的开发者的应用(解决方法)
  18. 双柱状图与双折线图混合
  19. 2021-10-11日python笔记(VM虚拟机安装Linux)
  20. GXNNCTF 2018 We_ax WriteUp 第三届南宁市网络安全技术大赛

热门文章

  1. 思维方式-《如何作出正确决策》书中的精髓:运用RPD模型,在困境中作出正确决策。
  2. list在python里是什么意思_python中的list是什么意思
  3. Peluso 发布 P-280 多指向性电子管麦克风
  4. IntelliJ IDEA 下载安装教程(详细图文)
  5. 数据库系统之SQL SELECT语句-4
  6. 逻辑 || !运算用法
  7. 彩票复式投注时,计算注数的程序(C#)
  8. 龙尚科技5G通信技术助力数字经济,中国移动2021全球合作伙伴大会圆满举办
  9. 每日学术速递5.28
  10. 自编码AE 实现图片去马赛克 pytorch