目录
介绍
迷宫中离入口最近的出口
算法思路
代码实现
最小基因变化
算法思路
代码实现
单词接龙
算法思路
代码实现
为高尔夫比赛砍树
算法思路
代码实现
介绍
最短路问题是
图论
中非常经典的一种问题,其实就是通过代码找到两点之间的最优路径(往往是距离最短)
,最短路问题的解法很多,比如A*算法,迪杰斯特拉算法等等,本文介绍最短路问题中最简单的一种边权为1的最短路问题。
所谓的边权,就是指两个地点之间的距离为1
(如下图所示)
很明显,实际情况的道路更加复杂,两个地点之间的距离不能全是1,所以边权为1的最短路问题是比较特殊,简单的最短路问题
要记录整个过程的最短路,可以通过bfs来解决,选定一个地点,以这个地点为中心向外扩展(因为一个地点连接着很多的地点)
在扩散的时候来时的路是不需要再扩散回去的,所以就需要使用进行过标记。
下面是几道例题
迷宫中离入口最近的出口
例题地址:. - 力扣(LeetCode)
给你一个 m x n
的迷宫矩阵 maze
(下标从 0 开始),矩阵中有空格子(用 '.'
表示)和墙(用 '+'
表示)。同时给你迷宫的入口 entrance
,用 entrance = [entrancerow, entrancecol]
表示你一开始所在格子的行和列。
每一步操作,你可以往 上,下,左 或者 右 移动一个格子。你不能进入墙所在的格子,你也不能离开迷宫。你的目标是找到离 entrance
最近 的出口。出口 的含义是 maze
边界 上的 空格子。entrance
格子 不算 出口。
请你返回从 entrance
到最近出口的最短路径的 步数 ,如果不存在这样的路径,请你返回 -1
。
示例 1:
输入:maze = [["+","+",".","+"],[".",".",".","+"],["+","+","+","."]], entrance = [1,2] 输出:1 解释:总共有 3 个出口,分别位于 (1,0),(0,2) 和 (2,3) 。 一开始,你在入口格子 (1,2) 处。 - 你可以往左移动 2 步到达 (1,0) 。 - 你可以往上移动 1 步到达 (0,2) 。 从入口处没法到达 (2,3) 。 所以,最近的出口是 (0,2) ,距离为 1 步。
算法思路
这道题给出了起点,让求出到达终点最小的路径,也就是走了几步到达了终点,终点就是矩阵的边缘且不能在墙上走。
首先进行准备工作,定义好矩阵的边界,和遍历需要的dx,dy数组,以及标记数组。
1.在nearestExit函数中只需要调用一次bfs即可;
2.bfs函数实现:count来统计走的步数,老一套的模版了,不懂得可以看博客CSDN,不同的是,这里我们每次进行一层的遍历都需要把当前层的全部都删除,这样我们在下一层中找到合法的数据,在进行插入队列。在每次插入新的元素时千万别忘记了标记,因为走过的路是不需要再回去的。
代码实现
class Solution {
public:
int m,n;
int dx[4]={0,0,1,-1};
int dy[4]={1,-1,0,0};
bool vis[101][101];
int nearestExit(vector<vector<char>>& maze, vector<int>& entrance) {
m=maze.size();n=maze[0].size();
int i=entrance[0];int j=entrance[1];
return bfs(maze, i, j);
}
int bfs(vector<vector<char>>& maze, int i,int j)
{
int count=0;//每次遍历一层就步数+1
queue<pair<int,int>>q;
q.push({i,j});
vis[i][j]=true;
while(q.size())
{
count++;
int size=q.size();//将这一层的都派出去
for(int r=0;r<size;r++)
{
auto [a,b]=q.front();//取出上一层的,然后把下一层的合法的都插入队列
q.pop();
for(int k=0;k<4;k++)
{
int x=a+dx[k];int y=b+dy[k];
if(x>=0&&x<m&&y>=0&&y<n&&maze[x][y]=='.'&&!vis[x][y])
{
if(x==0||x==m-1||y==0||y==n-1)return count;//如果到达边界直接返回
q.push({x,y});
vis[x][y]=true;
}
}
}
}
return -1;
}
};
最小基因变化
例题地址:. - 力扣(LeetCode)
基因序列可以表示为一条由 8 个字符组成的字符串,其中每个字符都是 'A'
、'C'
、'G'
和 'T'
之一。
假设我们需要调查从基因序列 start
变为 end
所发生的基因变化。一次基因变化就意味着这个基因序列中的一个字符发生了变化。
- 例如,
"AACCGGTT" --> "AACCGGTA"
就是一次基因变化。
另有一个基因库 bank
记录了所有有效的基因变化,只有基因库中的基因才是有效的基因序列。(变化后的基因必须位于基因库 bank
中)
给你两个基因序列 start
和 end
,以及一个基因库 bank
,请你找出并返回能够使 start
变化为 end
所需的最少变化次数。如果无法完成此基因变化,返回 -1
。
注意:起始基因序列 start
默认是有效的,但是它并不一定会出现在基因库中。
示例 1:
输入:start = "AACCGGTT", end = "AACCGGTA", bank = ["AACCGGTA"] 输出:1
示例 2:
输入:start = "AACCGGTT", end = "AAACGGTA", bank = ["AACCGGTA","AACCGCTA","AAACGGTA"] 输出:2
示例 3:
输入:start = "AAAAACCC", end = "AACCCCCC", bank = ["AAAACCCC","AAACCCCC","AACCCCCC"] 输出:3
算法思路
这道题也是一道最短路问题,第一次进行变化有这么多种的情况,然后下一次图中的每一种情况会在进行变化一个字符,然后推演出更多种的基因序列,直到出现了最后目标的基因序列,就算是达到了终点。我们每走一步就枚举处所有的情况,然后把在基因库中存在的基因序列且没有出现过的放在队列中,回到最后目标基因出现。
代码实现
class Solution {
public:
int minMutation(string startGene, string endGene, vector<string>& bank) {
set<string>hash(bank.begin(),bank.end());
set<string>vis;
string change="ATGC";//用来枚举改变的基因;
if(startGene==endGene)return 0;///如果一开始基因就是目标基因,那么直接返回即可;
if(!hash.count(endGene))return -1;//如果目标基因不在基因库中那么就无需操作了
queue<string>q;
q.push(startGene);
vis.insert(startGene);
int step=0;
while(q.size())
{
step++;
int ret=q.size();
while(ret--)
{
string t=q.front();
q.pop();
for(int i=0;i<8;i++)
{
string tmp=t;//不能改变原来的基因序列,所以需要中间变量、
for(int j=0;j<4;j++)//枚举每一位否改变的情况
{
tmp[i]=change[j];
if(hash.count(tmp)&&!vis.count(tmp))
{
if(tmp==endGene)return step;
q.push(tmp);
vis.insert(tmp);
}
}
}
}
}
return -1;
}
};
单词接龙
例题地址:. - 力扣(LeetCode)
字典 wordList
中从单词 beginWord
到 endWord
的 转换序列 是一个按下述规格形成的序列 beginWord -> s1 -> s2 -> ... -> sk
:
- 每一对相邻的单词只差一个字母。
- 对于
1 <= i <= k
时,每个si
都在wordList
中。注意,beginWord
不需要在wordList
中。 sk == endWord
给你两个单词 beginWord
和 endWord
和一个字典 wordList
,返回 从 beginWord
到 endWord
的 最短转换序列 中的 单词数目 。如果不存在这样的转换序列,返回 0
。
示例 1:
输入:beginWord = "hit", endWord = "cog", wordList = ["hot","dot","dog","lot","log","cog"] 输出:5 解释:一个最短转换序列是 "hit" -> "hot" -> "dot" -> "dog" -> "cog", 返回它的长度 5。
示例 2:
输入:beginWord = "hit", endWord = "cog", wordList = ["hot","dot","dog","lot","log"] 输出:0 解释:endWord "cog" 不在字典中,所以无法进行转换。
算法思路
这道题与上一道题是思路是相同的,只是让统计的是变化中出现单词的个数;
代码实现
class Solution {
public:
int ladderLength(string beginWord, string endWord, vector<string>& wordList) {
set<string>hash(wordList.begin(),wordList.end());
set<string>vis;
if(beginWord==endWord)return 1;
if(!hash.count(endWord))return 0;
queue<string>q;
q.push(beginWord);
vis.insert(beginWord);
int ret=1;
while(q.size())
{
ret++;
int sz=q.size();
while(sz--)
{
string t=q.front();
q.pop();
for(int i=0;i<beginWord.size();i++)
{
string tmp=t;
for(char ch='a';ch<='z';ch++)
{
tmp[i]=ch;
if(hash.count(tmp)&&!vis.count(tmp))
{
if(tmp==endWord)return ret;
q.push(tmp);
vis.insert(tmp);
}
}
}
}
}
return 0;
}
};
为高尔夫比赛砍树
例题地址:. - 力扣(LeetCode)
你被请来给一个要举办高尔夫比赛的树林砍树。树林由一个 m x n
的矩阵表示, 在这个矩阵中:
0
表示障碍,无法触碰1
表示地面,可以行走比 1 大的数
表示有树的单元格,可以行走,数值表示树的高度
每一步,你都可以向上、下、左、右四个方向之一移动一个单位,如果你站的地方有一棵树,那么你可以决定是否要砍倒它。
你需要按照树的高度从低向高砍掉所有的树,每砍过一颗树,该单元格的值变为 1
(即变为地面)。
你将从 (0, 0)
点开始工作,返回你砍完所有树需要走的最小步数。 如果你无法砍完所有的树,返回 -1
。
可以保证的是,没有两棵树的高度是相同的,并且你至少需要砍倒一棵树。
示例 1:
输入:forest = [[1,2,3],[0,0,4],[7,6,5]] 输出:6 解释:沿着上面的路径,你可以用 6 步,按从最矮到最高的顺序砍掉这些树。
算法思路
不能再0上走,砍树的顺序要从低到高,因此我们需要先按照树的高度对左边进行一个排序,然后使用BFS进行多次调用,得到总路程;
代码实现
class Solution {
public:
int m,n;
int cutOffTree(vector<vector<int>>& forest) {
m=forest.size();n=forest[0].size();
//准备工作,将要砍的树的坐标进行排序
vector<pair<int,int>>trees;
for(int i=0;i<m;i++)
for(int j=0;j<n;j++)
if(forest[i][j]>1)trees.push_back({i,j});
//对左边进行顺序化处理
sort(trees.begin(), trees.end(), [&](const pair<int, int>& p1, const pair<int, int>& p2)
{
return forest[p1.first][p1.second] < forest[p2.first][p2.second];
});
int dx=0,dy=0;
int ret=0;
for(auto [a,b]:trees)
{
int t=bfs(forest,dx,dy,a,b);//调用bfs得到到达目的地的步数
if(t==-1)return -1;//无法到达就返回-1终止;
ret+=t;//累加合法的步数
dx=a;dy=b;//更新起点
}
return ret;
}
int _x[4]={0,0,1,-1};
int _y[4]={1,-1,0,0};
int bfs(vector<vector<int>>& forest,int dx,int dy,int ex,int ey)
{
if(dx==ex&&dy==ey)return 0;//如果开始就是终点直接返回
bool vis[51][51];//标记数组
memset(vis,0,sizeof vis);//每次从起点到终点都需要重置标记
queue<pair<int,int>>q;
q.push({dx,dy});
vis[dx][dy]=true;//走过的就没必要进行遍历了
int ret=0;
while(q.size())
{
ret++;
int sz=q.size();
while(sz--)
{
//进行遍历
auto [a,b]=q.front();
q.pop();
for(int i=0;i<4;i++)
{
int x=a+_x[i];int y=b+_y[i];
if(x>=0&&x<m&&y>=0&&y<n&&forest[x][y]&&!vis[x][y])//位置合法且没有走过,树的高度合法
{
//如果达到目的地就返回
if(x==ex&&y==ey)return ret;
q.push({x,y});
vis[x][y]=true;
}
}
}
}
return -1;
}
};