BFS、双向BFS

目录

一,BFS

1,显式树

2,隐式树

POJ 3126 Prime Path(素数变换路径)

3,显式图

力扣 1466. 重新规划路线

力扣 1042. 不邻接植花

4,隐式图

HDU 2102 A计划

POJ 1915 Knight Moves

力扣 1197. 进击的骑士

CSU 1224 ACM小组的古怪象棋

CSU 1046 追杀

HDU 1241 Oil Deposits(连通块的数量)

HDU 1312 Numerically Speaking

ZOJ 1649 Rescue

力扣 529. 扫雷游戏

力扣 778. 水位上升的泳池中游泳

力扣 841. 钥匙和房间

力扣 1631. 最小体力消耗路径(优先队列)

力扣 1971. 寻找图中是否存在路径

力扣 542. 01 矩阵

力扣 1345. 跳跃游戏 IV

二,BFS多起点洪泛

力扣 934. 最短的桥

力扣 1162. 地图分析

其他实战

三,反向BFS

力扣 1245. 树的直径

力扣 1522. N 叉树的直径

力扣 310. 最小高度树

四,双向BFS

vijos 1360 八数码问题

HackerRank - pacman-astar

力扣 752. 打开转盘锁

五,广度优先树


一,BFS

参考 DFS

1,显式树

2,隐式树

POJ 3126 Prime Path(素数变换路径)

题目:

Description

The ministers of the cabinet were quite upset by the message from the Chief of Security stating that they would all have to change the four-digit room numbers on their offices. 
— It is a matter of security to change such things every now and then, to keep the enemy in the dark. 
— But look, I have chosen my number 1033 for good reasons. I am the Prime minister, you know! 
— I know, so therefore your new number 8179 is also a prime. You will just have to paste four new digits over the four old ones on your office door. 
— No, it’s not that simple. Suppose that I change the first digit to an 8, then the number will read 8033 which is not a prime! 
— I see, being the prime minister you cannot stand having a non-prime number on your door even for a few seconds. 
— Correct! So I must invent a scheme for going from 1033 to 8179 by a path of prime numbers where only one digit is changed from one prime to the next prime. 

Now, the minister of finance, who had been eavesdropping, intervened. 
— No unnecessary expenditure, please! I happen to know that the price of a digit is one pound. 
— Hmm, in that case I need a computer program to minimize the cost. You don't know some very cheap software gurus, do you? 
— In fact, I do. You see, there is this programming contest going on... Help the prime minister to find the cheapest prime path between any two given four-digit primes! The first digit must be nonzero, of course. Here is a solution in the case above. 

1033 
1733 
3733 
3739 
3779 
8779 
8179

The cost of this solution is 6 pounds. Note that the digit 1 which got pasted over in step 2 can not be reused in the last step – a new 1 must be purchased.

Input

One line with a positive number: the number of test cases (at most 100). Then for each test case, one line with two numbers separated by a blank. Both numbers are four-digit primes (without leading zeros).

Output

One line for each case, either with a number stating the minimal cost or containing the word Impossible.

Sample Input

3
1033 8179
1373 8017
1033 1033

Sample Output

6
7
0

这个题目是用广度优先搜索。

每次都出队得到一个数,然后对和这个数只隔了一个数码的数(一共有8+9+9+9=35个)进行判断,如果是素数那么就进队。

这样我开始的代码是:

#include<iostream>
#include<queue>
using namespace std;


int n, a, b;
int list[10000];
queue<int>q;


bool is_prime(int n)
{
	if (n % 2 == 0)return false;
	for (int i = 3; i*i <= n; i += 2)if (n%i == 0)return false;
	return true;
}


void f(int t)
{
	if (is_prime(t) && t != a)
	{
		q.push(t);
		list[t] = list[a] + 1;
	}
}


int main()
{
	int a1, a2, a3, a4;
	cin >> n;
	while (n--)
	{
		cin >> a >> b;
		q.push(a);
		int sum = 0;
		memset(list, -1, sizeof(list));
		list[a] = 0;
		while (!q.empty() && list[b]<0)
		{
			a = q.front();//a=a1a2a3a4
			q.pop();
			a4 = a % 10;
			a3 = (a / 10) % 10;
			a2 = (a / 100) % 10;
			a1 = a / 1000;
			int t;
			for (int i = 1; i < 10; i++)f(a - a1 * 1000 + i * 1000);
			for (int i = 0; i < 10; i++)
			{
				f(a - a2 * 100 + i * 100);
				f(a - a3 * 10 + i * 10);
				f(a - a4 + i);
			}
		}
		cout << list[b] << endl;
	}
	return 0;
}

运行的结果是

4
1033 8179
1873
1373 8017
55
1033 1033
0

发现结果非常大。

然后我把函数改成了

void f(int t)
{
     if (is_prime(t) && t != a)
    {
          q.push(t);
          if(list[t]<0)list[t] = list[a] + 1;
     }
}

运行结果是

4
1033 8179
6
1373 8017
4
1033 1033
0

然后再仔细看这个函数,发现还是不对,又改成了

void f(int t)
{
	if (is_prime(t) && t != a && list[t] < 0)
	{
		q.push(t);
		list[t] = list[a] + 1;
	}
}

在进队之前就判断有没有被访问过。

其实很明显就应该这样,但是以前我对队列不熟,对广度优先也不熟,很多问题都是用深度优先的回溯做的。

运行结果是

4
1033 8179
6
1373 8017
8
1033 1033
0

只有中间的那一组是错的,单独运行

2
1373 8017
7

结果又是对的。

所以我认为是队列没有清空的原因。

加了一句 while(q.size())q.pop(); 之后

代码变成:

#include<iostream>
#include<queue>
using namespace std;
 
int n, a, b;
int list[10000];
queue<int>q;
 
bool is_prime(int n)	//判断是不是素数
{
	if (n % 2 == 0)return false;
	for (int i = 3; i*i <= n; i += 2)if (n%i == 0)return false;
	return true;
}
 
void f(int t)			//这个函数是用来进队的,写这个函数只是为了节约重复代码
{
	if (is_prime(t) && t != a && list[t] < 0)
	{
		q.push(t);
		list[t] = list[a] + 1;
	}
}
 
int main()
{	
	int a1, a2, a3, a4;	
	cin >> n;	
	while (n--)
	{
		cin >> a >> b;
		while(q.size())q.pop();		//清空队列
		q.push(a);
		int sum = 0;
		memset(list, -1, sizeof(list));//初始化list
		list[a] = 0;
		while (!q.empty()&&list[b]<0)
		{
			a = q.front();		//a=(a1a2a3a4)10进制
			q.pop();
			a4 = a % 10;
			a3 = (a / 10) % 10;
			a2 = (a / 100) % 10;
			a1 = a / 1000;
			int t;
			for (int i = 1; i < 10; i++)f(a - a1 * 1000 + i * 1000);
			for (int i = 0; i < 10; i++)
			{
				f(a - a2 * 100 + i * 100);
				f(a - a3 * 10 + i * 10);
				f(a - a4 + i);
			}			
		}
		cout << list[b] << endl;
	}
	return 0;
}

然后运行结果对了,一提交,果然是对的。

3,显式图

力扣 1466. 重新规划路线

n 座城市,从 0 到 n-1 编号,其间共有 n-1 条路线。因此,要想在两座不同城市之间旅行只有唯一一条路线可供选择(路线网形成一颗树)。去年,交通运输部决定重新规划路线,以改变交通拥堵的状况。

路线用 connections 表示,其中 connections[i] = [a, b] 表示从城市 a 到 b 的一条有向路线。

今年,城市 0 将会举办一场大型比赛,很多游客都想前往城市 0 。

请你帮助重新规划路线方向,使每个城市都可以访问城市 0 。返回需要变更方向的最小路线数。

题目数据 保证 每个城市在重新规划路线方向后都能到达城市 0 。

示例 1:

输入:n = 6, connections = [[0,1],[1,3],[2,3],[4,0],[4,5]]
输出:3
解释:更改以红色显示的路线的方向,使每个城市都可以到达城市 0 。
示例 2:

输入:n = 5, connections = [[1,0],[1,2],[3,2],[3,4]]
输出:2
解释:更改以红色显示的路线的方向,使每个城市都可以到达城市 0 。
示例 3:

输入:n = 3, connections = [[1,0],[2,0]]
输出:0
 

提示:

2 <= n <= 5 * 10^4
connections.length == n-1
connections[i].length == 2
0 <= connections[i][0], connections[i][1] <= n-1
connections[i][0] != connections[i][1]

思路:

本题一共2步,第一步BFS计算deep,第二步遍历路径统计答案。

无论是树还是图,逻辑是一样的,从城市0开始BFS,获取所有节点距离城市0的最短距离deep。

这题因为是树,保证单连通无圈,那么每条路径连接的2个点的deep一定是相隔为1,正负1分别对应正确的和反向的路径,遍历所有的路径即可得到答案。

class Solution {
public:
    int minReorder(int n, vector<vector<int>>& con) {
        vector<int>v[50001];
        for(int i=0;i<con.size();i++)
        {
            v[con[i][0]].push_back(con[i][1]);
            v[con[i][1]].push_back(con[i][0]);
        }
        int deep[50001];
        memset(deep,0,sizeof(int)*n);
        deep[0]=1;
        queue<int>q;
        q.push(0);
        while(!q.empty())
        {
            int k=q.front();
            q.pop();
            for(int i=0;i<v[k].size();i++)
            {
                if(deep[v[k][i]])continue;
                deep[v[k][i]]=deep[k]+1;
                q.push(v[k][i]);
            }
        }
        int ans=0;
        for(int i=0;i<con.size();i++)
        {
            if(deep[con[i][0]]<deep[con[i][1]])ans++;
        }
        return ans;
    }
};

力扣 1042. 不邻接植花

有 n 个花园,按从 1 到 n 标记。另有数组 paths ,其中 paths[i] = [xi, yi] 描述了花园 xi 到花园 yi 的双向路径。在每个花园中,你打算种下四种花之一。

另外,所有花园 最多 有 3 条路径可以进入或离开.

你需要为每个花园选择一种花,使得通过路径相连的任何两个花园中的花的种类互不相同。

以数组形式返回 任一 可行的方案作为答案 answer,其中 answer[i] 为在第 (i+1) 个花园中种植的花的种类。花的种类用  1、2、3、4 表示。保证存在答案。

示例 1:

输入:n = 3, paths = [[1,2],[2,3],[3,1]]
输出:[1,2,3]
解释:
花园 1 和 2 花的种类不同。
花园 2 和 3 花的种类不同。
花园 3 和 1 花的种类不同。
因此,[1,2,3] 是一个满足题意的答案。其他满足题意的答案有 [1,2,4]、[1,4,2] 和 [3,2,1]
示例 2:

输入:n = 4, paths = [[1,2],[3,4]]
输出:[1,2,1,2]
示例 3:

输入:n = 4, paths = [[1,2],[2,3],[3,4],[4,1],[1,3],[2,4]]
输出:[1,2,3,4]
 

提示:

1 <= n <= 104
0 <= paths.length <= 2 * 104
paths[i].length == 2
1 <= xi, yi <= n
xi != yi
每个花园 最多 有 3 条路径可以进入或离开

