目录导航

  • 解法1:遍历排序
    • 代码
    • 运行结果
  • 解法2:排除筛选(迷宫模拟)
    • 小结分析
    • 代码
    • 运行结果
    • 题外扩展1——贴墙排查方式:
      • 代码
      • 运行结果
    • 题外扩展2——结合扩展1封路后用栈方式寻找最佳路线:
      • 代码
      • 运行结果
    • 题外扩展3——相似题:
      • 代码
      • 运行结果

题目链接:
https://wenku.baidu.com/view/d8253e24f90f76c660371ac2.html?from=search
问题描述:
XX大学城离市中心比较远,因此占地面积巨大,因而XX市团委准备充分利用资源,在大学城举办定向越野比赛,但规则与普通定向越野不同,每个队被要求从某个起点出发最后到达终点,只要是地图上每个标注的点都可以走,经过一个点时必须在打卡器上打卡做记录,记录该点的打卡器所在位置的海拔高度,高度用一个非负整数来度量,该数将会被保存在卡中。最后到达终点时,该队的成绩就为卡中记录的最大数与最小数之差,差最小的队伍将摘取桂冠。
ZZ和他的同学也参加了这次比赛,拿到地图后,他们想要迅速的找到一条最佳路线以确保获得冠军。
PS:其实光脑子好使能算出最佳路线还不够,还得能跑,但是我们假设ZZ他们队个个都是SUPERMAN,只要你能帮助他们找到最佳路线,它们就一定是冠军。
输入:
由多组数据组成,输入文件以EOF结尾每组数据的第一行包含一个 正整数n,表示校园地图上共有n*n被标注的点(n<=100),接下来n行每行有n个非负整数Ai, j,表示该点打卡器所在位置高度(Ai, j<=200),ZZ和他的同学从(1,1) 出发,目的地为(n,n)
输出:
每组数据对应一行输出,包含一个整数,及最小的高度差的值
输入样本:
5
1 1 3 6 8
1 2 2 5 5
4 4 0 3 3
8 0 2 2 4
4 3 0 3 1
输出样本:
3
Tips:
最佳路线为
(1, 1)→(1,2)→(2,2)→(2, 3)→(3,3)→(4, 3)→(4,4)→(5, 4)→(5, 5)
路线上最高高度为3,最低高度为0,所以答案为3.当然,最佳路线可能不止一条。

解法1:遍历排序

把所有路线都遍历出来,然后根据路线各自的最大高度差值(最大高度差 - 最小高度差)自小到大排序得出【最小的高度差的值】;

从起点开始遍历
起点(1,1)的下一步有两种可能:(1,2)或者(2,1),
以此类推,像烟花那样除了来时的方向外有3个可能方向;

所以【下一步】的排除条件有:
1.【下一步】必须在给定的坐标范围内,就是符合值域 x 和 y ∈ [ 1 , n ]
2.【下一步】必须不在已走过的路线内
3.【下一步】不能连续朝同一个方向拐弯两次
4.路线的起点和终点必须与题目一致

