2020.7.29 LeetCode

好难啊

题目描述

我们得到了一副藏宝图,藏宝图显示,在一个迷宫中存在着未被世人发现的宝藏。

迷宫是一个二维矩阵,用一个字符串数组表示。它标识了唯一的入口(用 'S' 表示),和唯一的宝藏地点(用 'T' 表示)。但是,宝藏被一些隐蔽的机关保护了起来。在地图上有若干个机关点(用 'M' 表示),只有所有机关均被触发,才可以拿到宝藏。

要保持机关的触发,需要把一个重石放在上面。迷宫中有若干个石堆(用 'O' 表示),每个石堆都有无限个足够触发机关的重石。但是由于石头太重,我们一次只能搬一个石头到指定地点。

迷宫中同样有一些墙壁(用 '#' 表示),我们不能走入墙壁。剩余的都是可随意通行的点(用 '.' 表示)。石堆、机关、起点和终点(无论是否能拿到宝藏)也是可以通行的。

我们每步可以选择向上/向下/向左/向右移动一格,并且不能移出迷宫。搬起石头和放下石头不算步数。那么,从起点开始,我们最少需要多少步才能最后拿到宝藏呢?如果无法拿到宝藏,返回 -1 。

示例

示例一:
输入: ["S#O", "M..", "M.T"]输出:16解释:最优路线为: S->O, cost = 4, 去搬石头 O->第二行的M, cost = 3, M机关触发 第二行的M->O, cost = 3, 我们需要继续回去 O 搬石头。 O->第三行的M, cost = 4, 此时所有机关均触发 第三行的M->T, cost = 2,去T点拿宝藏。 总步数为16。 ![](https://img2020.cnblogs.com/blog/1922094/202007/1922094-20200729100707468-980981759.png)示例二:
输入: ["S#O", "M.#", "M.T"]输出:-1解释:我们无法搬到石头触发机关示例三:
输入: ["S#O", "M.T", "M.."]输出:17解释:注意终点也是可以通行的。

题解

class Solution {private static final int MAX_VALUE = Integer.MAX_VALUE/2;static class Point{int x;int y;int dis;public Point(int x, int y) {this.x = x;this.y = y;}public Point setDis(int dis) {this.dis = dis;return this;}@Overridepublic boolean equals(Object o) {if (this == o) return true;if (o == null || getClass() != o.getClass()) return false;Point point = (Point) o;return x == point.x &&y == point.y;}@Overridepublic int hashCode() {return Objects.hash(x, y);}}int[] sToMdis;int[][] mToMdis;int[] sToOdis;int[][] mToOdis;int[][] oToMdis;int[] mToTdis;Map<Point, Integer> pointToIndex = new HashMap<>();char[][] map;public int minimalSteps(String[] maze) {map = new char[maze.length+2][maze[0].length()+2];for(char[] row : map) {Arrays.fill(row, '#');}int oLen = 0;int mLen = 0;Point start = null;Point end = null;List<Point> oList = new ArrayList<>();List<Point> mList = new ArrayList<>();for(int y = 0;y<maze.length;y++) {for(int x = 0; x < maze[0].length();x++) {map[y+1][x+1] = maze[y].charAt(x);Point p = new Point(x+1, y+1);if (map[y+1][x+1] == 'O') {oList.add(p);pointToIndex.put(p, oLen++);} else if (map[y+1][x+1] == 'M') {mList.add(p);pointToIndex.put(p, mLen++);}else if(map[y+1][x+1] == 'S') {pointToIndex.put(p, 0);start = new Point(x+1,y+1);}else if (map[y+1][x+1] == 'T') {pointToIndex.put(p,0);end = p;}}}Map<Integer,Integer> sToTMap = bfs(start.x, start.y, 'T');// 如果s到不了T,直接返回-1if (sToTMap.isEmpty()) {return -1;}// 如果M的个数为0,那么直接返回S到T的距离if (mLen == 0) {return sToTMap.get(0);}// 计算s到所有O的距离sToOdis = new int[oLen];Arrays.fill(sToOdis, MAX_VALUE);Map<Integer,Integer> sToOMap = bfs(start.x, start.y, 'O');// 如果s连1个O都到不了,肯定不行。if (sToOMap.isEmpty()) {return -1;}for (Map.Entry<Integer, Integer> entry : sToOMap.entrySet()) {sToOdis[entry.getKey()] = entry.getValue();}// 如果s到不了所有M,则也不行Map<Integer,Integer> sToMMap = bfs(start.x, start.y, 'M');if (sToMMap.size() < mLen) {return -1;}// 计算所有o到M的距离oToMdis = new int[oLen][mLen];for (int[] oom : oToMdis) {Arrays.fill(oom, MAX_VALUE);}int i = 0;for (Point op : oList) {Map<Integer,Integer> oToMMap = bfs(op.x, op.y, 'M');if (oToMMap.isEmpty()) {i++;continue;}for (Map.Entry<Integer, Integer> entry : oToMMap.entrySet()) {oToMdis[i][entry.getKey()] = entry.getValue();}i++;}mToOdis = new int[mLen][oLen];mToTdis = new int[mLen];i = 0;// 计算所有m到O的距离,和所有m到T的距离for (Point mp : mList) {Map<Integer,Integer> mToOMap = bfs(mp.x, mp.y, 'O');// 有一个M找不到任何和O的路径,那么就是错误的if (mToOMap.isEmpty()) {return -1;}for (Map.Entry<Integer, Integer> entry : mToOMap.entrySet()) {mToOdis[i][entry.getKey()] = entry.getValue();}// 有一个找不到T,那也是错误的Map<Integer,Integer> mToTMap = bfs(mp.x, mp.y, 'T');if (mToTMap.isEmpty()) {return -1;}mToTdis[i] = mToTMap.get(0);i++;}sToMdis = new int[mLen];Arrays.fill(sToMdis, MAX_VALUE);mToMdis = new int[mLen][mLen];for (int[] mmm : mToMdis) {Arrays.fill(mmm, MAX_VALUE);}for (int m = 0; m < mLen;m++) {for (int o = 0; o < oLen;o++) {// 计算s到各m且至少经过1个O的最小距离int dis = sToOdis[o] + oToMdis[o][m];if (dis < sToMdis[m]) {sToMdis[m] = dis;}// 计算m到各mEnd且至少经过1个O的最小距离for (int mEnd = 0;mEnd<mLen;mEnd++) {int mmDis = mToOdis[m][o] + oToMdis[o][mEnd];if (mmDis < mToMdis[m][mEnd]) {mToMdis[m][mEnd] = mmDis;}}}}int mStatu = (1 << mLen);int[][][] dp = new int[mLen+1][mStatu][mLen];int bigStatu = mStatu - 1;for (int m = 0; m < mLen;m++) {dp[mLen][bigStatu][m] = mToTdis[m];    }// dp指该状态下该位置的m到终点的最短距离// 对于那个m他要选1个mfor (int mCount = mLen-1;mCount>=1;mCount--) {for(int statu = bigStatu; statu>=0;statu--) {for (int m = 0; m < mLen;m++) {int minDis = MAX_VALUE;for(int endM = 0;endM < mLen;endM++) {// endM必须是0if ((statu & (1<<endM)) != 0 || m == endM) {continue;}int realDis = mToMdis[m][endM] + dp[mCount+1][statu | (1<<endM)][endM];if (realDis < minDis) {minDis = realDis;}}dp[mCount][statu][m] = minDis;}}}int result = MAX_VALUE;for (int m = 0; m < mLen;m++) {int sToMToTDis = sToMdis[m] + dp[1][1<<m][m];if (sToMToTDis < result) {result = sToMToTDis; }}return result == MAX_VALUE?-1:result;}Map<Integer, Integer> bfs(int x, int y, char findChar) {boolean[][] vis = new boolean[map.length][map[0].length];Queue<Point> queue = new LinkedList<>();queue.offer(new Point(x, y));vis[y][x] = true;Map<Integer, Integer> indexAndDisMap = new HashMap<>();while(!queue.isEmpty()) {Point p = queue.poll();int px = p.x;int py = p.y;if (map[py][px] == findChar) {indexAndDisMap.put(pointToIndex.get(p), p.dis);}deal(px+1,py,queue,vis, p.dis + 1);deal(px-1,py,queue,vis, p.dis + 1);deal(px,py-1,queue,vis, p.dis + 1);deal(px,py+1,queue,vis, p.dis + 1);}return indexAndDisMap;}void deal(int x,int y, Queue<Point> queue, boolean[][] vis, int dis) {if (!vis[y][x] && map[y][x] != '#') {vis[y][x] = true;queue.offer(new Point(x,y).setDis(dis));}}
}

思路

我是真的不会

第一轮状态压缩

先利用bfs
求出起点S到所有O的最短路径s2oDis[] (2就是英文to的意思)
求出所有O到所有M的最短路径o2mDis[][]
求出所有M到所有O的最短路径m2oDis[][] (这个其实把上面的o2mDis反一下就行了,我写的时候忘记了,又重新跑一边)
求出所有M到终点T的最短路径m2tDis[]
这时候相当于已经对图做了一轮压缩了。
但这时候还是不能起做搜索, O的节点有40个,M的节点有16个,合起来相当于最多有六十多层,铁定超时。

第二轮压缩

那么还能压缩吗?
可以!
S到每个M的时候,中间至少要经过1个O
每个M到其他的M时,也至少要经过1个O
那么S到每个M的距离s2mDis[mIndex] = Min(s2oDis[oIndex] + o2mDis[oIndex][mIndex])
直接对oIndex和mIndex做2个for循环搞定。

for (int m = 0; m < mLen;m++) {for (int o = 0; o < oLen;o++) {// 计算s到各m且至少经过1个O的最小距离int dis = sToOdis[o] + oToMdis[o][m];if (dis < sToMdis[m]) {sToMdis[m] = dis;}}}

M到各M同理,于是我们又得到了m2mDis[][]

那么整个图就压缩成了3个数组,并且节点最多也就18个。
起点S到个M的最短距离 s2mDis[]
各M到各M的最短距离 m2mDis[][]
各M到终点的最短距离 m2tDis[]

搜索和剪枝

那么来就是个搜索问题了,dfs或者bfs都可以 ,最多也就18层。
但是这里有个条件: 必须经过所有的M。以至于你得注意剪枝。避免反复搜重复的局面。

那么如何判断局面是否经历过?
题目把M的最大个数正好设定成16,其实就是在暗示你用位运算来记录已走路线。
假设M有4个,则二进制(1001)=9代表了此时第0个M和第3个M已被走过,可以用statu来表示
还得注意一下此时所处M的位置, 用nowMIndex表示
则我们就可以用数组minDis[statu][nowMIndex]来记录已搜索到的最短路径了,做一下剪枝,就能AC了。
非搜索的dp方式
除了用bfs和dfs做最后的搜索, 也可以用dp,我是用dp的,因为之前做过类似的题目。
dp[已走的M个数][当前M局面]][当前所处M位置] 等于该状态下到T的最短距离

dp[已走的M个数][当前M局面]][当前所处M位置]
= min( dp[已走M个数+1][选择下一个M后的局面][下一个M的位置] )
dp的时候从最后的局面一步步往前搜即可。

注:第一个下标[已走M的个数]也可以不需要,避免局面值已经能计算出M的个数了
但我为了方便写还是用了,代价就是最长例子用了1.4秒差点超了,如果会超时我就去掉这个下标

int mStatu = (1 << mLen);int[][][] dp = new int[mLen+1][mStatu][mLen];int bigStatu = mStatu - 1;for (int m = 0; m < mLen;m++) {dp[mLen][bigStatu][m] = mToTdis[m];    }// dp指该状态下该位置的m到终点的最短距离//dp[已走的M个数][当前M局面]][当前所处M位置] for (int mCount = mLen-1;mCount>=1;mCount--) {for(int statu = bigStatu; statu>=0;statu--) {for (int m = 0; m < mLen;m++) {int minDis = MAX_VALUE;for(int endM = 0;endM < mLen;endM++) {// endM所在位必须是0。if ((statu & (1<<endM)) != 0 || m == endM) {continue;}int realDis = mToMdis[m][endM] + dp[mCount+1][statu | (1<<endM)][endM];if (realDis < minDis) {minDis = realDis;}}dp[mCount][statu][m] = minDis;}}}

需要注意的情况(如果用搜索可能会陷入这个坑)
不存在M和O的情况
只存在O,不存在M( 这个贼坑)
S到不了T
S到不了所有的M
存在M到不了任意一个O

LCP 13. 寻宝相关推荐

  1. LeeCode LCP.13 寻宝【python】

    这么高频的题,这么难是我万万没想到的,差点当场裂开,这里就写一写博主研究官方答案和一众博主的思路,然后研究了很久得到的思路以及代码,就差抄了!!!!淦. 有一说一,这道题快把我写哭了,一个转行的菜鸡太 ...

  2. leetcode题目

    <!DOCTYPE html> <html><head><meta charset="utf-8"><title>Lee ...

  3. H3C学习笔记《五》(初级理论知识)

    交换机之间互联技术: (1)级联:用普通网线连接(2)堆叠:专用线缆和专用的接口,将多台交换机当成一个交换机用.           a.增加交换机的连接带宽:b.增加端口密度:c.统一管理:d.交换 ...

  4. 五分钟搞懂后缀数组!

    为什么学后缀数组 后缀数组是一个比较强大的处理字符串的算法,是有关字符串的基础算法,所以必须掌握.  学会后缀自动机(SAM)就不用学后缀数组(SA)了?不,虽然SAM看起来更为强大和全面,但是有些S ...

  5. OpenLDAP+freeradius+samba+802.1x实现无线和有线网络认证+动态vlan下发——openLDAP篇

    OpenLDAP+freeradius+samba+802.1x实现无线和有线网络认证+动态vlan下发--openLDAP篇 全部都是开源工具,顺便学习一下openldap的用户管理. 也欢迎大家来 ...

  6. mahout安装测试

    Mahout 是 Apache Software Foundation(ASF) 旗下的一个开源项目,提供一些可扩展的机器学习领域经典算法的实现,旨在帮助开发人员更加方便快捷地创建智能应用程序.Apa ...

  7. 亮度翻转_ROG幻13翻转本发布;联想IdeaPad 5G轻薄本即将上线

    ROG去年首发幻14高性能轻薄潮玩本,凭借创新设计.强劲性能.轻薄机身被誉为"性能小钢炮",今年则形成了幻15.幻14.幻13的完整产品线,其中幻13是ROG旗下首款13英寸轻薄全 ...

  8. bzoj1624:[Usaco2008 Open] Clear And Present Danger 寻宝之路

    Description 农夫约翰正驾驶一条小艇在牛勒比海上航行. 海上有N(1≤N≤100)个岛屿,用1到N编号.约翰从1号小岛出发,最后到达N号小岛.一张藏宝图上说,如果他的路程上经过的小岛依次出现 ...

  9. 全球及中国LCP行业应用项目布局及产能规模预测报告2021版

    全球及中国LCP行业应用项目布局及产能规模预测报告2021版  HS--HS--HS--HS--HS--HS--HS--HS--HS--HS--HS--HS-- [修订日期]:2021年11月 [搜索 ...

最新文章

  1. IOS开发基础知识--碎片34
  2. Translucent System Bars-4.4新特性
  3. EasyUI中Datagride数据网格的简单使用
  4. XShell中浏览文件时上拉下拉
  5. aws 堆栈模板_使用Arquillian和LocalStack脱机测试AWS云堆栈
  6. rust种的南瓜为什么老是消失_科技的力量!3种“奇葩”的发明,你都见过吗?...
  7. android ffmpeg 优点_在Android中使用FFmpeg(android studio环境)
  8. Windows Phone开发(40):漫谈关键帧动画之中篇 转:http://blog.csdn.net/tcjiaan/article/details/7559978...
  9. 如何自定义IHttpModule
  10. syslog收到的日志存放在哪里_Linux使用RsyslogServer记录远程主机系统日志
  11. 网络原理考点之滑动窗口协议
  12. dp hp oracle 备份软件_HP-DP备份软件设置
  13. 微信公众号开通留言功能条件有哪些?
  14. 通过Redis入侵服务器
  15. 数字PCR简介(一)
  16. SCL3400-D01双轴高精度倾角计
  17. 论文阅读笔记(1):Multi-Task Feature Learning for Knowledge Graph Enhanced Recommendation
  18. windows服务与计划任务
  19. 在ROS中使用USB网络摄像头传输图像
  20. Hibernate快速入门(2)

热门文章

  1. antimalware service executable占用内存_解决 vue 项目运行过程中内存泄漏问题
  2. DHCP配置 TFTP服务
  3. iOS:重识Transform和frame
  4. Java集合--WeakHashMap
  5. 计蒜客——回文平方数
  6. 微信App支付全解析
  7. XML DOM学习笔记(JS)
  8. 7Python全栈之路系列之Django表单
  9. c++ 2条中线焦点_三角形的中线为何交于一点
  10. jQuery Event.delegateTarget 属性详解