//输入无向边集{{1,2}{1,3}{2,3}},输出邻接表{1:{2,3},2:{1,3},3:{1,2}}
map<int, vector<int>> undirectedEdgeToAdjaList(vector<vector<int>>& v)
{
	map<int, vector<int>> ans;
	for (auto& vi : v) {
		ans[vi[0]].push_back(vi[1]);
		ans[vi[1]].push_back(vi[0]);
	}
	return ans;
}

class Solution {
public:
	vector<int> gardenNoAdj(int n, vector<vector<int>>& paths) {
		map<int, vector<int>> m = undirectedEdgeToAdjaList(paths);
		vector<int>x(n + 1);
		queue<int>q;
		map<int, int>visit;
		for (int i = 1; i <= n; i++)
		{
			if (x[i])continue;
			while (!q.empty())q.pop();
			q.push(i);
			visit[i] = 1;
			while (!q.empty()) {
				int k = q.front();
				q.pop();
				map<int, int>mv;
				for (auto ai : m[k]) {
					if (x[ai] == 0 && visit[ai] == 0)q.push(ai), visit[ai] = 1;
					else mv[x[ai]] = 1;
				}
				x[k] = 1;
				while (mv[x[k]])x[k]++;
			}
		}
		x.erase(x.begin());
		return x;
	}
};

4,隐式图

HDU 2102 A计划

题目:

Description

可怜的公主在一次次被魔王掳走一次次被骑士们救回来之后,而今,不幸的她再一次面临生命的考验。魔王已经发出消息说将在T时刻吃掉公主,因为他听信谣言说吃公主的肉也能长生不老。年迈的国王正是心急如焚,告招天下勇士来拯救公主。不过公主早已习以为常,她深信智勇的骑士LJ肯定能将她救出。 
现据密探所报,公主被关在一个两层的迷宫里,迷宫的入口是S(0,0,0),公主的位置用P表示,时空传输机用#表示,墙用*表示,平地用.表示。骑士们一进入时空传输机就会被转到另一层的相对位置,但如果被转到的位置是墙的话,那骑士们就会被撞死。骑士们在一层中只能前后左右移动,每移动一格花1时刻。层间的移动只能通过时空传输机,且不需要任何时间。

Input

输入的第一行C表示共有C个测试数据,每个测试数据的前一行有三个整数N,M,T。 N,M迷宫的大小N*M(1 <= N,M <=10)。T如上所意。接下去的前N*M表示迷宫的第一层的布置情况,后N*M表示迷宫第二层的布置情况。

Output

如果骑士们能够在T时刻能找到公主就输出“YES”,否则输出“NO”。

Sample Input

1
5 5 14
S*#*.
.#...
.....
****.
...#.
 
..*.P
#.*..
***..
...*.
*.#..

Sample Output

YES

这个题目。。。吐血啊。

找了好久的错误都找不到,比赛结束了之后,时间就充足了,然后就开始写一些测试用例看能不能找到算错的情况,那样就可以调试。

最后一个关键测试是

2 3 3
S..
..#
...
..P

输出的结果居然是NO

然后就调试了几分钟就发现整个代码只有1个字符是错的,改了之后就AC了

就是矩阵的维度,有个地方把m写成n了,吐血。。。

思路很简单,我就不说什么了,直接上代码。

代码:

#include<iostream>
#include<queue>
using namespace std;
 
queue<int>q;
int list[23][11];
int n, m, t;
int l1[4] = { 0, 0, 1, -1 };
int l2[4] = { 1, -1, 0, 0 };
 
int main()
{
	int cas;
	cin >> cas;
	char c;
	int px, py;
	for (int i = 1; i <= cas;i++)
	{
		cin >> n >> m >> t;
		while (!q.empty())q.pop();
		for (int i = 0; i < n * 2; i++)
		{
			for (int j = 0; j < m; j++)
			{
				cin >> c;
				if (c == '.')list[i][j] = -1;
				else if (c == '#')list[i][j] = -2;
				else if (c == '*')list[i][j] = -3;
				else
				{
					px = i;
					py = j;
					list[i][j] = -1;
				}
			}
		}
		list[0][0] = 0;
		q.push(0);
		while (!q.empty() && list[px][py] < 0)
		{
			int x = q.front() / 11;
			int y = q.front() % 11;
			q.pop();
			if (list[x][y]>t)break;
			for (int i = 0; i < 4; i++)
			{
				if (x + l1[i] < 0 || y + l2[i] < 0 || x + l1[i] >= n * 2 || y + l2[i] >= m || x < n && x + l1[i] >= n || x >= n && x + l1[i] < n)continue;
				if (list[x + l1[i]][y + l2[i]] == -3 || list[x + l1[i]][y + l2[i]] == -2 && list[(x + l1[i] + n) % (n * 2)][y + l2[i]] == -3)continue;
				if (list[x + l1[i]][y + l2[i]] == -1)
				{
					list[x + l1[i]][y + l2[i]] = list[x][y] + 1;
					q.push((x + l1[i]) * 11 + y + l2[i]);
				}
				else if (list[x + l1[i]][y + l2[i]] == -2 && list[(x + l1[i] + n) % (n * 2)][y + l2[i]] ==-1)
				{
					list[(x + l1[i] + n) % (n * 2)][y + l2[i]] = list[x][y] + 1;
					q.push(((x + l1[i] + n) % (n * 2)) * 11 + y + l2[i]);
				}
			}
		}
		if (list[px][py]<0 || list[px][py]>t)cout << "NO" << endl;
		else cout << "YES" << endl;
	}
	return 0;
}
<iostream>
#include<queue>
using namespace std;

queue<int>q;
int list[23][11];
int n, m, t;
int l1[4] = { 0, 0, 1, -1 };
int l2[4] = { 1, -1, 0, 0 };

int main()
{
	int cas;
	cin >> cas;
	char c;
	int px, py;
	for (int i = 1; i <= cas;i++)
	{
		cin >> n >> m >> t;
		while (!q.empty())q.pop();
		for (int i = 0; i < n * 2; i++)
		{
			for (int j = 0; j < m; j++)
			{
				cin >> c;
				if (c == '.')list[i][j] = -1;
				else if (c == '#')list[i][j] = -2;
				else if (c == '*')list[i][j] = -3;
				else
				{
					px = i;
					py = j;
					list[i][j] = -1;
				}
			}
		}
		list[0][0] = 0;
		q.push(0);
		while (!q.empty() && list[px][py] < 0)
		{
			int x = q.front() / 11;
			int y = q.front() % 11;
			q.pop();
			if (list[x][y]>t)break;
			for (int i = 0; i < 4; i++)
			{
				if (x + l1[i] < 0 || y + l2[i] < 0 || x + l1[i] >= n * 2 || y + l2[i] >= m || x < n && x + l1[i] >= n || x >= n && x + l1[i] < n)continue;
				if (list[x + l1[i]][y + l2[i]] == -3 || list[x + l1[i]][y + l2[i]] == -2 && list[(x + l1[i] + n) % (n * 2)][y + l2[i]] == -3)continue;
				if (list[x + l1[i]][y + l2[i]] == -1)
				{
					list[x + l1[i]][y + l2[i]] = list[x][y] + 1;
					q.push((x + l1[i]) * 11 + y + l2[i]);
				}
				else if (list[x + l1[i]][y + l2[i]] == -2 && list[(x + l1[i] + n) % (n * 2)][y + l2[i]] ==-1)
				{
					list[(x + l1[i] + n) % (n * 2)][y + l2[i]] = list[x][y] + 1;
					q.push(((x + l1[i] + n) % (n * 2)) * 11 + y + l2[i]);
				}
			}
		}
		if (list[px][py]<0 || list[px][py]>t)cout << "NO" << endl;
		else cout << "YES" << endl;
	}
	return 0;
}

虽然是广度优先搜索,不过矩阵的维度很小,所以0ms就运行完了。

POJ 1915 Knight Moves

Description

Background
Mr Somurolov, fabulous chess-gamer indeed, asserts that no one else but him can move knights from one position to another so fast. Can you beat him? 
The Problem
Your task is to write a program to calculate the minimum number of moves needed for a knight to reach one point from another, so that you have the chance to be faster than Somurolov. 
For people not familiar with chess, the possible knight moves are shown in Figure 1. 

Input

The input begins with the number n of scenarios on a single line by itself. 
Next follow n scenarios. Each scenario consists of three lines containing integer numbers. The first line specifies the length l of a side of the chess board (4 <= l <= 300). The entire board has size l * l. The second and third line contain pair of integers {0, ..., l-1}*{0, ..., l-1} specifying the starting and ending position of the knight on the board. The integers are separated by a single blank. You can assume that the positions are valid positions on the chess board of that scenario.

Output

For each scenario of the input you have to calculate the minimal amount of knight moves which are necessary to move from the starting point to the ending point. If starting point and ending point are equal,distance is zero. The distance must be written on a single line.

Sample Input

3
8
0 0
7 0
100
0 0
30 50
10
1 1
1 1

Sample Output

5
28
0

这个题目,我的运行时间比他们都短,我只有47ms。

维度l不超过300可能还不明显,如果维度再大几倍可能就非常明显了。

我的思路是基于我自己对马的一些研究的。

整个求解分为2大部分,第一部分是化简,第二部分是广度优先搜索。

第一部分的核心代码:

cin >> n >> a >> b >> c >> d;
int x = (a > c) ? a - c : c - a;
int y = (b > d) ? b - d : d - b;
int sum = 0;
while (x > 5 || y > 5)
{
	if (x > y)
	{
		x -= 2;
		if (y)y--;
		else y++;
	}
	else
	{
		y -= 2;
		if (x)x--;
		else x++;
	}
	sum++;
}

x就是横坐标a和c的差,y就是纵坐标b和d的差。

然后直接开始走马,直到x和y都不超过5(保持x和y必须非负),每走一步sum都加1。

到了这个时候,广度优先搜索可以说是常数级别的时间代价了。

甚至可以用数组保存结果,直接输出。

当然了,考虑到边界的问题,这样做非常麻烦。

所以第二部分还是用了广度优先搜索,因为x和y都不超过5,广度优先搜索非常快。

至于我的思路的正确性,首先给出我思考的时候画的2个图。

2个图都是从0出发,到每个格子的最短步数。

这个图是在6*6的棋盘上面的结果,即0就是在角落。

这个图是在没有边界的棋盘上面的结果,没有角落,没有边界限制。

这2个图仅有的区别是3个格子,我已经标注出来了。

也就是说,对于这样的L型中的格子来说,对于任意维度的棋盘和对于任意的边界位置来说,结果都是一样的。

如果棋盘足够大,而且0在中间的话,4个这样的L型其实是构成了一个保护圈的。

请允许我自定义它为“数据一致性保护圈”。

如果0在边界的话,2个L型和1条边界照样构成保护圈。

如果0在角落的话,1个L型和2条边界也构成保护圈。

那么,保护圈到底有什么用呢?

很明显,要从保护圈外面到0那个点,就必须先到保护圈中的某个点,所以总步数是2个部分分开求的。

而且,并不需要遍历保护圈,求总步数的最小值,直接根据2个点的位置就可以求出来应该选保护圈中的哪个点作为中转点。