代码

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Windows;
     static void Main(string[] args){List<string> readLineStrings = new List<string> {// 方便测试"5",// 原题"1 1 3 6 8","1 2 2 5 5","4 4 0 3 3","8 0 2 2 4","4 3 0 3 1",// 中间有一层全排除//"1 1 3 6 8",//"1 2 2 8 5",//"4 4 8 3 3",//"8 8 2 2 4",//"8 3 0 3 1",// 排除开头//"7 1 3 6 8",//"1 2 2 5 5",//"4 4 0 3 3",//"8 0 2 2 4",//"4 3 0 3 1",// 排除结尾//"1 1 3 6 8",//"1 2 2 5 5",//"4 4 0 3 3",//"8 0 2 2 4",//"4 3 0 3 7",// 分层式排除//"0 0 3 6 8",//"1 0 7 7 7",//"4 7 0 0 3",//"7 0 2 0 4",//"4 3 0 0 0",// 分层式排除//"0 0 0 6 8",//"1 7 0 0 7",//"7 0 0 7 3",//"0 0 7 0 4",//"4 0 0 0 0",};int n = int.Parse(readLineStrings[0]);List<int> intKinds = new List<int>();// 高度值的种类Dictionary<Point, int> coordinates = new Dictionary<Point, int>();// 原接收输入的值(还原是个立体坐标系)string[] strList = null;for (int y = 1; y <= n; y++)for (int x = 1; x <= n; x++){strList = readLineStrings[y].Split();int z = int.Parse(strList[x - 1]);coordinates.Add(new Point(x, y), z);if (!intKinds.Contains(z)) // 收集不同的高度值intKinds.Add(z);}intKinds.Sort((int i1, int i2) => { return i2 - i1; });// 自大到小排序不同的高度值List<List<Point>> lines = new List<List<Point>> { new List<Point> { new Point(1, 1) } };// 穷举所有路线int newLineCount = 0;do{newLineCount = 0;for (int i = lines.Count - 1; i >= 0; i--){List<Point> line = lines[i];Point lastPoint = line.Last();if (line.Last() == new Point(n, n)) continue;// 跳过已到终点的路线Point upPoint = new Point(lastPoint.X, lastPoint.Y - 1);Point downPoint = new Point(lastPoint.X, lastPoint.Y + 1);Point leftPoint = new Point(lastPoint.X - 1, lastPoint.Y);Point rightPoint = new Point(lastPoint.X + 1, lastPoint.Y);List<Point> nextPoints = new List<Point> { upPoint, downPoint, leftPoint, rightPoint };foreach (Point nextPoint in nextPoints){Point nextUpPoint = new Point(nextPoint.X, nextPoint.Y - 1);Point nextDownPoint = new Point(nextPoint.X, nextPoint.Y + 1);Point nextLeftPoint = new Point(nextPoint.X - 1, nextPoint.Y);Point nextRightPoint = new Point(nextPoint.X + 1, nextPoint.Y);List<Point> nextNextPoints = new List<Point> { nextUpPoint, nextDownPoint, nextLeftPoint, nextRightPoint };if (!nextNextPoints.Any((nextNextPoint) =>{return nextNextPoint != lastPoint && line.Contains(nextNextPoint); // 下下一步不能重合原路线})&& coordinates.ContainsKey(nextPoint) // 下一步必须在路线内&& !line.Contains(nextPoint) // 防止路线逆行){List<Point> newLine = new List<Point>(line) { nextPoint };lines.Add(newLine);newLineCount++;}}if (newLineCount > 0){lines.RemoveAll((removeLine) =>{if (removeLine.Count != line.Count || removeLine.Last() == new Point(n, n)) return false;for (int j = 0; j < line.Count; j++)if (removeLine[j] != line[j]) return false;return true;});}}} while (newLineCount > 0);lines.RemoveAll((removeLine) => { return removeLine.Last() != new Point(n, n); });// 去除不在终点结束的路线SortedDictionary<int, List<int[]>> diffCombineList = new SortedDictionary<int, List<int[]>>();// 高度差值列表<高度差,对应高度差组合>int startHeight = coordinates[new Point(1, 1)];// 起点高度int finHeight = coordinates[new Point(n, n)];// 终点高度// 当存在起点高度等于终点高度 & 这个高度值的数量大于等于最短路线的步数2n-1时,可以添加差值为0的组合if (startHeight == finHeight && coordinates.Count((coordinate) => { return coordinate.Value == startHeight; }) >= 2 * n - 1)diffCombineList.Add(0, new List<int[]> { new int[] { startHeight, finHeight } });for (int i = 0; i < intKinds.Count; i++)for (int j = i + 1; j < intKinds.Count; j++)if (intKinds[i] >= startHeight && intKinds[j] <= startHeight && intKinds[i] >= finHeight && intKinds[j] <= finHeight)// ∵起点和终点是必经之地∴起点&终点必定在值域范围内{int diff = intKinds[i] - intKinds[j];// 由于高度值已排序所以intKinds[i] > intKinds[j]if (diffCombineList.ContainsKey(diff))diffCombineList[diff].Add(new int[] { intKinds[i], intKinds[j] });elsediffCombineList.Add(diff, new List<int[]> { new int[] { intKinds[i], intKinds[j] } });}foreach (KeyValuePair<int, List<int[]>> diff_combine in diffCombineList){int diff = diff_combine.Key;bool isFind = false;// 是否找到有起点终点相通的路线foreach (int[] combine in diff_combine.Value)// 遍历所有高度差值范围 [h1,h2],其中h1>h2{List<List<Point>> correctLines = lines.FindAll((line) =>{return line.All((point) =>{return coordinates[point] <= combine[0] && coordinates[point] >= combine[1];});});if (correctLines != null && correctLines.Any()){List<Point> bestLine = correctLines[0];foreach (List<Point> correctLine in correctLines)if (correctLine.Count < bestLine.Count)bestLine = correctLine;Console.WriteLine("最小高度差:" + diff + "\t范围:[" + combine[1] + "," + combine[0] + "]");StringBuilder stringBuilder = new StringBuilder("路线:");foreach (Point point in bestLine){stringBuilder.Append("(" + point.X + "," + point.Y + "," + coordinates[point] + ")");stringBuilder.Append("→");}stringBuilder.Remove(stringBuilder.Length - 1, 1);Console.WriteLine(stringBuilder);Console.WriteLine();isFind = true;//break;// 题外:求所有路线}}//if (isFind) break;// 题外:求其他次选方案}Console.ReadKey();}

运行结果

解法2:排除筛选(迷宫模拟)

先把各个高度差值以及高度差的差值组合形成的值域遍历出来;
例如答案中【高度差值=3】【值域为[0,3]】
对这些差值及值域组合分类讨论,
讨论时,我们把点阵想象成一个N宫格的迷宫,值域之外的格子都视为【迷宫】的【墙】,值域内的格子视为【迷宫】的【路】;

然后,根据传说中的【迷宫万能解法】:只要出入口相通,从入口出发时只要贴着某一边的墙走就必定能走到出口。也就是说从【高度差值】小的【值域】组合开始试,最早遇到能走通的【迷宫】对应的【高度差值】就是所求的【最小的高度差的值】;

相反有符合所求的情况就有要排除的情况,而排除的情况就是:
1.起点开始最后沿着墙回到了起点
2.回到时方向跟最开始时一样(注意从【凸】形迷宫的中间开始走的话不管哪个方向开始都会经过起点,所以只有条件一不够)
也就是这个解法的前提不成立——出入口不相通;

PS.不知道还有多少人记得早期 Windows 的迷宫屏保呢…有的都老了,它并不是走最短路线的,但最终都能走出去,用的就是这个解法,虽然有个迷宫机关会上下旋转,但是颠倒之后会沿着另一边的墙走,所以还是贴回最初的墙继续走,直到走出迷宫。

需要注意的是【最佳路线】 不一定就是 【最短路线】(2 * n - 1步),因为有可能会有迂回的情况
例如(假设黑色的是【墙】):
□ □ □ □ □
■ ■ ■ ■ □
□ □ □ □ □
□ ■ ■ ■ ■
□ □ □ □ □
根据这个思路,那么就还要在外围加四面【墙】,而【墙】的值就设置为【-1】(想象成模型就是凹下去的一条槽,想方便理解或者可以设置成高墙int.最值),因为-1(或者int.最值)必定在输入的高度值的值域之外,加完后的效果如下:

按照题目答案造出来的【墙】以及贴着上边的【墙】走就有下面的路线图

由于路线是根据墙生成的,所以我们先确定【墙】的起点和终点以及贴【墙】直行、左/右转的变化;

首先,这个迷宫的坐标系是一个上下颠倒的【直角坐标系】,那么按照题目起点固定是【(1,1)】,其上边的【墙】坐标就是固定是【(1,0)】(沿着下边走的话就是【(0,1)】开始),从起点开始判断与其连接的【墙】是不是唯一的,从这里,是就继续判断那面唯一的【墙】(1,0)的下一面【墙】;


如果还思维不清晰的话,可以想象换成实际场景中【迷宫万能解法】贴墙走的情景,人站在某个点,面朝前方,左手试图扶墙:
1.直走:发现当前位置的左手边有墙可扶,前方还有路,那就往前走一格;

2.就地右转:发现当前位置的左手边有墙可扶,但前方没路,就地右转;
(如果来到死路就是3面墙的点,那么就会因为这个规则转2次方向,回头后手已经放在来时相反的另一边的墙上往回走)

3.左转往前:发现当前位置的左手边无墙可扶,无论前方情况,就地左转后往前走一格;
(左边不是墙,那么一定是路)

也就是只用讨论这3种情况

这里把上面思路情况具象化一下就是:
1.在起点(1,1),(可以扶的墙有两面,当选的是扶上边缘那面墙)左手有墙可扶,前方有路,符合直走条件,往前走就是(2,1);

2.同样在起点(1,1),如果选的是扶左边缘的墙时,左手有墙可扶,前方没路,就地右转

3.如下图情况时,左手无墙可扶,就地左转后往前走一格(白色箭头表示上一步的方向和位置)

小结分析

那么,总结优化一下,在九宫格里:
1.只需要讨论当前点左方+前方的格子情况,其他格子并不需要讨论;

2.只需要确定当前点坐标以及左墙坐标就能确定(面朝)方向;

3.情况1【直走】和情况2【右转】对比,情况2右转后新的墙坐标刚好是【直走】的新坐标;

4.情况3【左转+直走】的前一步绝对是情况1【直走】,也就是前面的点的左方不是墙就可以直接两步并一步处理,原点往原点左墙的方向斜着走一步,刚好就是再次直走后的左墙,左墙点不变;

因为4的情况的触发条件的必然的,而且触发后的新点必定【左手有墙可扶】所以,判断条件再优化一下:
1.直走:前方还有路,那就往前走一格,左墙点亦往前一格;

1.1.直走+左转+直走:发现将要直走(直走条件成立)的点的位置左手边无墙可扶,墙点位置不变,新站点就是第一次直走时的新左墙点;

2.原地右转:前方没路,原地右转(原点不变),左墙点就是原来应该往前的那一格;

最后转换到代码判断——判断前方及左前方的格子情况(扶左墙时):
0.先测前方是否有路

1.0 前方有路,前方的左边是否有墙可扶
→1.1 前方的左边有墙可扶——直走
→1.2 前方的左边无墙可扶——直走+左转+直走

2.1 前方无路——原地右转

PS.下方代码只是方便完成题目要求,如果要看怎么求迷宫最佳路线就继续看下文【题外扩展1】吧~

代码

     List<string> readLineStrings = new List<string> {// 方便测试"5",// 原题"1 1 3 6 8","1 2 2 5 5","4 4 0 3 3","8 0 2 2 4","4 3 0 3 1",// 中间有一层全排除//"1 1 3 6 8",//"1 2 2 8 5",//"4 4 8 3 3",//"8 8 2 2 4",//"8 3 0 3 1",// 排除开头//"7 1 3 6 8",//"1 2 2 5 5",//"4 4 0 3 3",//"8 0 2 2 4",//"4 3 0 3 1",// 排除结尾//"1 1 3 6 8",//"1 2 2 5 5",//"4 4 0 3 3",//"8 0 2 2 4",//"4 3 0 3 7",// 分层式排除//"0 0 3 6 8",//"1 0 7 7 7",//"4 7 0 0 3",//"7 0 2 0 4",//"4 3 0 0 0",// 分层式排除//"0 0 0 6 8",//"1 7 0 0 7",//"7 0 0 7 3",//"0 0 7 0 4",//"4 0 0 0 0",};int n = int.Parse(readLineStrings[0]);List<int> intKinds = new List<int>();// 高度值的种类Dictionary<Point, int> coordinates = new Dictionary<Point, int>();// 原接收输入的值(还原是个立体坐标系)string[] strList = null;for (int y = 0; y <= n + 1; y++)for (int x = 0; x <= n + 1; x++)if (y == 0 || y == n + 1 || x == 0 || x == n + 1) // 上边或底边的墙coordinates.Add(new Point(x, y), -1);else{strList = readLineStrings[y].Split();int z = int.Parse(strList[x - 1]);coordinates.Add(new Point(x, y), z);if (!intKinds.Contains(z)) // 收集不同的高度值{intKinds.Add(z);}}intKinds.Sort((int i1, int i2) => { return i2 - i1; });// 自大到小排序不同的高度值SortedDictionary<int, List<int[]>> diffCombineList = new SortedDictionary<int, List<int[]>>();// 高度差值列表<高度差,对应高度差组合>int startHeight = coordinates[new Point(1, 1)];// 起点高度int finHeight = coordinates[new Point(n, n)];// 终点高度// 当存在起点高度等于终点高度 & 这个高度值的数量大于等于最短路线的步数2n-1时,可以添加差值为0的组合if (startHeight == finHeight && coordinates.Count((coordinate) => { return coordinate.Value == startHeight; }) >= 2 * n - 1)diffCombineList.Add(0, new List<int[]> { new int[] { startHeight, finHeight } });for (int i = 0; i < intKinds.Count; i++)for (int j = i + 1; j < intKinds.Count; j++)if (intKinds[i] >= startHeight && intKinds[j] <= startHeight && intKinds[i] >= finHeight && intKinds[j] <= finHeight)// ∵起点和终点是必经之地∴起点&终点必定在值域范围内{int diff = intKinds[i] - intKinds[j];// 由于高度值已排序所以intKinds[i] > intKinds[j]if (diffCombineList.ContainsKey(diff))diffCombineList[diff].Add(new int[] { intKinds[i], intKinds[j] });elsediffCombineList.Add(diff, new List<int[]> { new int[] { intKinds[i], intKinds[j] } });}foreach (KeyValuePair<int, List<int[]>> diff_combine in diffCombineList){int diff = diff_combine.Key;foreach (int[] combine in diff_combine.Value)// 遍历所有高度差值范围 [h1,h2],其中h1>h2,数值范围外视为墙{// 在起点开始走Point startPoint = new Point(1, 1), startLeftWall = new Point(1, 1 - 1), nowPoint, nowleftWall, nextPoint = new Point(0, 0), nextleftWall = new Point(0, 0);nowPoint = startPoint;nowleftWall = startLeftWall;// 构建模型时已经确定是墙string direction = "";// 方向List<string> directions = new List<string>();// 运行路线List<Point> bestCrossPoints = new List<Point> { startPoint };// 最短路线do{double nowX = nowPoint.X, nowY = nowPoint.Y;// 起点坐标if (nowX == nowleftWall.X) // 同一竖线的一上一下{if (nowY > nowleftWall.Y) // 按照题目靠下方的坐标值大direction = "right";else if (nowY < nowleftWall.Y)direction = "left";}else if (nowY == nowleftWall.Y)  // 同一横线的一左一右{if (nowX > nowleftWall.X) // 按照题目靠右方的坐标值大direction = "up";else if (nowX < nowleftWall.X)direction = "down";}elsethrow new ArgumentOutOfRangeException("当前点坐标值有误,导致方向有误:nowX=" + nowX + "\tnowY:" + nowY);switch (direction)// 求前方及前方左墙的点坐标{case "right":nextPoint = new Point(nowX + 1, nowY);nextleftWall = new Point(nowX + 1, nowY - 1);break;case "left":nextPoint = new Point(nowX - 1, nowY);nextleftWall = new Point(nowX - 1, nowY + 1);break;case "up":nextPoint = new Point(nowX, nowY - 1);nextleftWall = new Point(nowX - 1, nowY - 1);break;case "down":nextPoint = new Point(nowX, nowY + 1);nextleftWall = new Point(nowX + 1, nowY + 1);break;default:throw new ArgumentOutOfRangeException("当前点坐标值有误,导致方向有误:nowX=" + nowX + "\tnowY:" + nowY);}bestCrossPoints.Add(nextPoint);// 路线记录if (coordinates[nextPoint] > combine[0] || coordinates[nextPoint] < combine[1]) // 前方是墙,原地右转,右转后新的墙坐标刚好是上一步前方的坐标{nextleftWall = nextPoint;// 把前方变成下一步的左墙nextPoint = nowPoint;// 退回原地direction = "turnRight";bestCrossPoints.RemoveAt(bestCrossPoints.Count - 1);// 路线记录:退回原地=删除刚刚的直走}else if (coordinates[nextleftWall] <= combine[0] && coordinates[nextleftWall] >= combine[1])// 前方及前左边都不是墙,直接符合情况3,左转+直走,跟当前的直走并起来就是直走+左转+直走,相当于墙点不变,原点往原点左墙的方向斜着走一步,刚好就是直走后的左墙{nextPoint = nextleftWall;nextleftWall = nowleftWall;bestCrossPoints.Add(nextPoint);// 路线记录switch (direction){case "right":direction = "up&Right";break;case "left":direction = "down&left";break;case "up":direction = "up&left";break;case "down":direction = "down&Right";break;default:throw new ArgumentOutOfRangeException("当前点坐标值有误,导致方向有误:nowX=" + nowX + "\tnowY:" + nowY);}}nowPoint = nextPoint;nowleftWall = nextleftWall;directions.Add(direction);} while (bestCrossPoints.Count((p) => { return p == new Point(1, 1); }) < 2);if (bestCrossPoints.Contains(new Point(n,n))){Console.WriteLine("最小高度差:" + diff + "\t范围:[" + combine[1] + "," + combine[0] + "]");// 下面是额外显示内容for (int y = 1; y <= n; y++){for (int x = 1; x <= n; x++){Point point = new Point(x, y);int z = coordinates[point];if (z > combine[0] || z < combine[1])Console.Write("(-,-," + z + ")");elseConsole.Write("(" + x + "," + y + "," + z + ")");}Console.WriteLine();}Console.Write("贴墙一圈路线:");foreach (string d in directions)switch (d){case "right":Console.Write("→ ");break;case "left":Console.Write("← ");break;case "up":Console.Write("↑ ");break;case "down":Console.Write("↓ ");break;case "turnRight":Console.Write("T ");break;case "up&Right":Console.Write("↗ ");break;case "down&left":Console.Write("↙ ");break;case "up&left":Console.Write("↖ ");break;case "down&Right":Console.Write("↘ ");break;default:throw new ArgumentOutOfRangeException("当前点坐标值有误,导致方向有误!");}Console.WriteLine();List<Point> leftWallPoints = new List<Point>();List<Point> rightWallPoints = new List<Point>();for (int i = 0; i <= bestCrossPoints.IndexOf(new Point(n, n)); i++)leftWallPoints.Add(bestCrossPoints[i]);Console.Write("贴左墙最佳路线:");for (int i = 0; i <= leftWallPoints.IndexOf(new Point(n, n)); i++){leftWallPoints.RemoveRange(i, leftWallPoints.LastIndexOf(leftWallPoints[i]) - i);// 走到重复点证明,这两个重复点之间是冤枉路所以去掉Console.Write("(" + leftWallPoints[i].X + "," + leftWallPoints[i].Y + ")");}Console.WriteLine();for (int i = bestCrossPoints.IndexOf(new Point(n, n)); i < bestCrossPoints.Count; i++)rightWallPoints.Insert(0, bestCrossPoints[i]);if (rightWallPoints.Count > 1){Console.Write("贴右墙最佳路线:");for (int i = 0; i <= rightWallPoints.IndexOf(new Point(n, n)); i++){rightWallPoints.RemoveRange(i, rightWallPoints.LastIndexOf(rightWallPoints[i]) - i);// 走到重复点证明,这两个重复点之间是冤枉路所以去掉Console.Write("(" + rightWallPoints[i].X + "," + rightWallPoints[i].Y + ")");}}Console.WriteLine();//break;// 放出则不求所有路线}}//if (bestCrossPoints.Contains(new Point(n,n))) break;// 放出则不求其他次选方案}Console.ReadKey();

运行结果



PS.上面说的最佳路线只是指贴某边墙的最佳路线,但不包括不贴墙的最佳路线,如果想查验其他组合的路线的最大高度差可以把上面代码里2个带【题外】的注释位置进行【行注释】↓

题外扩展1——贴墙排查方式:

PS.这里试多了几个样式的迷宫后可以智能点识别迷宫减少循环,其实就是把上面的基本动作组合一套;
例如:
1.如果可以一直直走,那么就走到尽头;
1.1.尽头是死路,就回到原点或者当前原点的下一步反向后继续;
2.如果走到墙前必定右转,但如果右转后会触发【直走→转左→直走】,那么直接↗走

当然除了上面的优化外,还可以用【堵路法】——除起点终点,将3个方向都是墙的点都变为墙,循环N次,直到迷宫内没有这种点为止:

第一次循环后:

就这样,循环4次后就有最佳的贴墙走法:

但是这样子还不是适用所有迷宫的最佳走法,因为还有下面这种情况:

贴墙走的最佳路线要11步,但是我们肉眼可见最佳路线(不止一条)应该是9步:

那么我们需要堵住的路应该是(最佳步数会更趋于2*n-1):

这里就需要整理下思路,获取当前点的上下左右4个点的情况,3或4面墙的好判断,是堵上后不会有问题,但≤2面墙的,不仅要判断墙是否相连而且要判断堵上后是否影响原有道路的畅通,所以不能单纯用墙的数量作为依据去将该点变为【墙】。
那就先把畅通性作为首要条件,那么堵上(中点)后不影响畅通性的组合有:
PS.【 ?】为可【墙】可【路】;【0】为【路】;【-1】为【墙】;每个情况旋转90°讨论一次 × 4 ,重复板型则跳过

4 面墙:

3 面墙:

2 面墙:
PS.对立的两面墙的情况可以直接排除

1 面墙:

中间的【0】确实堵上都不会影响【通行】,但如果是这种情况反而会造成【绕路】,所以这种左边或右边还有路情况应当不执行堵路

目前想到的就是这2种情况是可以堵的

同样也要多次排查,只不过,可以把排查过的就不再重复排查,减少无效排查数;
然后发现如果对应高度差本来就是不通的话,新添加的墙肯定会随着循环而渐渐堵满除起点和终点的路,最后只剩起点和终点两个坐标,所以,如果出现这种情况直接视为排除的方案,这样,就可以直接优化迷宫路线的同时排除无解的不相通的迷宫布局,也就是上面的代码的处理记录路线部分变成了拿来二次验证;

由于最优解已经把冤枉路堵住了,所以原代码对冤枉路的处理就可以去掉了;
PS.下面代码里对【外墙】的定义坐标 x ∈ [ 1 , n ] & y ∈ [ 1 , n ]范围以外的都视为【外墙】

代码

List<string> readLineStrings = new List<string> {// 这里方便测试不设置输入"5",// 原题"1 1 3 6 8","1 2 2 5 5","4 4 0 3 3","8 0 2 2 4","4 3 0 3 1",// 双路线//"1 1 1 1 1",//"1 4 4 4 1",//"1 4 4 4 1",//"1 4 4 4 1",//"1 1 1 1 1",// 3路线//"1 1 1 1 1",//"1 1 1 4 1",//"1 4 1 4 1",//"1 4 1 1 1",//"1 1 1 1 1",// 中间有一层全排除//"1 1 3 6 8",//"1 2 2 8 5",//"4 4 8 3 3",//"8 8 2 2 4",//"8 3 0 3 1",// 排除开头//"7 1 3 6 8",//"1 2 2 5 5",//"4 4 0 3 3",//"8 0 2 2 4",//"4 3 0 3 1",// 排除结尾//"1 1 3 6 8",//"1 2 2 5 5",//"4 4 0 3 3",//"8 0 2 2 4",//"4 3 0 3 7",// 分层式排除//"0 0 3 6 8",//"1 0 7 7 7",//"4 7 0 0 3",//"7 0 2 0 4",//"4 3 0 0 0",// 分层式排除//"0 0 0 6 8",//"1 7 0 0 7",//"7 0 0 7 3",//"0 0 7 0 4",//"4 0 0 0 0",};int n = int.Parse(readLineStrings[0]);List<int> intKinds = new List<int>();// 高度值的种类Dictionary<Point, int> coordinates = new Dictionary<Point, int>();// 原接收输入的值(还原是个立体坐标系)string[] strList = null;for (int y = 1; y <= n; y++)for (int x = 1; x <= n; x++){strList = readLineStrings[y].Split();int z = int.Parse(strList[x - 1]);coordinates.Add(new Point(x, y), z);if (!intKinds.Contains(z)) // 收集不同的高度值intKinds.Add(z);}intKinds.Sort((int i1, int i2) => { return i2 - i1; });// 自大到小排序不同的高度值SortedDictionary<int, List<int[]>> diffCombineList = new SortedDictionary<int, List<int[]>>();// 高度差值列表<高度差,对应高度差组合>int startHeight = coordinates[new Point(1, 1)];// 起点高度int finHeight = coordinates[new Point(n, n)];// 终点高度// 当存在起点高度等于终点高度 & 这个高度值的数量大于等于最短路线的步数2n-1时,可以添加差值为0的组合if (startHeight == finHeight && coordinates.Count((coordinate) => { return coordinate.Value == startHeight; }) >= 2 * n - 1)diffCombineList.Add(0, new List<int[]> { new int[] { startHeight, finHeight } });for (int i = 0; i < intKinds.Count; i++)for (int j = i + 1; j < intKinds.Count; j++)// ∵起点和终点是必经之地∴起点&终点必定在值域范围内if (intKinds[i] >= startHeight && intKinds[j] <= startHeight && intKinds[i] >= finHeight && intKinds[j] <= finHeight){int diff = intKinds[i] - intKinds[j];// ∵高度值已排序∴intKinds[i] > intKinds[j]if (diffCombineList.ContainsKey(diff))diffCombineList[diff].Add(new int[] { intKinds[i], intKinds[j] });elsediffCombineList.Add(diff, new List<int[]> { new int[] { intKinds[i], intKinds[j] } });}foreach (KeyValuePair<int, List<int[]>> diff_combine in diffCombineList){int diff = diff_combine.Key;foreach (int[] combine in diff_combine.Value)// 从最小高度差开始遍历所有高度差值范围 [h1,h2],其中h1>h2{List<Point> newWalls = new List<Point>();// 当前高度差值范围内视为墙的点int newWallCount = 0;// 将可以堵的路堵上do{newWallCount = 0;for (int y = 1; y <= n; y++)// 遍历所有点for (int x = 1; x <= n; x++){if ((x == 1 && y == 1) || (x == n && y == n))// 排除起点 & 终点continue;int wallCount = 0;// 当前点上下左右的墙数量Point point = new Point(x, y);if (!newWalls.Contains(point)) // 本身不是墙{int mid = coordinates.ContainsKey(point) ? coordinates[point] : -1;// 当前点上方的点高度if (mid > combine[0] || mid < combine[1]) // 高度在范围外的直接视为墙{newWalls.Add(point);newWallCount++;continue;}bool hasUpWall = IsWall(new Point(x, y - 1), coordinates, newWalls, combine);// 当前点上方的点是否为墙bool hasDownWall = IsWall(new Point(x, y + 1), coordinates, newWalls, combine);// 当前点下方的点是否为墙bool hasLeftWall = IsWall(new Point(x - 1, y), coordinates, newWalls, combine);// 当前点左方的点是否为墙bool hasRightWall = IsWall(new Point(x + 1, y), coordinates, newWalls, combine);// 当前点右方的点是否为墙if (hasUpWall) wallCount++;if (hasDownWall) wallCount++;if (hasLeftWall) wallCount++;if (hasRightWall) wallCount++;if (wallCount == 0)continue;if (wallCount == 3 || wallCount == 4)// 3或4面都是墙 = 只有一面是路 就堵上{newWalls.Add(point);newWallCount++;continue;}else //if (wallCount <= 2)// 相邻2面都是墙 = 只有2面是路{bool hasUpLeftWall = IsWall(new Point(x - 1, y - 1), coordinates, newWalls, combine);// 左上方的点是否为墙bool hasUpRightWall = IsWall(new Point(x + 1, y - 1), coordinates, newWalls, combine);// 右上方的点是否为墙bool hasDownLeftWall = IsWall(new Point(x - 1, y + 1), coordinates, newWalls, combine);// 左下方的点是否为墙bool hasDownRightWall = IsWall(new Point(x + 1, y + 1), coordinates, newWalls, combine);// 右下方的点是否为墙if (wallCount == 2// 墙数:有助于简化判断非墙的点&& !((hasUpWall && hasDownWall) || (hasLeftWall && hasRightWall))// 这两面墙是【上&下】或者【左&右】的情况则忽略&& ((hasLeftWall && hasUpWall && !hasDownRightWall)// 两墙靠在左上角|| (hasRightWall && hasUpWall && !hasDownLeftWall)// 两墙靠在右上角|| (hasRightWall && hasDownWall && !hasUpLeftWall)// 两墙靠在右下角|| (hasDownWall && hasLeftWall && !hasUpRightWall)// 两墙靠在左下角)){newWalls.Add(point);newWallCount++;continue;}if (wallCount == 1)// 墙数:有助于简化判断非墙的点{bool hasUpLeftUpWall = IsWall(new Point(x - 1, y - 2), coordinates, newWalls, combine);// 左上上方的点是否为墙bool hasUpLeftLeftWall = IsWall(new Point(x - 2, y - 1), coordinates, newWalls, combine);// 左上左方的点是否为墙bool hasUpRightUpWall = IsWall(new Point(x + 1, y - 2), coordinates, newWalls, combine);// 右上上方的点是否为墙bool hasUpRightRightWall = IsWall(new Point(x + 2, y - 1), coordinates, newWalls, combine);// 右上右方的点是否为墙bool hasDownLeftLeftUpWall = IsWall(new Point(x - 2, y + 1), coordinates, newWalls, combine);// 左下左方的点是否为墙bool hasDownLeftDownWall = IsWall(new Point(x - 1, y + 2), coordinates, newWalls, combine);// 左下下方的点是否为墙bool hasDownRightRightWall = IsWall(new Point(x + 2, y + 1), coordinates, newWalls, combine);// 右下右方的点是否为墙bool hasDownRightDownWall = IsWall(new Point(x + 1, y + 2), coordinates, newWalls, combine);// 右下下方的点是否为墙if ((hasLeftWall && !hasUpRightWall && !hasDownRightWall && (!hasDownRightDownWall || !hasUpRightUpWall))// 左墙|| (hasUpWall && !hasDownLeftWall && !hasDownRightWall && (!hasDownLeftLeftUpWall || !hasDownRightRightWall))// 上墙|| (hasRightWall && !hasUpLeftWall && !hasDownLeftWall && (!hasUpLeftUpWall || !hasDownLeftDownWall))// 右墙|| (hasDownWall && !hasUpLeftWall && !hasUpRightWall && (!hasUpLeftLeftWall || !hasUpRightRightWall))// 下墙){newWalls.Add(point);newWallCount++;continue;}}}}}} while (newWallCount > 0);// 没有新的墙之后就结束循环if (n * n - newWalls.Count < 2 * n - 1) // 如果本来就不能起点终点想通的排除到最后绝对就只剩起点和终点continue;Console.WriteLine("高度差:" + diff + "\t范围:[" + combine[1] + "," + combine[0] + "]");// 下面是额外显示内容for (int y = 1; y <= n; y++){for (int x = 1; x <= n; x++){Point point = new Point(x, y);int z = coordinates[point];if (z > combine[0] || z < combine[1])Console.Write("(-,-," + z + ")");// 不在高度范围内的原题点else if (newWalls.Contains(point))Console.Write("(+,+," + z + ")");// 新添加的堵路点elseConsole.Write("(" + x + "," + y + "," + z + ")");}Console.WriteLine();}// 在起点开始走(这里其实就是验证作用而已)Point startPoint = new Point(1, 1), startLeftWall = new Point(1, 1 - 1), nowPoint, nowleftWall, nextPoint = new Point(0, 0), nextLeftWall = new Point(0, 0);nowPoint = startPoint;nowleftWall = startLeftWall;// 构建模型时已经确定是墙string direction = "";// 方向List<Point> crossPoints = new List<Point> { startPoint };// 路线do{double nowX = nowPoint.X, nowY = nowPoint.Y;// 起点坐标if (nowX == nowleftWall.X) // 同一竖线的一上一下{if (nowY > nowleftWall.Y) // 按照题目靠下方的坐标值大direction = "right";else if (nowY < nowleftWall.Y)direction = "left";}else if (nowY == nowleftWall.Y)  // 同一横线的一左一右{if (nowX > nowleftWall.X) // 按照题目靠右方的坐标值大direction = "up";else if (nowX < nowleftWall.X)direction = "down";}elsethrow new ArgumentOutOfRangeException("当前点坐标值有误,导致方向有误:nowX=" + nowX + "\tnowY:" + nowY);switch (direction)// 求前方及前方左墙的点坐标{case "right":nextPoint = new Point(nowX + 1, nowY);nextLeftWall = new Point(nowX + 1, nowY - 1);break;case "left":nextPoint = new Point(nowX - 1, nowY);nextLeftWall = new Point(nowX - 1, nowY + 1);break;case "up":nextPoint = new Point(nowX, nowY - 1);nextLeftWall = new Point(nowX - 1, nowY - 1);break;case "down":nextPoint = new Point(nowX, nowY + 1);nextLeftWall = new Point(nowX + 1, nowY + 1);break;default:throw new ArgumentOutOfRangeException("当前点坐标值有误,导致方向有误:nowX=" + nowX + "\tnowY:" + nowY);}crossPoints.Add(nextPoint);// 路线记录if (!coordinates.ContainsKey(nextPoint) || newWalls.Contains(nextPoint) || coordinates[nextPoint] > combine[0] || coordinates[nextPoint] < combine[1]) // 前方是墙,原地右转,右转后新的墙坐标刚好是上一步前方的坐标{nextLeftWall = nextPoint;// 把前方变成下一步的左墙nextPoint = nowPoint;// 退回原地direction = "turnRight";crossPoints.RemoveAt(crossPoints.Count - 1);// 路线记录:退回原地=删除刚刚的直走}// 前方及前左边都不是墙,直接符合情况3,左转+直走,跟当前的直走并起来就是直走+左转+直走,相当于墙点不变,原点往原点左墙的方向斜着走一步,刚好就是直走后的左墙else if (coordinates.ContainsKey(nextLeftWall) && !newWalls.Contains(nextLeftWall) && coordinates[nextLeftWall] <= combine[0] && coordinates[nextLeftWall] >= combine[1]){nextPoint = nextLeftWall;nextLeftWall = nowleftWall;crossPoints.Add(nextPoint);// 路线记录switch (direction){case "right":direction = "up&Right";break;case "left":direction = "down&left";break;case "up":direction = "up&left";break;case "down":direction = "down&Right";break;default:throw new ArgumentOutOfRangeException("当前点坐标值有误,导致方向有误:nowX=" + nowX + "\tnowY:" + nowY);}}if (nextPoint == new Point(0, 0) || nextLeftWall == new Point(0, 0))new ArgumentOutOfRangeException("当前点坐标值有误,导致计算直走后的新坐标有误:nowX=" + nowX + "\tnowY:" + nowY);nowPoint = nextPoint;nowleftWall = nextLeftWall;} while (crossPoints.Count((bcp) => { return bcp == new Point(1, 1); }) < 2);// 当有两个起点时意味着路线已经先沿着左墙再沿着右墙回到起点,即使没经过终点if (crossPoints.Contains(new Point(n, n)))// 如果能走到出口则正解{Console.Write("贴墙路线:");Console.WriteLine();int rightEndIndex = 0;Console.Write("贴左墙路线:");for (int i = 0; i < crossPoints.Count; i++){Console.Write("(" + crossPoints[i] + ")");if (crossPoints[i] == new Point(n, n)){rightEndIndex = i;break;}}Console.WriteLine();Console.Write("贴右墙路线:");for (int i = crossPoints.Count-1; i >= rightEndIndex; i--){Console.Write("(" + crossPoints[i] + ")");if (crossPoints[i] == new Point(n, n)){rightEndIndex = i;break;}}//break;// 放出来则不求右墙路线}Console.WriteLine();}//if (bestCrossPoints.Contains(new Point(n, n))) break;// 放出来则不求其他高度差方案}Console.ReadKey();
     /// <summary>/// 当前点是否为墙/// </summary>/// <param name="point">点坐标</param>/// <param name="coordinates">立体坐标系</param>/// <param name="newWalls">当前高度差的墙坐标集合</param>/// <param name="combine">当前高度差</param>/// <returns></returns>private static bool IsWall(Point point, Dictionary<Point, int> coordinates, List<Point> newWalls, int[] combine) {int high = coordinates.ContainsKey(point) ? coordinates[point] : -1;// 点高度return newWalls.Contains(point) || high > combine[0] || high < combine[1];// 是否为墙}

运行结果


题外扩展2——结合扩展1封路后用栈方式寻找最佳路线:

按扩展1逻辑,封路后能连通起点终点的情况下,用类似【蚯蚓走迷宫实验过程】试探出所有路线
最短的路线就显而易见

代码

using System;
using System.Collections.Generic;
using System.Linq;
using System.Windows;
     static void Main(string[] args){List<string> readLineStrings = new List<string> {// 这里方便测试不设置输入"5",// 原题//"1 1 3 6 8",//"1 2 2 5 5",//"4 4 0 3 3",//"8 0 2 2 4",//"4 3 0 3 1",// 双路线//"1 1 1 1 1",//"1 4 4 4 1",//"1 4 4 4 1",//"1 4 4 4 1",//"1 1 1 1 1",// 3路线"1 1 1 1 1","1 1 1 4 1","1 4 1 4 1","1 4 1 1 1","1 1 1 1 1",// 中间有一层全排除//"1 1 3 6 8",//"1 2 2 8 5",//"4 4 8 3 3",//"8 8 2 2 4",//"8 3 0 3 1",// 排除开头//"7 1 3 6 8",//"1 2 2 5 5",//"4 4 0 3 3",//"8 0 2 2 4",//"4 3 0 3 1",// 排除结尾//"1 1 3 6 8",//"1 2 2 5 5",//"4 4 0 3 3",//"8 0 2 2 4",//"4 3 0 3 7",// 分层式排除//"0 0 3 6 8",//"1 0 7 7 7",//"4 7 0 0 3",//"7 0 2 0 4",//"4 3 0 0 0",// 分层式排除//"0 0 0 6 8",//"1 7 0 0 7",//"7 0 0 7 3",//"0 0 7 0 4",//"4 0 0 0 0",};int n = int.Parse(readLineStrings[0]);List<int> intKinds = new List<int>();// 高度值的种类Dictionary<Point, int> coordinates = new Dictionary<Point, int>();// 原接收输入的值(还原是个立体坐标系)string[] strList = null;for (int y = 1; y <= n; y++)for (int x = 1; x <= n; x++){strList = readLineStrings[y].Split();int z = int.Parse(strList[x - 1]);coordinates.Add(new Point(x, y), z);if (!intKinds.Contains(z)) // 收集不同的高度值intKinds.Add(z);}intKinds.Sort((int i1, int i2) => { return i2 - i1; });// 自大到小排序不同的高度值SortedDictionary<int, List<int[]>> diffCombineList = new SortedDictionary<int, List<int[]>>();// 高度差值列表<高度差,对应高度差组合>int startHeight = coordinates[new Point(1, 1)];// 起点高度int finHeight = coordinates[new Point(n, n)];// 终点高度// 当存在起点高度等于终点高度 & 这个高度值的数量大于等于最短路线的步数2n-1时,可以添加差值为0的组合if (startHeight == finHeight && coordinates.Count((coordinate) => { return coordinate.Value == startHeight; }) >= 2 * n - 1)diffCombineList.Add(0, new List<int[]> { new int[] { startHeight, finHeight } });for (int i = 0; i < intKinds.Count; i++)for (int j = i + 1; j < intKinds.Count; j++)// ∵起点和终点是必经之地∴起点&终点必定在值域范围内if (intKinds[i] >= startHeight && intKinds[j] <= startHeight && intKinds[i] >= finHeight && intKinds[j] <= finHeight){int diff = intKinds[i] - intKinds[j];// ∵高度值已排序∴intKinds[i] > intKinds[j]if (diffCombineList.ContainsKey(diff))diffCombineList[diff].Add(new int[] { intKinds[i], intKinds[j] });elsediffCombineList.Add(diff, new List<int[]> { new int[] { intKinds[i], intKinds[j] } });}foreach (KeyValuePair<int, List<int[]>> diff_combine in diffCombineList){int diff = diff_combine.Key;foreach (int[] combine in diff_combine.Value)// 从最小高度差开始遍历所有高度差值范围 [h1,h2],其中h1>h2{List<Point> newWalls = new List<Point>();// 当前高度差值范围内视为墙的点int newWallCount = 0;// 将可以堵的路堵上do{newWallCount = 0;for (int y = 1; y <= n; y++)// 遍历所有点for (int x = 1; x <= n; x++){if ((x == 1 && y == 1) || (x == n && y == n))// 排除起点 & 终点continue;int wallCount = 0;// 当前点上下左右的墙数量Point point = new Point(x, y);if (!newWalls.Contains(point)) // 本身不是墙{int mid = coordinates.ContainsKey(point) ? coordinates[point] : -1;// 当前点上方的点高度if (mid > combine[0] || mid < combine[1]) // 高度在范围外的直接视为墙{newWalls.Add(point);newWallCount++;continue;}bool hasUpWall = IsWall(new Point(x, y - 1), coordinates, newWalls, combine);// 当前点上方的点是否为墙bool hasDownWall = IsWall(new Point(x, y + 1), coordinates, newWalls, combine);// 当前点下方的点是否为墙bool hasLeftWall = IsWall(new Point(x - 1, y), coordinates, newWalls, combine);// 当前点左方的点是否为墙bool hasRightWall = IsWall(new Point(x + 1, y), coordinates, newWalls, combine);// 当前点右方的点是否为墙if (hasUpWall) wallCount++;if (hasDownWall) wallCount++;if (hasLeftWall) wallCount++;if (hasRightWall) wallCount++;if (wallCount == 0)continue;if (wallCount == 3 || wallCount == 4)// 3或4面都是墙 = 只有一面是路 就堵上{newWalls.Add(point);newWallCount++;continue;}else //if (wallCount <= 2)// 相邻2面都是墙 = 只有2面是路{bool hasUpLeftWall = IsWall(new Point(x - 1, y - 1), coordinates, newWalls, combine);// 左上方的点是否为墙bool hasUpRightWall = IsWall(new Point(x + 1, y - 1), coordinates, newWalls, combine);// 右上方的点是否为墙bool hasDownLeftWall = IsWall(new Point(x - 1, y + 1), coordinates, newWalls, combine);// 左下方的点是否为墙bool hasDownRightWall = IsWall(new Point(x + 1, y + 1), coordinates, newWalls, combine);// 右下方的点是否为墙if (wallCount == 2// 墙数:有助于简化判断非墙的点&& !((hasUpWall && hasDownWall) || (hasLeftWall && hasRightWall))// 这两面墙是【上&下】或者【左&右】的情况则忽略&& ((hasLeftWall && hasUpWall && !hasDownRightWall)// 两墙靠在左上角|| (hasRightWall && hasUpWall && !hasDownLeftWall)// 两墙靠在右上角|| (hasRightWall && hasDownWall && !hasUpLeftWall)// 两墙靠在右下角|| (hasDownWall && hasLeftWall && !hasUpRightWall)// 两墙靠在左下角)){newWalls.Add(point);newWallCount++;continue;}if (wallCount == 1)// 墙数:有助于简化判断非墙的点{bool hasUpLeftUpWall = IsWall(new Point(x - 1, y - 2), coordinates, newWalls, combine);// 左上上方的点是否为墙bool hasUpLeftLeftWall = IsWall(new Point(x - 2, y - 1), coordinates, newWalls, combine);// 左上左方的点是否为墙bool hasUpRightUpWall = IsWall(new Point(x + 1, y - 2), coordinates, newWalls, combine);// 右上上方的点是否为墙bool hasUpRightRightWall = IsWall(new Point(x + 2, y - 1), coordinates, newWalls, combine);// 右上右方的点是否为墙bool hasDownLeftLeftUpWall = IsWall(new Point(x - 2, y + 1), coordinates, newWalls, combine);// 左下左方的点是否为墙bool hasDownLeftDownWall = IsWall(new Point(x - 1, y + 2), coordinates, newWalls, combine);// 左下下方的点是否为墙bool hasDownRightRightWall = IsWall(new Point(x + 2, y + 1), coordinates, newWalls, combine);// 右下右方的点是否为墙bool hasDownRightDownWall = IsWall(new Point(x + 1, y + 2), coordinates, newWalls, combine);// 右下下方的点是否为墙if ((hasLeftWall && !hasUpRightWall && !hasDownRightWall && (!hasDownRightDownWall || !hasUpRightUpWall))// 左墙|| (hasUpWall && !hasDownLeftWall && !hasDownRightWall && (!hasDownLeftLeftUpWall || !hasDownRightRightWall))// 上墙|| (hasRightWall && !hasUpLeftWall && !hasDownLeftWall && (!hasUpLeftUpWall || !hasDownLeftDownWall))// 右墙|| (hasDownWall && !hasUpLeftWall && !hasUpRightWall && (!hasUpLeftLeftWall || !hasUpRightRightWall))// 下墙){newWalls.Add(point);newWallCount++;continue;}}}}}} while (newWallCount > 0);// 没有新的墙之后就结束循环if (n * n - newWalls.Count < 2 * n - 1) // 如果本来就不能起点终点想通的排除到最后绝对就只剩起点和终点continue;Console.WriteLine("高度差:" + diff + "\t范围:[" + combine[1] + "," + combine[0] + "]");// 下面是额外显示内容for (int y = 1; y <= n; y++){for (int x = 1; x <= n; x++){Point point = new Point(x, y);int z = coordinates[point];if (z > combine[0] || z < combine[1])Console.Write("(-,-," + z + ")");// 不在高度范围内的原题点else if (newWalls.Contains(point))Console.Write("(+,+," + z + ")");// 新添加的堵路点elseConsole.Write("(" + x + "," + y + "," + z + ")");}Console.WriteLine();}// 在起点开始走(这里其实就是验证作用而已)Point startPoint = new Point(1, 1), startLeftWall = new Point(1, 1 - 1), nowPoint, nowleftWall, nextPoint = new Point(0, 0), nextLeftWall = new Point(0, 0);nowPoint = startPoint;nowleftWall = startLeftWall;// 构建模型时已经确定是墙Stack<KeyValuePair<Point, Dictionary<Point, bool>>> divergingPoint_points = new Stack<KeyValuePair<Point, Dictionary<Point, bool>>>();// 分叉路点Point farestPoint = new Point(1, 1);// 当前路线走到最远的点 = 当前分岔点do{Point farestPointUp = new Point(farestPoint.X, farestPoint.Y - 1);// 当前分岔点上方坐标Point farestPointDown = new Point(farestPoint.X, farestPoint.Y + 1);// 当前分岔点下方坐标Point farestPointLeft = new Point(farestPoint.X - 1, farestPoint.Y);// 当前分岔点左方坐标Point farestPointRight = new Point(farestPoint.X + 1, farestPoint.Y);// 当前分岔点右方坐标bool isUpRoad = !IsWall(farestPointUp, coordinates, newWalls, combine);// 当前分岔点上方能否走bool isDownRoad = !IsWall(farestPointDown, coordinates, newWalls, combine);// 当前分岔点下方能否走bool isLeftRoad = !IsWall(farestPointLeft, coordinates, newWalls, combine);// 当前分岔点左方能否走bool isRightRoad = !IsWall(farestPointRight, coordinates, newWalls, combine);// 当前分岔点右方能否走Dictionary<Point, bool> divergingPoints = new Dictionary<Point, bool>();// 当前点分岔路的点,是否未走过if (divergingPoint_points.Any() && divergingPoint_points.Peek().Key == farestPoint)// 当前点是最近到过分岔点divergingPoints = divergingPoint_points.Peek().Value;else// 该方向能走 && 不是来时方向 == 添加新的分岔点 (如果是直路,当前分岔点则只有一个分岔点){if (isUpRoad && !divergingPoint_points.Any((p) => { return p.Key == farestPointUp; }))divergingPoints.Add(farestPointUp, true);if (isDownRoad && !divergingPoint_points.Any((p) => { return p.Key == farestPointDown; }))divergingPoints.Add(farestPointDown, true);if (isLeftRoad && !divergingPoint_points.Any((p) => { return p.Key == farestPointLeft; }))divergingPoints.Add(farestPointLeft, true);if (isRightRoad && !divergingPoint_points.Any((p) => { return p.Key == farestPointRight; }))divergingPoints.Add(farestPointRight, true);divergingPoint_points.Push(new KeyValuePair<Point, Dictionary<Point, bool>>(farestPoint, divergingPoints));}bool isDeadLoad = false;// 是否走到尽头// 不是冤枉路/回头路 && 该方向有路 && 该分岔路未走过if (!divergingPoints.Any())// 无路可走/当前点所有岔路已经走过isDeadLoad = true;else if (isUpRoad && divergingPoints.ContainsKey(farestPointUp) && divergingPoints[farestPointUp])farestPoint = farestPointUp;else if (isDownRoad && divergingPoints.ContainsKey(farestPointDown) && divergingPoints[farestPointDown])farestPoint = farestPointDown;else if (isLeftRoad && divergingPoints.ContainsKey(farestPointLeft) && divergingPoints[farestPointLeft])farestPoint = farestPointLeft;else if (isRightRoad && divergingPoints.ContainsKey(farestPointRight) && divergingPoints[farestPointRight])farestPoint = farestPointRight;if (farestPoint == new Point(n, n))// 走到终点{List<KeyValuePair<Point, Dictionary<Point, bool>>> roads = new List<KeyValuePair<Point, Dictionary<Point, bool>>>(divergingPoint_points);roads.Reverse();// 反向排序foreach (KeyValuePair<Point, Dictionary<Point, bool>> road in roads) // 打印当前路线Console.Write("(" + road.Key + ")");Console.WriteLine("(" + farestPoint + ")");isDeadLoad = true;}if (isDeadLoad){do{divergingPoint_points.Pop();} while (divergingPoint_points.Any() && divergingPoint_points.Peek().Value.All((p_b) => { return !p_b.Value; }));// 回到最近的分岔点if (divergingPoint_points.Any())farestPoint = divergingPoint_points.Peek().Key;elsebreak;// 不存在未走过的分叉路}elsedivergingPoints[farestPoint] = false;// 标志该岔路点已走过} while (true);}//if (bestCrossPoints.Contains(new Point(n, n))) break;// 放出来则不求其他高度差方案}Console.ReadKey();}
     /// <summary>/// 当前点是否为墙/// </summary>/// <param name="point">点坐标</param>/// <param name="coordinates">立体坐标系</param>/// <param name="newWalls">当前高度差的墙坐标集合</param>/// <param name="combine">当前高度差</param>/// <returns></returns>private static bool IsWall(Point point, Dictionary<Point, int> coordinates, List<Point> newWalls, int[] combine){int high = coordinates.ContainsKey(point) ? coordinates[point] : -1;// 点高度return newWalls.Contains(point) || high > combine[0] || high < combine[1];// 是否为墙}

运行结果

题外扩展3——相似题:

之前有道相似的题目,相当于这题改成了固定只能走最短的步数(2*n-1),且求最小高度而非高度差;
其模型可以看成一个【菱形二叉树】,就是把输入样本顺时针转一小下(差不多45°的样子),这样就是跟题目里“飞”的路线一样,从起点一次“飞”一层往下“飞”到终点;
(但代码里在新列阵中要进行原坐标x,y与新点阵的转换运算,空间思维不强的可以在coordinateLists新列阵形成时添加(坐标,高度)的键值对作为添加对象,或者构造一个int[][][] 三维数组或者集合进行讨论,就是不旋转)
旋转后的新列阵:

新列阵中的原坐标:

从列阵坐标可以看出,每层的坐标值的和(x+y)一定且依次+1递增,且和的值域为【2 ~ 2 * n】,以此为规则在原坐标阵的基础上重新分组,一行一组;

然后使用排除法进行筛选方案得出最大高度的最小值:
例如:
排除[0,7]外的高度值后,列阵就变成下面的点阵:
(其中紫色的是因为红色的排除后没有上层的点够得着,所以也作为推算排除点一并排除)

排除到差值为3,组合为[0,3]的时候就有下面的图,同样形成【有路可走】:.
(其中蓝色的是因为红色的排除后没有下层的点够得着,所以也作为推算排除点一并排除)

但当排除到高度差值为2,值域为[0,2]时就会有下面的图:

这时终点是够不着,导致起点终点连不上,形成【无路可走】,所以这类高度差值组合就会排除;

然后我们从高度差值最小的开始找,最先发现能这里就讨论【有路可走】的条件——至少保留一条连接起点和终点的路线:
除了终点、起点外,每层至少存在一个同时连接其上层左上角或右上角以及下层的左下角或右下角的坐标点;

(也可以反过来从小的高度排查处理,直到遇到第一个【有路可走】:
每一层都有小于??高度且能连接上下的坐标点)

代码

         List<string> readLineStrings = new List<string> {// 方便测试"5",// 原题"1 1 3 6 8","1 2 2 5 5","4 4 0 3 3","8 0 2 2 4","4 3 0 3 1",// 中间有一层全排除//"1 1 3 6 8",//"1 2 2 8 5",//"4 4 8 3 3",//"8 8 2 2 4",//"8 3 0 3 1",// 排除开头//"7 1 3 6 8",//"1 2 2 5 5",//"4 4 0 3 3",//"8 0 2 2 4",//"4 3 0 3 1",// 排除结尾//"1 1 3 6 8",//"1 2 2 5 5",//"4 4 0 3 3",//"8 0 2 2 4",//"4 3 0 3 7",// 分层式排除//"0 0 3 6 8",//"1 0 7 7 7",//"4 7 0 0 3",//"7 0 2 0 4",//"4 3 0 0 0",};int n =int.Parse( readLineStrings[0]);List<int> intKinds = new List<int>();// 高度值的种类List<List<int>> coordinates = new List<List<int>>();// 原接收输入的值for (int i = 1; i <= n; i++){string[] strList = readLineStrings[i].Split();List<int> intList = new List<int>();for (int j = 0; j < n; j++){string str = strList[j];int height = int.Parse(str);intList.Add(height);// 收集行内高度值if (!intKinds.Contains(height)) // 收集不同的高度值{intKinds.Add(height);}}coordinates.Add(intList);// 收集某行高度值}intKinds.Sort((int i1,int i2)=> { return i2 - i1; });// 自大到小排序不同的高度值List<List<int>> coordinateLists = new List<List<int>>();// 新的列阵集合组for (int i = 2; i <= 2 * n; i++) // 每层的坐标值的和(x + y)依次 + 1递增,且和的值域为【2 ~2 * n】{List<int> addList=new List<int>(); // 新的列阵单行集合for (int j = 1; j <= n; j++){for (int k = 1; k <= n; k++){if (j + k == i)// 坐标值的和固定为:i=j+k{// 由于当前循环是在原坐标自上往下,而在新列阵是由原列阵顺时针旋转45°得到的,会从新列阵的队尾开始检索到,所以要反向排序addList.Insert(0, coordinates[j-1][k-1]);}}}coordinateLists.Add(addList);}List<Point> points = new List<Point>();// 整个新列阵里能连接接上下层的原坐标/************************这步只是想看下符合的坐标,不做这步也可以求出结果**********************/Dictionary<int, string> results = new Dictionary<int, string>();// 排除不同高度值后的坐标点阵Dictionary<int, List<Point>> deductivePoints = new Dictionary<int, List<Point>>();/*******************************************************************************************/string result = "";for (int i = 0; i < intKinds.Count; i++){/************************这步只是想看下符合的坐标,不做这步也可以求出结果**********************/result = "";for (int j = 1; j <= n; j++){for (int k = 1; k <= n; k++){if (i == 0 || points.Any((point) =>{return point.X == j && point.Y == k;})){result += "(" + j + "," + k + "," + coordinates[j - 1][k - 1] + ")\t";}else{result += "(-,-," + coordinates[j - 1][k - 1] + ")\t";}}result += "\n";}results.Add(intKinds[i], result);/*******************************************************************************************/points.Clear();// 循环前清空收集结果coordinateLists.ForEach((coordinateList) =>// 将新的列阵集合组里的【待排除高度值】改为-1,视为为不可走{for (int j = 0; j < coordinateList.Count; j++)// 由于涉及到改基础数据类型值,不合适ForEach方法遍历{if (coordinateList[j] == intKinds[i]){coordinateList[j] = -1;}}});bool hasLinkPoint = false;// 是否有上下连通的坐标点for (int j = 0; j < coordinateLists.Count; j++)// 第j+1层{List<int> coordinateList = coordinateLists[j];if (j == 0 || j == coordinateLists.Count - 1)// 起点、终点 特值处理{if (coordinateLists[j][0] != -1){if (j == 0){points.Add(new Point(1, 1));// 起点}else{points.Add(new Point(n, n));// 终点}continue;}else break;}hasLinkPoint = false;for (int k = 0; k < coordinateList.Count; k++){if (coordinateList[k] == -1) continue;// 非排除点// 左上角、右上角、左下角、右下角高度值(默认-1)int upLeft = -1, upRight = -1, downLeft = -1, downRight = -1;if (j + 1 < n)// 新列阵上半部分必定有左下和右下的数{if (k > 0)// 非左上方的边上坐标才有左上角的坐标{upLeft = coordinateLists[j - 1][k - 1];}if (k < coordinateLists[j - 1].Count)// 非右上方的边上坐标才有右上角的坐标{upRight = coordinateLists[j - 1][k];}// 上半层的坐标必有左下角&右下角坐标downLeft = coordinateLists[j + 1][k];downRight = coordinateLists[j + 1][k + 1];}else if (j + 1 == n) // 列阵中间层(新列阵里最长的一层){if (k > 0)// 除第一个坐标外都有左上角&左下角的坐标{upLeft = coordinateLists[j - 1][k - 1];downLeft = coordinateLists[j + 1][k - 1];}if (k < coordinateLists[j - 1].Count)// 除最后一个坐标外都有右上角&右下角的坐标{upRight = coordinateLists[j - 1][k];downRight = coordinateLists[j + 1][k];}}else//  (j + 1 > n) // 列阵下半部分必定有左上角和右上角的坐标{// 上半层的坐标必有左上角&右上角坐标upLeft = coordinateLists[j - 1][k];upRight = coordinateLists[j - 1][k + 1];if (k > 0)// 非左下方边上的坐标才有左下角的坐标{downLeft = coordinateLists[j + 1][k - 1];}if (k < coordinateLists[j + 1].Count)// 非右下方边上的坐标才有左右角的坐标{downRight = coordinateLists[j + 1][k];}}int x, y;// 原坐标值if (j + 1 <= n) // 上半层及中间层{y = k + 1;}else// 下半层(中间层也适用){y = n - (coordinateList.Count - (k + 1));}x = j + 1 + 1 - y;// 层数(j+1)+1是两个坐标的和if ((downLeft != -1 || downRight != -1) // 能连接下层坐标(左下角或右下角坐标)&& points.Any((point) =>// 能连接上层坐标且在已校验的结果中{return (upLeft != -1 && point.X == x && point.Y == y - 1) || (upRight != -1 && point.X == x - 1 && point.Y == y);})){points.Add(new Point(x, y));hasLinkPoint = true;}else{if (deductivePoints.ContainsKey(intKinds[i])){deductivePoints[intKinds[i]].Add(new Point(x, y));}else {deductivePoints.Add(intKinds[i], new List<Point> { new Point(x, y) });}}}if (!hasLinkPoint) break;// 只要有一层没有了连接上下层的点,那么排除后的所有路线都会变成【无路可走】,而当前排除值的前一个排除值就是答案}if (!hasLinkPoint){Console.WriteLine("结果:" + intKinds[i]);Console.WriteLine(results[intKinds[i]]);// 显示测试效果Console.WriteLine("推算排除点:");// 测试用if (i > 0){Console.WriteLine("推算排除点:");// 测试用deductivePoints[intKinds[i - 1]].ForEach((point) =>{Console.Write("(" + point.X + "," + point.Y + ")\t");});Console.WriteLine();}break;}}Console.ReadKey();

运行结果

C#试玩程序设计试题——定向越野(迷宫)相关推荐

  1. C#试玩程序设计试题——拱猪计分

    题目链接: https://wenku.baidu.com/view/d8253e24f90f76c660371ac2.html?from=search 问题描述: 拱猪是一种有趣的扑克牌游戏.即使你 ...

  2. 质数的后代c语言,(信息学奥赛辅导)程序设计试题汇编(答案10)

    (信息学奥赛辅导)程序设计试题汇编(答案10) 更新时间:2017/1/26 1:12:00  浏览量:741  手机版 程序设计试题及答案 (备注:试题难度评价采取五★级评价体系,分基础.容易.一般 ...

  3. 试玩系列 | 真香!大疆TT无人机编程初体验,教你对它为所欲为!

    先放个项目演示视频镇帖(点击小程序查看演示视频): 认识我的朋友,大概都知道,我是一个"运气爆棚"的人,经常能"捡"到一些好玩的东西.这不,前两天在家门口&qu ...

  4. 校招linux面试题,2013华为校招机试与面试题整理

    2013华为校招机试与面试题整理 2013华为校招机试与面试题整理 1 (1.) 字母大小写反转 这到题没什么可说的,只是我很久没写这样要IO输入输出的代码,当时看到华为的提示纸条上写着"只 ...

  5. 应用、游戏和品牌的新营销方式-试玩广告

    一.什么是"试玩广告"? 试玩广告是部署为广告格式的迷你游戏和教程.它们提供应用程序提供的预览.游戏和非游戏应用程序都采用可玩广告来提高用户获取 (UA). 与传统广告格式不同,试 ...

  6. java程序设计教程试题_java程序设计试题库.doc

    java程序设计试题库.doc 还剩 67页未读, 继续阅读 下载文档到电脑,马上远离加班熬夜! 亲,很抱歉,此页已超出免费预览范围啦! 如果喜欢就下载吧,价低环保! 内容要点: <Java 语 ...

  7. 网络赚钱方法之游戏试玩赚钱

    游戏试玩赚钱平台排名 第一名:聚享游 注册聚享游:http://www.juxiangyou.com/ 聚享游成立已经4年时间,在天天钻还没出来之前一直是试玩界的老大,后面被天天钻超越,现在经过多次调 ...

  8. 假设当年产值为100c语言答案,C语言程序设计试题题库含答案zdui.doc

    C语言程序设计试题题库含答案zdui 班号姓名 C语言 试 题 题号一二三四五六七八九十总分附加题分数 一.选择题:(20分,每题2分) 1.以下不正确的C语言标识符是( ). A. ABC B. a ...

  9. 假设当年产值为100c语言答案,C语言程序设计试题题库含答案zdui汇总.doc

    C语言程序设计试题题库含答案zdui汇总 班号姓名 C语言 试 题 题号一二三四五六七八九十总分附加题分数 一.选择题:(20分,每题2分) 1.以下不正确的C语言标识符是( ). A. ABC B. ...

  10. c语言程序设计试题及答案十,C语言程序设计试题试题及答案.doc

    <C语言程序设计试题试题及答案.doc>由会员分享,可在线阅读,更多相关<C语言程序设计试题试题及答案.doc(49页珍藏版)>请在装配图网上搜索. 1.C ,C+(120)1 ...

最新文章

  1. 统计学习方法笔记(四)-最大熵模型原理及python实现
  2. Sklearn中的CV与KFold详解
  3. xcode 4.2 如何调试 EXC_BAD_ACCESS
  4. Python - 列表与字符串的互相转换
  5. IP插件:批量替换论文图片
  6. CSC 121, 122.. MAT 181, 182, 252, 271, 281, 474.. ECN 272, 273, 372, 472
  7. 站长工具:天和流量王绿色版 下载
  8. 非对称网络不通 子网掩码是“祸首”
  9. 某大型银行深化系统技术方案之八:核心层之异步流程控制机制
  10. Bag-of-words模型、TF-IDF模型
  11. MaxScript批量修改材质、贴图名称
  12. BugKu-MISC
  13. C#,欧拉数(Eulerian Number)的算法与源代码
  14. 云脉芯联加入龙蜥社区,共建网络“芯”生态
  15. html网页怎么自动返回,返回首页html代码?打开某个特定网页时,网页总是自动跳回主页,是怎?...
  16. redis中的incr和incrBy
  17. Android调试高德SDK,如何获取SHA1?
  18. IPVS -三种IP负载均衡技术与八种调度算法
  19. java的maxrow_聊聊pg jdbc statement的maxRows参数
  20. 笨办法学python第五版_笨办法学python PDF下载|笨办法学python第五版 电子版附目录_最火软件站...

热门文章

  1. HHL算法第四弹(回顾伴随、正定算子、半正定算子、正规算子、酉矩阵、幺正矩阵、厄米矩阵,极式分解,奇异值分解)
  2. 南宁:“数字城管”让智慧城市建设提质提速
  3. window10_vs2015安装教程
  4. SAP ByDesign Cloud 中的条形码扫描知识
  5. 条码扫描枪在仓库管理无线网络AP解决方案
  6. 使用Python实战反欺诈模型
  7. Failed installing tomcat9 service
  8. treetable怎么带参数_Layui实现TreeTable(树形数据表格)
  9. 百度api翻译html,帮助文档首页
  10. 从“星空”主题绘画系统出发寻求绘画的可能性