请注意,中转点的选择很容易,而且是绝对正确的,但是从中转点到0那个点的最短步数是不能直接用上面的数的。

比如对于3*30的棋盘,上面的图片中的具体数值是没有意义的。

但是仍然可以轻松选出中转点,然后从中转点到0那个点的距离用广度优先搜索求的话非常非常快。

代码:

#include<iostream>
#include<queue>
using namespace std;

bool ok(int c, int d, int n, int** list)
{
	if (c < 0 || c >= n)return false;
	if (d < 0 || d >= n)return false;
	if (list[c][d] < 0)return true;
	return false;
}

int list1[8] = { 1, 1, 2, 2, -1, -1, -2, -2 };
int list2[8] = { 2, -2, 1, -1, 2, -2, 1, -1 };

int bfs(int a, int b, int x, int y, int n)
{
	int** list = new int* [n];
	for (int i = 0; i < n; i++)
	{
		list[i] = new int[n];
		for (int j = 0; j < n; j++)list[i][j] = -1;
	}
	list[a][b] = 0;
	queue <int>q;
	q.push(a * n + b);
	int c, d;
	while (list[x][y] < 0)
	{
		c = q.front() / n;
		d = q.front() % n;
		q.pop();
		for (int i = 0; i < 8; i++)
			if (ok(c + list1[i], d + list2[i], n, list))
			{
				q.push((c + list1[i]) * n + d + list2[i]);
				list[c + list1[i]][d + list2[i]] = list[c][d] + 1;
			}
	}
	return list[x][y];
}
int func(int n,int a,int b,int c,int d)
{
	int x = (a > c) ? a - c : c - a;
	int y = (b > d) ? b - d : d - b;
	int sum = 0;
	while (x > 5 || y > 5)
	{
		if (x > y)
		{
			x -= 2;
			if (y)y--;
			else y++;
		}
		else
		{
			y -= 2;
			if (x)x--;
			else x++;
		}
		sum++;
	}
	int xx = a, yy = b;
	if (xx >= x)xx -= x;
	else xx += x;
	if (yy >= y)yy -= y;
	else yy += y;
	sum += bfs(a, b, xx, yy, n);
	return sum;
}
int main()
{
	int t;
	cin >> t;
	int n;
	int a, b, c, d;
	while (t--)
	{
		cin >> n >> a >> b >> c >> d;
		cout << func(n,a,b,c,d) << endl;
	}
	return 0;
}

力扣 1197. 进击的骑士

一个坐标可以从 -infinity 延伸到 +infinity 的 无限大的 棋盘上,你的 骑士 驻扎在坐标为 [0, 0] 的方格里。

骑士的走法和中国象棋中的马相似,走 “日” 字:即先向左(或右)走 1 格,再向上(或下)走 2 格;或先向左(或右)走 2 格,再向上(或下)走 1 格。

每次移动,他都可以按图示八个方向之一前进。

返回 骑士前去征服坐标为 [x, y] 的部落所需的最小移动次数 。本题确保答案是一定存在的。

示例 1:

输入:x = 2, y = 1
输出:1
解释:[0, 0] → [2, 1]

示例 2:

输入:x = 5, y = 5
输出:4
解释:[0, 0] → [2, 1] → [4, 2] → [3, 4] → [5, 5]

提示:

  • -300 <= x, y <= 300
  • 0 <= |x| + |y| <= 300

这题是上一题的简化版,直接复用代码。

class Solution {
public:
    int minKnightMoves(int x, int y) {
					x=abs(x),y=abs(y);
					if(x==1&&y==1)return 2;
					if(x==0&&y==3)return 3;
					if(x==3&&y==0)return 3;
          return func(300,0,0,x,y);
    }
};

CSU 1224 ACM小组的古怪象棋

题目:

Description

ACM小组的Samsara和Staginner对中国象棋特别感兴趣,尤其对马(可能是因为这个棋子的走法比较多吧)的使用进行深入研究。今天他们又在 构思一个古怪的棋局:假如Samsara只有一个马了,而Staginner又只剩下一个将,两个棋子都在棋盘的一边,马不能出这一半棋盘的范围,另外这 一半棋盘的大小很奇特(n行m列)。Samsara想知道他的马最少需要跳几次才能吃掉Staginner的将(我们假定其不会移动)。当然这个光荣的任 务就落在了会编程的你的身上了。

Input

每组数据一行,分别为六个用空格分隔开的正整数n,m,x1,y1,x2,y2分别代表棋盘的大小n,m,以及将的坐标和马的坐标。(1<=x1,x2<=n<=20,1<=y1,y2<=m<=20,将和马的坐标不相同)

Output

输出对应也有若干行,请输出最少的移动步数,如果不能吃掉将则输出“-1”(不包括引号)。

Sample Input

8 8 5 1 4 5

Sample Output

3

这个题目和POJ 1915 Knight Moves不同的地方只有3个,所以我直接修改那个题目的代码(完全我自己写的),就得到了这个题目的代码。

第一,不是正方形,只需要修改矩阵的维度即可,不涉及一些逻辑判断什么的。

对了,哈希也需要略改,改写(x,y)对应x*21+y

第二,n和m可以很小,所以可能无解,所以bfs中的循环应该修改退出条件,

变成while (q.size()&&list[x][y]<0)

第三,格子的编号不是从0到n-1而是从1到n,所以我为了偷懒,直接在主函数把a、b、c、d都自减1了。

当然,还有一些细节的改动,比如函数的常数多了一个啊,比如ok函数略改啊,又比如main函数的最后改成

int temp = bfs(a, b, xx, yy, n, m);
if (temp >= 0)cout << sum + temp << endl;
else cout << -1 << endl;

代码:

#include<iostream>
#include<queue>
using namespace std;
 
bool ok(int c, int d, int n,int m, int**list)
{
	if (c < 0 || c >= n)return false;
	if (d < 0 || d >= m)return false;
	if (list[c][d] < 0)return true;
	return false;
}
 
int list1[8] = { 1, 1, 2, 2, -1, -1, -2, -2 };
int list2[8] = { 2, -2, 1, -1, 2, -2, 1, -1 };
 
int bfs(int a, int b, int x, int y, int n,int m)
{
	int **list = new int*[n];
	for (int i = 0; i < n; i++)
	{
		list[i] = new int[m];
		for (int j = 0; j < m; j++)list[i][j] = -1;
	}
	list[a][b] = 0;
	queue <int>q;
	q.push(a*21 + b);
	int c, d;
	while (q.size()&&list[x][y]<0)
	{
		c = q.front() / 21;
		d = q.front() % 21;
		q.pop();
		for (int i = 0; i < 8; i++)
		if (ok(c + list1[i], d + list2[i], n, m,list))
		{
			q.push((c + list1[i])* 21 + d + list2[i]);
			list[c + list1[i]][d + list2[i]] = list[c][d] + 1;
		}
	}
	return list[x][y];
}
 
int main()
{
	int n, m;
	int a, b, c, d;
	while (cin >> n >> m >> a >> b >> c >> d)
	{
		a--;
		b--;
		c--;
		d--;
		int x = (a > c) ? a - c : c - a;
		int y = (b > d) ? b - d : d - b;
		int sum = 0;
		while (x > 5 || y > 5)
		{
			if (x > y)
			{
				x -= 2;
				if (y)y--;
				else y++;
			}
			else
			{
				y -= 2;
				if (x)x--;
				else x++;
			}
			sum++;
		}
		int xx = a, yy = b;
		if (xx >= x)xx -= x;
		else xx += x;
		if (yy >= y)yy -= y;
		else yy += y;
		int temp = bfs(a, b, xx, yy, n, m);
		if (temp >= 0)cout << sum + temp << endl;
		else cout << -1 << endl;		
	}
	return 0;
}

CSU 1046 追杀

题目:

Description

在一个8行9列的国际象棋棋盘上,有一名骑士在追杀对方的国王。该骑士每秒跨越一个2*3的区域,如下图所示。

而对方的国王慌忙落逃,他先沿着右下斜线方向一直跑,遇到边界以后会沿着光线反射方向继续跑(遇到死角则原路返回),他每秒只跑一格。

给出骑士和国王的初始位置,求最快在多少秒的时候骑士能追杀掉对方的国王。骑士和国王每一秒都必须要有行动,而不能原地等待。

Input

有多组测试数据。对于每组测试数据,输入只有一行:nx,ny,kx,ky,前2个表示骑士的初始坐标,后2个表示国王的初始坐标,以左上角的格子为(0,0),向右为x轴正方向,向下为y轴正方向。(0<=nx,kx<=8,0<=ny,ky<=7)

Output

    对于每组测试数据,仅输出一个整数,即骑士追杀到国王的最快时刻。初始位置的时刻为0。追杀到的时刻是指骑士和国王处在同一格的时刻。

Sample Input

0 7 0 0

Sample Output

3

思路:直接枚举时间times,直接算出times之后国王的位置,

再用广度优先搜索算出骑士能不能在times之后刚好到达这个位置。

代码:

#include<iostream>
#include<queue>
using namespace std;
 
int nx, ny, kx, ky;
int mx[8] = { 1,2,2,1,-1,-2,-2,-1 };
int my[8] = { 2,1,-1,-2,-2,-1,1,2 };
 
bool f(int times)
{
	queue<pair<int, int>>q;
	while (q.size())q.pop();
	q.push(make_pair(nx * 10 + ny,0));
	int x, y, t;
	while (q.size())
	{
		x = q.front().first / 10;
		y = q.front().first % 10;
		t = q.front().second;
		q.pop();
		if (t == times)
		{
			if (x == kx && y == ky)return true;			
			continue;
		}
		for (int i = 0; i < 8; i++)
		{
			if (x + mx[i] > 8 || x + mx[i] < 0)continue;
			if (y + my[i] > 7 || y + my[i] < 0)continue;
			q.push(make_pair((x + mx[i]) * 10 + y + my[i], t + 1));
		}
	}
	return false;
}
 
int main()
{	
	while (cin >> nx >> ny >> kx >> ky)
	{
		if (nx == kx && ny == ky)
		{
			cout << 0 << endl;
			continue;
		}
		int dx = 1, dy = 1;
		for (int i = 1;; i++)
		{
			if (kx == 8)dx = -1;
			if (kx == 0)dx = 1;
			if (ky == 7)dy = -1;
			if (ky == 0)dy = 1;
			kx += dx, ky += dy;
			if (f(i))
			{
				cout << i << endl;
				break;
			}
		}
	}
	return 0;
}

HDU 1241 Oil Deposits(连通块的数量)

题目:

Description

GeoSurvComp地质调查公司负责探测地下石油储藏。 GeoSurvComp现在在一块矩形区域探测石油,并把这个大区域分成了很多小块。他们通过专业设备,来分析每个小块中是否蕴藏石油。如果这些蕴藏石油的小方格相邻,那么他们被认为是同一油藏的一部分。在这块矩形区域,可能有很多油藏。你的任务是确定有多少不同的油藏。

Input

输入可能有多个矩形区域(即可能有多组测试)。每个矩形区域的起始行包含m和n,表示行和列的数量,1<=n,m<=100,如果m =0表示输入的结束,接下来是n行,每行m个字符。每个字符对应一个小方格,并且要么是'*',代表没有油,要么是'@',表示有油。

Output

对于每一个矩形区域,输出油藏的数量。两个小方格是相邻的,当且仅当他们水平或者垂直或者对角线相邻(即8个方向)。

Sample Input

1 1

*

3 5

*@*@*

**@**

*@*@*

1 8

@@****@*

5 5 

****@

*@@*@

*@**@

@@@*@

@@**@

0 0 

Sample Output

0

1

2

2

这个题目其实一点都不难,但是我找不到问题,不知道为什么超内存。

最后我把所有变量和数组以及队列都放在main函数外面,一眼就可以看出来,占用的内存很小。

而且队列也不需要清空,因为

while (!q.empty())
{
	a = q.front() / 101;
	b = q.front() % 101;
	list[a][b] = 0;
	q.pop();
	for (int i = 0; i < 8; i++)
		if (ok(a + list1[i], b + list2[i], row, col))
			q.push((a + list1[i]) * 101 + b + list2[i]);
}

这个while循环的退出条件是队列q为空。

我自己测试了题目给的几个例子,可以算出来,应该不是死循环的问题。

最后终于想到了问题在哪,原来我写的是每个格子在进入队列的时候没有标记为已经访问,出队列的时候才标记为已访问。

因为一个格子有8个邻居,所以这样会造成大量的重复进入队列,应该是指数级别的。

正确完整代码:

#include<iostream>
#include<queue>
using namespace std;
 
int list1[8] = { 1, 1, 1, 0, 0, -1, -1, -1 };
int list2[8] = { 1, -1, 0, 1, -1, 1, -1, 0 };
int list[101][101];
int row, col;
char c;
queue<int>q;
int sum, x, a, b, f;
 
bool ok(int a, int b, int row, int col)
{
	if (a < 0 || a >= row)return false;
	if (b < 0 || b >= col)return false;
	if (list[a][b])return true;
	return false;
}
 
int found(int x, int row, int col)
{
	for (int i = x; i < row; i++)for (int j = 0; j < col; j++)
		if (list[i][j])return i * 101 + j;
	return -1;
}
 
int main()
{
 
	while (cin >> row >> col)
	{
		if (row == 0 || col == 0)break;
		for (int i = 0; i < row; i++)
		{
			for (int j = 0; j < col; j++)
			{
				cin >> c;
				if (c == '*')list[i][j] = 0;
				else list[i][j] = 1;
			}
		}
		sum = 0;
		x = 0;
		while (1)
		{
			f = found(x, row, col);
			if (f < 0)break;
			x = f / 101;
			q.push(f);
			list[x][f % 101] = 0;
			while (!q.empty())
			{
				a = q.front() / 101;
				b = q.front() % 101;
				q.pop();
				for (int i = 0; i < 8; i++)
					if (ok(a + list1[i], b + list2[i], row, col))
					{
					q.push((a + list1[i]) * 101 + b + list2[i]);
					list[a + list1[i]][b + list2[i]] = 0;
					}
			}
			sum++;
		}
		cout << sum << endl;
	}
	return 0;
}

HDU 1312 Numerically Speaking

题目:

Description

There is a rectangular room, covered with square tiles. Each tile is colored either red or black. A man is standing on a black tile. From a tile, he can move to one of four adjacent tiles. But he can't move on red tiles, he can move only on black tiles. 

Write a program to count the number of black tiles which he can reach by repeating the moves described above. 

Input

The input consists of multiple data sets. A data set starts with a line containing two positive integers W and H; W and H are the numbers of tiles in the x- and y- directions, respectively. W and H are not more than 20. 

There are H more lines in the data set, each of which includes W characters. Each character represents the color of a tile as follows. 

'.' - a black tile 
'#' - a red tile 
'@' - a man on a black tile(appears exactly once in a data set) 

Output

For each data set, your program should output a line which contains the number of tiles he can reach from the initial tile (including itself). 

Sample Input

6 9
....#.
.....#
......
......
......
......
......
#@...#
.#..#.
11 9
.#.........
.#.#######.
.#.#.....#.
.#.#.###.#.
.#.#..@#.#.
.#.#####.#.
.#.......#.
.#########.
...........
11 6
..#..#..#..
..#..#..#..
..#..#..###
..#..#..#@.
..#..#..#..
..#..#..#..
7 7
..#.#..
..#.#..
###.###
...@...
###.###
..#.#..
..#.#..
0 0 

Sample Output

45
59
6
13 

这个题目用广度优先搜索。

为了方便,队列里面存储的是横坐标*22+纵坐标,这是一种简单的哈希,简单之处在于它明显没有冲突,而且还原成坐标数对超简单。

代码:

#include<iostream>
#include<queue>
using namespace std;
 
bool ok(int a, int b, int row,int col, int**list)
{
	if (a < 0 || a >= row)return false;
	if (b < 0 || b >= col)return false;
	if (list[a][b])return true;
	return false;
}
 
int main()
{
	int row, col;
	char c;
	while (cin>>col>>row)
	{
		if (row == 0)break;
		int **list = new int*[row];
		int x, y;
		for (int i = 0; i < row; i++)
		{
			list[i] = new int[col];
			for (int j = 0; j < col; j++)
			{
				cin >> c;
				if (c == '.')list[i][j] = 1;
				else if (c == '#')list[i][j] = 0;
				else
				{
					list[i][j] = 0;
					x = i;
					y = j;
				}
			}
		}
		int sum = 1;
		queue<int>q;
		q.push(x * 22 + y);
		while (!q.empty())
		{
			int a = q.front() / 22;
			int b = q.front() % 22;
			q.pop();
			if (ok(a + 1, b , row, col, list))
			{
				q.push((a + 1) * 22 + b);;
				list[a + 1][b] = 0;
				sum++;
			}
			if (ok(a - 1, b, row, col, list))
			{
				q.push((a - 1) * 22 + b);
				list[a - 1][b] = 0;
				sum++;
			}
			if (ok(a, b + 1, row, col, list))
			{
				q.push(a * 22 + b + 1);
				list[a][b + 1] = 0;
				sum++;
			}
			if (ok(a , b - 1, row, col, list))
			{
				q.push(a * 22 + b - 1);
				list[a][b - 1] = 0;
				sum++;
			}
		}
		cout << sum << endl;
	}
	return 0;
}

ZOJ 1649 Rescue

题目:

Description

Angel was caught by the MOLIGPY! He was put in prison by Moligpy. The prison is described as a N * M (N, M <= 200) matrix. There are WALLs, ROADs, and GUARDs in the prison. 
Angel's friends want to save Angel. Their task is: approach Angel. We assume that "approach Angel" is to get to the position where Angel stays. When there's a guard in the grid, we must kill him (or her?) to move into the grid. We assume that we moving up, down, right, left takes us 1 unit time, and killing a guard takes 1 unit time, too. And we are strong enough to kill all the guards. 
You have to calculate the minimal time to approach Angel. (We can move only UP, DOWN, LEFT and RIGHT, to the neighbor grid within bound, of course.) 

Input

First line contains two integers stand for N and M. 
Then N lines follows, every line has M characters. "." stands for road, "a" stands for Angel, and "r" stands for each of Angel's friend. 
//x是 guard,r不一定只有1个,#是墙
Process to the end of the file. 

Output

For each test case, your program should output a single integer, standing for the minimal time needed. If such a number does no exist, you should output a line containing "Poor ANGEL has to stay in the prison all his life." 

Sample Input

7 8
#.#####.
#.a#..r.
#..#x...
..#..#.#
#...##..
.#......
........

Sample Output

13

这个题目在OJ里面的掉了一些内容,我已经补上了。

广度优先搜索代码:

#include<iostream>
#include<queue>
#include<string.h>
using namespace std;
 
int n, m;
int  list[200][200];
int list2[200][200];		//list2只用来判断是不是敌人x
queue<int>q;
 
void f(int a, int b,int c,int d)
{
	if (a < 0 || a >= n)return;
	if (b < 0 || b >= m)return;
	if (list[a][b] == -1)
	{
		list[a][b] = list[c][d] + 1;
		q.push(a * 201 + b);
	}
	else if (list[a][b] == -3)
	{
		list[a][b] = list[c][d] + 2;
		q.push(a * 201 + b);
	}
	else if (list[a][b] > list[c][d] + 1)//因为没有用优先队列,所以有些格子需要重新进入队列
	{
		list[a][b] = list[c][d] + 1;
		if (list2[a][b])list[a][b]++;
		q.push(a * 201 + b);
	}
}
 
int main()
{	
	int a, b;
	char c;
	int x, y;
	while (cin>>n>>m)
	{
		while (q.size())q.pop();
		memset(list2, 0, sizeof(list2));
		for (int i = 0; i < n; i++)
		{
			for (int j = 0; j < m; j++)
			{
				cin >> c;
				if (c == '.')list[i][j] = -1;
				else if (c == '#')list[i][j] = -2;
				else if (c == 'r')
				{
					list[i][j] = 0;
					q.push(i * 201 + j);
				}
				else if (c == 'x')
				{
					list[i][j] = -3;
					list2[i][j] = 1;
				}
				else
				{
					x = i;
					y = j;
					list[x][y] = -1;
				}
			}
		}
	while (!q.empty())//即使list[x][y]>0也不能结束循环,必须队列为空才能得到list[x][y]的最小值
	{
		a = q.front() / 201;
		b = q.front() % 201;
		q.pop();
		f(a + 1, b, a, b);
		f(a - 1, b, a, b);
		f(a, b + 1, a, b);
		f(a, b - 1, a, b);
	}
		if (list[x][y]>0)cout << list[x][y];
		else cout << "Poor ANGEL has to stay in the prison all his life.";
		cout << endl;
	}
	return 0;
}

力扣 529. 扫雷游戏

扫雷游戏

力扣 778. 水位上升的泳池中游泳

在一个 n x n 的整数矩阵 grid 中,每一个方格的值 grid[i][j] 表示位置 (i, j) 的平台高度。

当开始下雨时,在时间为 t 时,水池中的水位为 t 。你可以从一个平台游向四周相邻的任意一个平台,但是前提是此时水位必须同时淹没这两个平台。假定你可以瞬间移动无限距离,也就是默认在方格内部游动是不耗时的。当然,在你游泳的时候你必须待在坐标方格里面。

你从坐标方格的左上平台 (0,0) 出发。返回 你到达坐标方格的右下平台 (n-1, n-1) 所需的最少时间 。

示例 1:

输入: grid = [[0,2],[1,3]]
输出: 3
解释:
时间为0时,你位于坐标方格的位置为 (0, 0)。
此时你不能游向任意方向,因为四个相邻方向平台的高度都大于当前时间为 0 时的水位。
等时间到达 3 时,你才可以游向平台 (1, 1). 因为此时的水位是 3,坐标方格中的平台没有比水位 3 更高的,所以你可以游向坐标方格中的任意位置
示例 2:

输入: grid = [[0,1,2,3,4],[24,23,22,21,5],[12,13,14,15,16],[11,17,18,19,20],[10,9,8,7,6]]
输出: 16
解释: 最终的路线用加粗进行了标记。
我们必须等到时间为 16,此时才能保证平台 (0, 0) 和 (4, 4) 是连通的
 

提示:

n == grid.length
n == grid[i].length
1 <= n <= 50
0 <= grid[i][j] < n2
grid[i][j] 中每个值 均无重复

题意:

求从最上角到右下角的路径的所有格子的最大值的最小值

class Solution {
public:
	int id(int x, int y)
	{
		return x * col + y;
	}
	vector<int> getNeighbor4(int k)
	{
		vector<int>ans;
		if (k >= col)ans.push_back(k - col);
		if (k < (row - 1) * col)ans.push_back(k + col);
		if (k % col)ans.push_back(k - 1);
		if (k % col < col - 1)ans.push_back(k + 1);
		return ans;
	}
	int swimInWater(const vector<vector<int>>& board) {
		row = board.size();
		col = board[0].size();
		map<int, int>m;
		for (int i = 0; i < row; i++)for (int j = 0; j < col; j++)m[id(i, j)] = INT_MAX;
		m[0] = board[0][0];
		queue<int>q;
		q.push(0);
		while (!q.empty()) {
			int k = q.front();
			q.pop();
			vector<int> v = getNeighbor4(k);
			for (auto vi : v) {
				if (m[vi] > max(board[vi / col][vi%col], m[k])) {
					m[vi] = max(board[vi / col][vi%col], m[k]);
					q.push(vi);
				}
			}
		}
		return m[id(row - 1, col - 1)];
	}
	int row;
	int col;
};

力扣 841. 钥匙和房间

有 n 个房间,房间按从 0 到 n - 1 编号。最初,除 0 号房间外的其余所有房间都被锁住。你的目标是进入所有的房间。然而,你不能在没有获得钥匙的时候进入锁住的房间。

当你进入一个房间,你可能会在里面找到一套不同的钥匙,每把钥匙上都有对应的房间号,即表示钥匙可以打开的房间。你可以拿上所有钥匙去解锁其他房间。

给你一个数组 rooms 其中 rooms[i] 是你进入 i 号房间可以获得的钥匙集合。如果能进入 所有 房间返回 true,否则返回 false。

示例 1:

输入:rooms = [[1],[2],[3],[]]
输出:true
解释:
我们从 0 号房间开始,拿到钥匙 1。
之后我们去 1 号房间,拿到钥匙 2。
然后我们去 2 号房间,拿到钥匙 3。
最后我们去了 3 号房间。
由于我们能够进入每个房间,我们返回 true。
示例 2:

输入:rooms = [[1,3],[3,0,1],[2],[0]]
输出:false
解释:我们不能进入 2 号房间。
 

提示:

n == rooms.length
2 <= n <= 1000
0 <= rooms[i].length <= 1000
1 <= sum(rooms[i].length) <= 3000
0 <= rooms[i][j] < n
所有 rooms[i] 的值 互不相同

class Solution {
public:
	bool canVisitAllRooms(vector<vector<int>>& rooms) {
		queue<int>q;
		q.push(0);
		map<int, int>m;
		while (!q.empty()) {
			int k = q.front();
			q.pop();
			if (m[k])continue;
			m[k] = 1;
			for (auto ri : rooms[k])q.push(ri);
		}
		for (int i = 0; i < rooms.size(); i++)if (m[i] == 0)return false;
		return true;
	}
};

力扣 1631. 最小体力消耗路径(优先队列)

你准备参加一场远足活动。给你一个二维 rows x columns 的地图 heights ,其中 heights[row][col] 表示格子 (row, col) 的高度。一开始你在最左上角的格子 (0, 0) ,且你希望去最右下角的格子 (rows-1, columns-1) (注意下标从 0 开始编号)。你每次可以往 上,下,左,右 四个方向之一移动,你想要找到耗费 体力 最小的一条路径。

一条路径耗费的 体力值 是路径上相邻格子之间 高度差绝对值 的 最大值 决定的。

请你返回从左上角走到右下角的最小 体力消耗值 。

示例 1:

输入:heights = [[1,2,2],[3,8,2],[5,3,5]]
输出:2
解释:路径 [1,3,5,3,5] 连续格子的差值绝对值最大为 2 。
这条路径比路径 [1,2,2,2,5] 更优,因为另一条路径差值最大值为 3 。
示例 2:

输入:heights = [[1,2,3],[3,8,4],[5,3,5]]
输出:1
解释:路径 [1,2,3,4,5] 的相邻格子差值绝对值最大为 1 ,比路径 [1,3,5,3,5] 更优。
示例 3:


输入:heights = [[1,2,1,1,1],[1,2,1,2,1],[1,2,1,2,1],[1,2,1,2,1],[1,1,1,2,1]]
输出:0
解释:上图所示路径不需要消耗任何体力。

提示:

rows == heights.length
columns == heights[i].length
1 <= rows, columns <= 100
1 <= heights[i][j] <= 106

简单代码:

class Solution {
public:
	int id(int x, int y)
	{
		return x * col + y;
	}
	vector<int> getNeighbor4(int k)
	{
		vector<int>ans;
		if (k >= col)ans.push_back(k - col);
		if (k < (row - 1) * col)ans.push_back(k + col);
		if (k % col)ans.push_back(k - 1);
		if (k % col < col - 1)ans.push_back(k + 1);
		return ans;
	}
	int minimumEffortPath(vector<vector<int>>& grid) {
		row = grid.size();
		col = grid[0].size();
		map<int, int>m;
		for (int i = 0; i < grid.size(); i++)for (int j = 0; j < grid[0].size(); j++)m[id(i, j)] = INT_MAX;
		m[0] = 0;
		queue<int>q;
		q.push(0);
		while (!q.empty()) {
			int k = q.front();
			q.pop();
			vector<int> v = getNeighbor4(k);
			for (auto vi : v) {
				if (m[vi] > max(m[k], abs(grid[k / col][k%col] - grid[vi / col][vi%col]))) {
					m[vi] = max(m[k], abs(grid[k / col][k%col] - grid[vi / col][vi%col]));
					q.push(vi);
				}
			}
		}
		return m[id(row - 1, col - 1)];
	}
	int row;
	int col;
};

这个代码的实现难度低,但是效率也低,果然在最后一个性能用例上超时了。

改成优先队列的实现方式:

struct Node
{
	int id;
	int num;
	bool operator<(const Node &b) const
	{
		if (num == b.num)return id < b.id;
		return num > b.num;
	}
};
class Solution {
public:
	int id(int x, int y)
	{
		return x * col + y;
	}
	vector<int> getNeighbor4(int k)
	{
		vector<int>ans;
		if (k >= col)ans.push_back(k - col);
		if (k < (row - 1) * col)ans.push_back(k + col);
		if (k % col)ans.push_back(k - 1);
		if (k % col < col - 1)ans.push_back(k + 1);
		return ans;
	}
	int minimumEffortPath(vector<vector<int>>& grid) {
		row = grid.size();
		col = grid[0].size();
		map<int, int>m;
		for (int i = 0; i < grid.size(); i++)for (int j = 0; j < grid[0].size(); j++)m[id(i, j)] = INT_MAX;
		m[0] = 0;
		priority_queue<Node>q;
		q.push(Node{ 0,0 });
		while (!q.empty()) {
			Node k = q.top();
			q.pop();
			if (k.id == id(row - 1, col - 1))break;
			if (k.num != m[k.id])continue;
			vector<int> v = getNeighbor4(k.id);
			for (auto vi : v) {
				if (m[vi] > max(m[k.id], abs(grid[k.id / col][k.id % col] - grid[vi / col][vi % col]))) {
					m[vi] = max(m[k.id], abs(grid[k.id / col][k.id % col] - grid[vi / col][vi % col]));
					q.push(Node{ vi, m[vi] });
				}
			}
		}
		return m[id(row - 1, col - 1)];
	}
	int row;
	int col;
};

292ms AC

力扣 1971. 寻找图中是否存在路径

有一个具有 n个顶点的 双向 图,其中每个顶点标记从 0 到 n - 1(包含 0 和 n - 1)。图中的边用一个二维整数数组 edges 表示,其中 edges[i] = [ui, vi] 表示顶点 ui 和顶点 vi 之间的双向边。 每个顶点对由 最多一条 边连接,并且没有顶点存在与自身相连的边。

请你确定是否存在从顶点 start 开始,到顶点 end 结束的 有效路径 。

给你数组 edges 和整数 n、start和end,如果从 start 到 end 存在 有效路径 ,则返回 true,否则返回 false 。

示例 1:


输入:n = 3, edges = [[0,1],[1,2],[2,0]], start = 0, end = 2
输出:true
解释:存在由顶点 0 到顶点 2 的路径:
- 0 → 1 → 2 
- 0 → 2
示例 2:


输入:n = 6, edges = [[0,1],[0,2],[3,5],[5,4],[4,3]], start = 0, end = 5
输出:false
解释:不存在由顶点 0 到顶点 5 的路径.

提示:

1 <= n <= 2 * 105
0 <= edges.length <= 2 * 105
edges[i].length == 2
0 <= ui, vi <= n - 1
ui != vi
0 <= start, end <= n - 1
不存在双向边
不存在指向顶点自身的边

//输入无向边集{{1,2}{1,3}{2,3}},输出邻接表{1:{2,3},2:{1,3},3:{1,2}}
map<int, vector<int>> undirectedEdgeToAdjaList(vector<vector<int>>& v)
{
	map<int,vector<int>> ans;
	for (auto &vi : v) {
		ans[vi[0]].push_back(vi[1]);
		ans[vi[1]].push_back(vi[0]);
	}
	return ans;
}
class Solution {
public:
	bool validPath(int n, vector<vector<int>>& edges, int source, int destination) {
		map<int, vector<int>>m = undirectedEdgeToAdjaList(edges);
		queue<int>q;
		q.push(source);
		map<int, int>m2;
		m2[source] = 1;
		while (!q.empty()) {
			int k = q.front();
			q.pop();
			if (k == destination)return true;
			for (auto ei : m[k])if (m2[ei] == 0) {
				q.push(ei);
				m2[ei] = 1;
			}
		}
		return false;
	}
};

力扣 542. 01 矩阵

 给定一个由 0 和 1 组成的矩阵,找出每个元素到最近的 0 的距离。

两个相邻元素间的距离为 1 。

示例 1:

输入:
[[0,0,0],
 [0,1,0],
 [0,0,0]]

输出:
[[0,0,0],
 [0,1,0],
 [0,0,0]]
示例 2:

输入:
[[0,0,0],
 [0,1,0],
 [1,1,1]]

输出:
[[0,0,0],
 [0,1,0],
 [1,2,1]]
 

提示:

给定矩阵的元素个数不超过 10000。
给定矩阵中至少有一个元素是 0。
矩阵中的元素只在四个方向上相邻: 上、下、左、右。

#define NUM(x,y) ((x<0||x>=A.size()||y<0||y>=A[0].size())? -1:A[x][y])
int dx[]={0,0,1,-1},dy[]={1,-1,0,0};

class Solution {
public:
    vector<vector<int>> updateMatrix(vector<vector<int>>& A) {
        vector<vector<int>>ans=A;
        for(int i=0;i<ans.size();i++)for(int j=0;j<ans[0].size();j++)ans[i][j]=-1;
        queue<int>q;
        for(int i=0;i<A.size();i++)for(int j=0;j<A[0].size();j++)if(A[i][j]==0)
        {
            ans[i][j]=0;
            q.push(i*10000+j);
        }
        while(!q.empty())
        {
            int k=q.front();
            q.pop();
            int kx=k/10000,ky=k%10000;
            for(int dire=0;dire<4;dire++)
            {
                int kxx=kx+dx[dire],kyy=ky+dy[dire];
                if(NUM(kxx,kyy)==-1)continue;
                if(ans[kxx][kyy]!=-1)continue;
                q.push(kxx*10000+kyy);
                ans[kxx][kyy]=ans[kx][ky]+1;
            }
        }
        return ans;
    }
};

力扣 1345. 跳跃游戏 IV

给你一个整数数组 arr ,你一开始在数组的第一个元素处(下标为 0)。

每一步,你可以从下标 i 跳到下标 i + 1 、i - 1 或者 j :

  • i + 1 需满足:i + 1 < arr.length
  • i - 1 需满足:i - 1 >= 0
  • j 需满足:arr[i] == arr[j] 且 i != j

请你返回到达数组最后一个元素的下标处所需的 最少操作次数 。

注意:任何时候你都不能跳到数组外面。

示例 1:

输入:arr = [100,-23,-23,404,100,23,23,23,3,404]
输出:3
解释:那你需要跳跃 3 次,下标依次为 0 --> 4 --> 3 --> 9 。下标 9 为数组的最后一个元素的下标。

示例 2:

输入:arr = [7]
输出:0
解释:一开始就在最后一个元素处,所以你不需要跳跃。

示例 3:

输入:arr = [7,6,9,6,9,6,9,7]
输出:1
解释:你可以直接从下标 0 处跳到下标 7 处,也就是数组的最后一个元素处。

提示:

  • 1 <= arr.length <= 5 * 104
  • -108 <= arr[i] <= 108
class Solution {
public:
	int minJumps(vector<int>& arr) {
        if (arr.size() == 1)return 0;
		map<int, vector<int>>id;
		for (int i = 0; i < arr.size(); i++) id[arr[i]].push_back(i);
		queue<int>q, q2;
		q.push(0);
		map<int,int>m;
		m[0] = 1;
		int ans = 0;
		while (true) {
			while (!q.empty()) {
				int x = q.front();
				q.pop();
				for (auto k : id[arr[x]]) {
					if (k == arr.size() - 1)return ans + 1;
					if (k == x || m[k])continue;
					q2.push(k);
					m[k] = 1;
				}
                id[arr[x]].clear();
				if (x+1 == arr.size() - 1)return ans + 1;
				if (m[x + 1] == 0)q2.push(x + 1), m[x + 1] = 1;
				if (x && m[x - 1] == 0)q2.push(x - 1), m[x - 1] = 1;
			}
			q = q2;
			q2 = queue<int>{};
			ans++;
		}
		return 0;
	}
};

二,BFS多起点洪泛

力扣 934. 最短的桥

在给定的二维二进制数组 A 中,存在两座岛。(岛是由四面相连的 1 形成的一个最大组。)

现在,我们可以将 0 变为 1,以使两座岛连接起来,变成一座岛。

返回必须翻转的 0 的最小数目。(可以保证答案至少是 1。)

示例 1:

输入:[[0,1],[1,0]]
输出:1
示例 2:

输入:[[0,1,0],[0,0,0],[0,0,1]]
输出:2
示例 3:

输入:[[1,1,1,1,1],[1,0,0,0,1],[1,0,1,0,1],[1,0,0,0,1],[1,1,1,1,1]]
输出:1
 

提示:

1 <= A.length = A[0].length <= 100
A[i][j] == 0 或 A[i][j] == 1

思路:一次暴力枚举+两次BFS

首先暴力枚举找到随意1个起点

然后BFS找到和他相邻的所有1

最后BFS找到另外一堆1

#define NUM(x,y) ((x<0||x>=A.size()||y<0||y>=A.size())? -1:A[x][y])
int dx[]={0,0,1,-1},dy[]={1,-1,0,0};
class Solution {
public:
    int shortestBridge(vector<vector<int>>& A) {
        int si,sj,len[10000],flag=0;
        queue<int>q1,q2;
        memset(len,-1,sizeof(len));
        for(si=0;si<A.size();si++)for(sj=0;sj<A.size();sj++)if(A[si][sj])goto g1;
g1:
        q1.push(si*100+sj);
        len[si*100+sj]=0;
        while(!q1.empty())
        {
            int k=q1.front();
            q1.pop();
            q2.push(k);
            int kx=k/100,ky=k%100;
            for(int dire=0;dire<4;dire++)
            {
                int kxx=kx+dx[dire],kyy=ky+dy[dire];
                if(NUM(kxx,kyy)==-1||NUM(kxx,kyy)==flag)continue;
                if(len[kxx*100+kyy]!=-1)continue;
                q1.push(kxx*100+kyy);
                len[kxx*100+kyy]=flag?len[k]+1:0;
                if(flag && NUM(kxx,kyy)==1)return len[k]; 
            }
        }
        q1=q2,flag=2;
        goto g1;
    }
};

力扣 1162. 地图分析

你现在手里有一份大小为 n x n 的 网格 grid,上面的每个 单元格 都用 0 和 1 标记好了。其中 0 代表海洋,1 代表陆地。

请你找出一个海洋单元格,这个海洋单元格到离它最近的陆地单元格的距离是最大的,并返回该距离。如果网格上只有陆地或者海洋,请返回 -1。

我们这里说的距离是「曼哈顿距离」( Manhattan Distance):(x0, y0) 和 (x1, y1) 这两个单元格之间的距离是 |x0 - x1| + |y0 - y1| 。

示例 1:

输入:grid = [[1,0,1],[0,0,0],[1,0,1]]
输出:2
解释: 
海洋单元格 (1, 1) 和所有陆地单元格之间的距离都达到最大,最大距离为 2。
示例 2:

输入:grid = [[1,0,0],[0,0,0],[0,0,0]]
输出:4
解释: 
海洋单元格 (2, 2) 和所有陆地单元格之间的距离都达到最大,最大距离为 4。
 

提示:

n == grid.length
n == grid[i].length
1 <= n <= 100
grid[i][j] 不是 0 就是 1

class Solution {
public:
	int id(int x, int y)
	{
		return x * col + y;
	}
	vector<int> getNeighbor4(int k)
	{
		vector<int>ans;
		if (k >= col)ans.push_back(k - col);
		if (k < (row - 1) * col)ans.push_back(k + col);
		if (k % col)ans.push_back(k - 1);
		if (k % col < col - 1)ans.push_back(k + 1);
		return ans;
	}
	int maxDistance(vector<vector<int>>& grid) {
		row = grid.size();
		col = grid[0].size();
		queue<int>q;
		map<int, int>m;
		for (int i = 0; i < row; i++)for (int j = 0; j < col; j++)
			if (grid[i][j] == 1)q.push(id(i, j)), m[id(i, j)] = 1;
		if (q.size() == 0 || q.size() == row * col)return -1;
		int ans = -1;
		while (!q.empty())
		{
			queue<int>q2;
			while (!q.empty())
			{
				int k = q.front();
				q.pop();
				vector<int> v = getNeighbor4(k);
				for (auto vi : v) {
					if (m[vi])continue;
					q2.push(vi), m[vi] = 1;
				}
			}
			q = q2;
			ans++;
		}
		return ans;
	}
	int row;
	int col;
};

其他实战

Infinity Loop

三,反向BFS

反过来以最外圈所有的点作为多起点洪泛,往里面搜索。

力扣 1245. 树的直径

给你这棵「无向树」,请你测算并返回它的「直径」:这棵树上最长简单路径的 边数

我们用一个由所有「边」组成的数组 edges 来表示一棵无向树,其中 edges[i] = [u, v] 表示节点 u 和 v 之间的双向边。

树上的节点都已经用 {0, 1, ..., edges.length} 中的数做了标记,每个节点上的标记都是独一无二的。

示例 1:

输入:edges = [[0,1],[0,2]]
输出:2
解释:
这棵树上最长的路径是 1 - 0 - 2,边数为 2。

示例 2:

输入:edges = [[0,1],[1,2],[2,3],[1,4],[4,5]]
输出:4
解释: 
这棵树上最长的路径是 3 - 2 - 1 - 4 - 5,边数为 4。

提示:

  • 0 <= edges.length < 10^4
  • edges[i][0] != edges[i][1]
  • 0 <= edges[i][j] <= edges.length
  • edges 会形成一棵无向树
class Solution {
public:
    int treeDiameter(vector<vector<int>>& edges) {
        if(edges.empty())return 0;
        map<int,int>m;
        map<int,vector<int>>neibor;
        map<int,int>ns;
        for(auto v:edges){
            m[v[0]]++,m[v[1]]++,
            neibor[v[0]].push_back(v[1]),neibor[v[1]].push_back(v[0]);
            ns[v[0]]++,ns[v[1]]++;
        }
        queue<int>q;
        map<int,int>deep;
        for(auto mi:m)if(mi.second==1)q.push(mi.first),deep[mi.first]=1;
        while(!q.empty()){
            int t = q.front();
            q.pop();
            for(auto x:neibor[t]){
                ns[x]--;
                if(ns[x]==1){
                    deep[x]=deep[t]+1;
                    q.push(x);
                }
            }
        }
        int maxDeep=0,n=0;
        for(auto di:deep){
            if(maxDeep<di.second)maxDeep=di.second,n=1;
            else if(maxDeep==di.second)n++;
        }
        return n>1?maxDeep*2-1:maxDeep*2-2;
    }
};

力扣 1522. N 叉树的直径

给定一棵 N 叉树 的根节点 root ,计算这棵树的直径长度。

N 叉树的直径指的是树中任意两个节点间路径中 最长 路径的长度。这条路径可能经过根节点,也可能不经过根节点。

(N 叉树的输入序列以层序遍历的形式给出,每组子节点用 null 分隔)

示例 1:

输入:root = [1,null,3,2,4,null,5,6]
输出:3
解释:直径如图中红线所示。

示例 2:

输入:root = [1,null,2,null,3,4,null,5,null,6]
输出:4

示例 3:

输入: root = [1,null,2,3,4,5,null,null,6,7,null,8,null,9,10,null,null,11,null,12,null,13,null,null,14]
输出: 7

提示:

  • N 叉树的深度小于或等于 1000 。
  • 节点的总个数在 [0, 10^4] 间。
class Solution {
public:
    int diameter(Node* root) {
        vector<vector<int>> edges;
        dfs(root,edges);
        return treeDiameter(edges);
    }
    void dfs(Node* root,vector<vector<int>>& edges){
        static map<Node*,int>m;
        if(m[root]==0)m[root]=m.size();
        for(auto chird:root->children){
            dfs(chird,edges);
            edges.push_back(vector<int>{m[root],m[chird]});
        }
    }
    int treeDiameter(vector<vector<int>>& edges) {
        if(edges.empty())return 0;
        map<int,int>m;
        map<int,vector<int>>neibor;
        map<int,int>ns;
        for(auto v:edges){
            m[v[0]]++,m[v[1]]++,
            neibor[v[0]].push_back(v[1]),neibor[v[1]].push_back(v[0]);
            ns[v[0]]++,ns[v[1]]++;
        }
        queue<int>q;
        map<int,int>deep;
        for(auto mi:m)if(mi.second==1)q.push(mi.first),deep[mi.first]=1;
        while(!q.empty()){
            int t = q.front();
            q.pop();
            for(auto x:neibor[t]){
                ns[x]--;
                if(ns[x]==1){
                    deep[x]=deep[t]+1;
                    q.push(x);
                }
            }
        }
        int maxDeep=0,n=0;
        for(auto di:deep){
            if(maxDeep<di.second)maxDeep=di.second,n=1;
            else if(maxDeep==di.second)n++;
        }
        return n>1?maxDeep*2-1:maxDeep*2-2;
    }
};

力扣 310. 最小高度树

树是一个无向图,其中任何两个顶点只通过一条路径连接。 换句话说,一个任何没有简单环路的连通图都是一棵树。

给你一棵包含 n 个节点的树,标记为 0 到 n - 1 。给定数字 n 和一个有 n - 1 条无向边的 edges 列表(每一个边都是一对标签),其中 edges[i] = [ai, bi] 表示树中节点 ai 和 bi 之间存在一条无向边。

可选择树中任何一个节点作为根。当选择节点 x 作为根节点时,设结果树的高度为 h 。在所有可能的树中,具有最小高度的树(即,min(h))被称为 最小高度树 。

请你找到所有的 最小高度树 并按 任意顺序 返回它们的根节点标签列表。

树的 高度 是指根节点和叶子节点之间最长向下路径上边的数量。

示例 1:

输入:n = 4, edges = [[1,0],[1,2],[1,3]]
输出:[1]
解释:如图所示,当根是标签为 1 的节点时,树的高度是 1 ,这是唯一的最小高度树。

示例 2:

输入:n = 6, edges = [[3,0],[3,1],[3,2],[3,4],[5,4]]
输出:[3,4]

提示:

  • 1 <= n <= 2 * 104
  • edges.length == n - 1
  • 0 <= ai, bi < n
  • ai != bi
  • 所有 (ai, bi) 互不相同
  • 给定的输入 保证 是一棵树,并且 不会有重复的边

思路:

问题和力扣 1522. N 叉树的直径其实是一样的,所以代码也是一样的,把最后几行统计答案的稍微改一点就可以了。

class Solution {
public:
    vector<int> findMinHeightTrees(int n, vector<vector<int>>& edges) {
        if(edges.empty())return vector<int>{0};
        map<int,int>m;
        map<int,vector<int>>neibor;
        map<int,int>ns;
        for(auto v:edges){
            m[v[0]]++,m[v[1]]++,
            neibor[v[0]].push_back(v[1]),neibor[v[1]].push_back(v[0]);
            ns[v[0]]++,ns[v[1]]++;
        }
        queue<int>q;
        map<int,int>deep;
        for(auto mi:m)if(mi.second==1)q.push(mi.first),deep[mi.first]=1;
        while(!q.empty()){
            int t = q.front();
            q.pop();
            for(auto x:neibor[t]){
                ns[x]--;
                if(ns[x]==1){
                    deep[x]=deep[t]+1;
                    q.push(x);
                }
            }
        }
        vector<int>ans;
        int maxDeep=0;
        for(auto di:deep){
            if(maxDeep<di.second)maxDeep=di.second,ans.clear();
            if(maxDeep==di.second)ans.push_back(di.first);
        }
        return ans;
    }
};

四,双向BFS

vijos 1360 八数码问题

背景

Yours和zero在研究A*启发式算法.拿到一道经典的A*问题,但是他们不会做,请你帮他们.

描述

在3×3的棋盘上,摆有八个棋子,每个棋子上标有1至8的某一数字。棋盘中留有一个空格,空格用0来表示。空格周围的棋子可以移到空格中。要求解的问题是:给出一种初始布局(初始状态)和目标布局(为了使题目简单,设目标状态为123804765),找到一种最少步骤的移动方法,实现从初始布局到目标布局的转变。

格式

输入格式

输入初试状态,一行九个数字,空格用0表示

输出格式

只有一行,该行只有一个数字,表示从初始状态到目标状态需要的最少移动次数(测试数据中无特殊无法到达目标状态数据)

样例1

样例输入1

283104765

样例输出1

4

题目:八数码问题 - Vijos

这个八数码问题很像拼图游戏,只是给的数字顺序变了变。

解法一:

BFS,从输入字符串开始,往目标字符串send="123804765"搜索

代码:

#include<iostream>
#include<map>
#include<vector>
#include<queue>
using namespace std;

vector<int> f(string s)
{
    vector<int>v;
    int k=0;
    for(k=0;k<9;k++)if(s[k]=='0')break; //获取0的位置
    v.push_back(k);
    if(k>=3)v.push_back(k-3);
    if(k<=5)v.push_back(k+3);
    if(k%3)v.push_back(k-1);
    if(k%3!=2)v.push_back(k+1);
    return v;
}

int main()
{
    string s,e="123804765";
    cin>>s;
    map<string,int>m;
    m[s]=1;
    queue<string>q;
    q.push(s);
    while(m[e]==0)
    {
        s=q.front();
        q.pop();
        vector<int>v=f(s);
        int k=v[0];
        string s2;
        for(int i=1;i<v.size();i++){
            s2=s,s2[k]=s2[v[i]],s2[v[i]]='0';
            if(m[s2]==0) {
                m[s2] = m[s]+1;
                q.push(s2);
            }
        }
    }
    cout<<m[e]-1;
    return 0;
}

总耗时506ms

解法二:

双向BFS,从首尾同时往中间搜

代码也很好写,只需要把BFS的代码稍微改一改即可。

代码:

#include<iostream>
#include<map>
#include<vector>
#include<queue>
using namespace std;

vector<int> f(string s)
{
    vector<int>v;
    int k=0;
    for(k=0;k<9;k++)if(s[k]=='0')break; //获取0的位置
    v.push_back(k);
    if(k>=3)v.push_back(k-3);
    if(k<=5)v.push_back(k+3);
    if(k%3)v.push_back(k-1);
    if(k%3!=2)v.push_back(k+1);
    return v;
}

int main()
{
    string s,e="123804765";
    cin>>s;
    map<string,int>m1,m2;
    m1[s]=1,m2[e]=1;
    queue<string>qs,qe;
    qs.push(s),qe.push(e);
    while(true)
    {
        s=qs.front();
        if(m2[s])break;
        qs.pop();
        vector<int>v=f(s);
        int k=v[0];
        string s2;
        for(int i=1;i<v.size();i++){
            s2=s,s2[k]=s2[v[i]],s2[v[i]]='0';
            if(m1[s2]==0) {
                m1[s2] = m1[s]+1;
                qs.push(s2);
            }
        }
        s=qe.front();
        if(m1[s])break;
        qe.pop();
        v=f(s);
        k=v[0];
        for(int i=1;i<v.size();i++){
            s2=s,s2[k]=s2[v[i]],s2[v[i]]='0';
            if(m2[s2]==0) {
                m2[s2] = m2[s]+1;
                qe.push(s2);
            }
        }
    }
    cout<<m1[s]+m2[s]-2;
    return 0;
}

总耗时26ms

从结果看,DBFS比BFS快很多。

HackerRank - pacman-astar

In the previous game, you performed UCS on the PacMan grid. In this game we use AStar algorithm to reduce the nodes expanded using search using a simple yet efficient heuristic.

AStar on graphs uses the following function

cost = d(s,c) + h(c)

where s is the source node, c is the node currently expanded and h(c) is the estimation of the cost to reach from c to the destination ( food ).

In this game, we use manhattan heuristic as an estimate. Given two nodes (r,c) and (r1,c1). The manhattan heuristic is the manhattan distance between the two nodes and is given by

|r1 - r | + |c1 - c|

Input Format

The first line contains 2 space separated integers which is the position of the PacMan.
The second line contains 2 space separated integers which is the position of the food.
The third line of the input contains 2 space separated integers. Indicating the size of the rows and columns respectively.
This is followed by row (r) lines each containing column (c) characters. A wall is represented by the character '%' ( ascii value 37 ), PacMan is represented by UpperCase alphabet 'P' ( ascii value 80 ), empty spaces which can be used by PacMan for movement is represented by the character '-' ( ascii value 45 ) and food is represented by the character '.' ( ascii value 46 )

The top left of the grid is indexed (0,0) and the bottom right of the grid is indexed (r-1,c-1)

The grid is indexed as per matrix convention

For the sake of uniformity across all codes, cost to reach a neighboring node

  • 0 if a food is present.
  • 1 otherwise.

Output Format

Each cell in the grid is represented by its position in the grid (x,y). PacMan can move only UP, DOWN, LEFT or RIGHT. Your task is to print all the nodes in the shortest path calculated using Astar search between Pacman and Food.

 %
%--
 -

In the above cell, LEFT and UP are invalid moves. You can either go RIGHT or DOWN. RIGHT is populated first followed by DOWN. i.e., populate the queue UP, LEFT, RIGHT and DOWN order so that UP gets popped first from the queue.

Print the distance 'D' between the source 'P' and the destination '.' calculated using Astar. D+1 lines follow, each line having a node encountered between 'P' and '.' both included. D+1 lines essentially representing the path between source and the destination.

Sample Input

35 35
35 1
37 37
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
%-------%-%-%-----------%---%-----%-%
%-%%%%%%%-%-%%%-%-%%%-%%%-%%%%%%%-%-%
%-------%-------%-%-----%-----%-%---%
%%%%%-%%%%%-%%%-%-%-%-%%%-%%%%%-%-%%%
%---%-%-%-%---%-%-%-%---%-%---%-%---%
%-%%%-%-%-%-%%%-%%%%%-%%%-%-%%%-%%%-%
%-------%-----%---%---%-----%-%-%---%
%%%-%%%%%%%%%-%%%%%%%-%%%-%%%-%-%-%-%
%-------------%-------%-%---%-----%-%
%-%-%%%%%-%-%%%-%-%-%%%-%-%%%-%%%-%-%
%-%-%-----%-%-%-%-%-----%---%-%-%-%-%
%-%-%-%%%%%%%-%-%%%%%%%%%-%%%-%-%%%-%
%-%-%-%-----%---%-----%-----%---%---%
%%%-%%%-%-%%%%%-%%%%%-%%%-%%%-%%%%%-%
%-----%-%-%-----%-%-----%-%---%-%-%-%
%-%-%-%-%-%%%-%%%-%%%-%%%-%-%-%-%-%-%
%-%-%-%-%-----------------%-%-%-----%
%%%-%%%%%%%-%-%-%%%%%-%%%-%-%%%-%%%%%
%-------%-%-%-%-----%---%-----%-%---%
%%%%%-%-%-%%%%%%%%%-%%%%%%%%%%%-%-%%%
%---%-%-----------%-%-----%---%-%---%
%-%%%-%%%%%-%%%%%%%%%-%%%%%-%-%-%%%-%
%-%---%------%--------%-----%-------%
%-%-%-%%%%%-%%%-%-%-%-%-%%%%%%%%%%%%%
%-%-%---%-----%-%-%-%-------%---%-%-%
%-%-%%%-%%%-%-%-%-%%%%%%%%%-%%%-%-%-%
%-%---%-%---%-%-%---%-%---%-%-%-----%
%-%%%-%%%-%%%%%-%%%-%-%-%%%%%-%-%%%%%
%-------%---%-----%-%-----%---%-%---%
%%%-%-%%%%%-%%%%%-%%%-%%%-%-%%%-%-%%%
%-%-%-%-%-%-%-%-----%-%---%-%---%-%-%
%-%-%%%-%-%-%-%-%%%%%%%%%-%-%-%-%-%-%
%---%---%---%-----------------%-----%
%-%-%-%-%%%-%%%-%%%%%%%-%%%-%%%-%%%-%
%.%-%-%-------%---%-------%---%-%--P%
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%

Sample Output

210
35 35
34 35
33 35
33 34
33 33
33 32
33 31
32 31
31 31
31 30
31 29
32 29
33 29
33 28
33 27
33 26
33 25
33 24
33 23
33 22
33 21
33 20
33 19
33 18
33 17
33 16
33 15
32 15
31 15
31 16
31 17
30 17
29 17
29 16
29 15
28 15
27 15
26 15
25 15
24 15
23 15
23 16
23 17
23 18
23 19
23 20
23 21
24 21
25 21
25 22
25 23
24 23
23 23
23 24
23 25
23 26
23 27
22 27
21 27
21 28
21 29
22 29
23 29
23 30
23 31
22 31
21 31
20 31
19 31
18 31
17 31
17 32
17 33
17 34
17 35
16 35
15 35
14 35
13 35
12 35
11 35
10 35
9 35
8 35
7 35
7 34
7 33
8 33
9 33
9 32
9 31
9 30
9 29
10 29
11 29
12 29
13 29
14 29
15 29
15 28
15 27
16 27
17 27
18 27
19 27
19 26
19 25
18 25
17 25
17 24
17 23
17 22
17 21
17 20
17 19
17 18
17 17
17 16
17 15
17 14
17 13
16 13
15 13
15 14
15 15
14 15
13 15
12 15
11 15
10 15
9 15
9 16
9 17
9 18
9 19
9 20
9 21
8 21
7 21
6 21
5 21
4 21
3 21
2 21
1 21
1 20
1 19
1 18
1 17
1 16
1 15
2 15
3 15
3 14
3 13
3 12
3 11
4 11
5 11
6 11
7 11
7 12
7 13
8 13
9 13
9 12
9 11
9 10
9 9
9 8
9 7
9 6
9 5
9 4
9 3
10 3
11 3
12 3
13 3
14 3
15 3
16 3
17 3
18 3
19 3
19 4
19 5
20 5
21 5
22 5
23 5
23 4
23 3
24 3
25 3
26 3
27 3
27 4
27 5
28 5
29 5
29 4
29 3
30 3
31 3
32 3
33 3
33 2
33 1
34 1
35 1

In this example, PacMan is at the position (35,35) and the food is at the position (35,1). The AStar path length between (35,35) and (35,1) is 210. All the nodes encountered between (35,35) and (35,1) both included is printed in the next 211 lines.

#include<iostream>
#include<queue>
#include<unordered_map>
#include<stack>
#include<vector>
using namespace std;

#define M 100
char ch[M][M];
int row, col;

struct Point
{
    int x, y;
};
int PtoI(Point s)
{
    return s.x * M + s.y;
}
Point  ItoP(int x)
{
    return { x / M,x % M };
}

bool inBoard(Point s)
{
    return s.x >= 0 && s.x < row&& s.y >= 0 && s.y < col;
}
bool available(Point s)
{
    return ch[s.x][s.y] != '%';
}

vector<Point> neighbor(Point s)
{
    int dx[] = { -1,0,1,0 };
    int dy[] = { 0,1,0,-1 };
    Point t;
    vector<Point>ans;
    for (int i = 0; i < 4; i++)
    {
        t.x = s.x + dx[i], t.y = s.y + dy[i];
        if (!inBoard(t))continue;
        if (!available(t))continue;
        ans.push_back(t);
    }
    return ans;
}

int main()
{
    Point s, e;
    cin >> s.x >> s.y >> e.x >> e.y;
    cin >> row >> col;
    for (int i = 0; i < row; i++)for (int j = 0; j < col; j++)cin >> ch[i][j];
    queue<Point>q1, q2;
    q1.push(s), q2.push(e);
    unordered_map<int, int>m1, m2;
    m1[PtoI(s)] = 1, m2[PtoI(e)] = 1;
    while (true)
    {
        s = q1.front();
        q1.pop();
        if (m2[PtoI(s)])break;
        vector<Point>v = neighbor(s);
        for (auto vs : v)
        {
            if (m1[PtoI(vs)])continue;
            m1[PtoI(vs)] = m1[PtoI(s)] + 1;
            q1.push(vs);
        }
        s = q2.front();
        q2.pop();
        if (m1[PtoI(s)])break;
        v = neighbor(s);
        for (auto vs : v)
        {
            if (m2[PtoI(vs)])continue;
            m2[PtoI(vs)] = m2[PtoI(s)] + 1;
            q2.push(vs);
        }
    }
    int k1 = m1[PtoI(s)], k2 = m2[PtoI(s)];
    cout << k1 + k2 - 2 << endl;
    Point s0 = s;
    stack<Point>sp;
    for (int i = k1; i > 1; i--)
    {
        vector<Point>v = neighbor(s);
        for (auto vs : v)
        {
            if (m1[PtoI(vs)] == i - 1) {
                s = vs;
                break;
            }
        }
        sp.push(s);
    }
    while (!sp.empty()) {
        s = sp.top();
        cout << s.x << " " << s.y << endl;
        sp.pop();
    }
    s = s0;
    for (int i = k2; i >1; i--)
    {
        cout << s.x << " " << s.y << endl;
        vector<Point>v = neighbor(s);
        for (auto vs : v)
        {
            if (m2[PtoI(vs)] == i - 1) {
                s = vs;
                break;
            }
        }
    }
    cout << s.x << " " << s.y << endl;
    return 0;
}

力扣 752. 打开转盘锁

你有一个带有四个圆形拨轮的转盘锁。每个拨轮都有10个数字: '0', '1', '2', '3', '4', '5', '6', '7', '8', '9' 。每个拨轮可以自由旋转:例如把 '9' 变为 '0','0' 变为 '9' 。每次旋转都只能旋转一个拨轮的一位数字。

锁的初始数字为 '0000' ,一个代表四个拨轮的数字的字符串。

列表 deadends 包含了一组死亡数字,一旦拨轮的数字和列表里的任何一个元素相同,这个锁将会被永久锁定,无法再被旋转。

字符串 target 代表可以解锁的数字,你需要给出解锁需要的最小旋转次数,如果无论如何不能解锁,返回 -1 。

示例 1:

输入:deadends = ["0201","0101","0102","1212","2002"], target = "0202"
输出:6
解释:
可能的移动序列为 "0000" -> "1000" -> "1100" -> "1200" -> "1201" -> "1202" -> "0202"。
注意 "0000" -> "0001" -> "0002" -> "0102" -> "0202" 这样的序列是不能解锁的,
因为当拨动到 "0102" 时这个锁就会被锁定。
示例 2:

输入: deadends = ["8888"], target = "0009"
输出:1
解释:把最后一位反向旋转一次即可 "0000" -> "0009"。
示例 3:

输入: deadends = ["8887","8889","8878","8898","8788","8988","7888","9888"], target = "8888"
输出:-1
解释:无法旋转到目标数字且不被锁定。
 

提示:

1 <= deadends.length <= 500
deadends[i].length == 4
target.length == 4
target 不在 deadends 之中
target 和 deadends[i] 仅由若干位数字组成

BFS解法:

string nebor(char c)
{
	if (c == '0')return "19";
	if (c == '9')return "08";
	string s = "00";
	s[0] = c - 1, s[1] = c + 1;
	return s;
}
vector<string> nebor(string s)
{
	vector<string>v;
	for (int i = 0; i < 4; i++) {
		string s2 = s;
		s2[i] = nebor(s[i])[0];
		v.push_back(s2);
		s2[i] = nebor(s[i])[1];
		v.push_back(s2);
	}
	return v;
}
class Solution {
public:
	int openLock(vector<string>& deadends, string s2) {
		map<string, int>dead;
		for (auto& si : deadends)dead[si] = 1;
        if(dead["0000"])return -1;
		string s1 = "0000";
		queue<string>q;
		q.push(s1);
		map<string, int>m;
		m[s1] = 1;
		while (!q.empty()) {
			string s = q.front();
			q.pop();
			if (s == s2)return m[s]-1;
			vector<string> v = nebor(s);
			for (auto& vi : v) {
				if (dead[vi]||m[vi])continue;
				q.push(vi);
				m[vi] = m[s] + 1;
			}
		}
		return -1;
	}
};

 双向BFS解法:

string nebor(char c)
{
	if (c == '0')return "19";
	if (c == '9')return "08";
	string s = "00";
	s[0] = c - 1, s[1] = c + 1;
	return s;
}
vector<string> nebor(string s)
{
	vector<string>v;
	for (int i = 0; i < 4; i++) {
		string s2 = s;
		s2[i] = nebor(s[i])[0];
		v.push_back(s2);
		s2[i] = nebor(s[i])[1];
		v.push_back(s2);
	}
	return v;
}
class Solution {
public:
	int openLock(vector<string>& deadends, string s2) {
		map<string, int>dead;
		for (auto& si : deadends)dead[si] = 1;
        if(dead["0000"])return -1;
		string s1 = "0000";
		queue<string>q1;
		q1.push(s1);
		map<string, int>m1;
		m1[s1] = 1;
		queue<string>q2;
		q2.push(s2);
		map<string, int>m2;
		m2[s2] = 1;
		bool flag = true;
		while (flag) {
			flag = false;
			if (!q1.empty()) {
				flag = true;
				string s = q1.front();
				q1.pop();
				if (m2[s])return m1[s] + m2[s] - 2;
				vector<string> v = nebor(s);
				for (auto& vi : v) {
					if (dead[vi] || m1[vi])continue;
					q1.push(vi);
					m1[vi] = m1[s] + 1;
				}
			}
			if (!q2.empty()) {
				flag = true;
				string s = q2.front();
				q2.pop();
				if (m1[s])return m1[s] + m2[s] - 2;
				vector<string> v = nebor(s);
				for (auto& vi : v) {
					if (dead[vi] || m2[vi])continue;
					q2.push(vi);
					m2[vi] = m2[s] + 1;
				}
			}
		}
		return -1;
	}
};

五,广度优先树

朴素的广度优先搜索,每个节点只遍历一遍,在遍历的过程中会产生一颗广度优先树。

每个节点从队列取出之后,把它的部分邻居添加到队列末尾,这个关系等同于广度优先树中的边,即父节点和若干子节点的关系。

多起点的广度优先搜索,会产生由多个广度优先树组成的森林。

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值