思路:
这道题目挺有意思的,需要脑经稍微转下,感觉笔试出这种题目的话也是无可厚非。。搜索问题一开始想到dfs,dfs需要构建搜索树啊,怎么构建呢?可以看到一共是4个键,每次可以选择其中一个键上调或者下调,特殊情况是0和9的情况0下调是9,9上调是0.这样每次操作就8个,时间复杂度就是8的n次方,那什么时候结束呢,题目设置了不能走禁区组合,走到就锁住了,如果只是不能触碰禁区没有其他条件如果走不到目标组合就一直走了,==所以我们每次走过的路都存入哈希表。==如果走过,下次就不走了,因为再走就是走老路,这步数肯定不比上次少。直到无路可走,因为都走过了。那就结束
但是用深度优先搜索的话这个判断机制还是比较难写。因为我们只要走到就行,这种题目适合bfs广度优先。每个节点扩张节点,走过就不扩张,直到走完!
注意到子函数必须写在主函数外面,leetcode官方题目把子函数写在里面了,所以用了不同的方式,我们还是走常规路线把。
但是还是来学习下,这个定义都是auto开头,get是函数名,然后= ,方括号如果虚拟变量要索引就把索引加到方括号,小括号是虚拟变量->给输出的变量类型,这样就定义好了.
auto get = [&](string& status) -> vector<string
注意到当输出是空的时候是这个形式,前面是function和void
function<void(int, int)> dfs = [&](int index, int steps)
下面附上bfs的代码:
class Solution {
public:
char num_prev(char x){
return (x == '0' ? '9' : x - 1);
}
char num_succ(char x) {
return (x == '9' ? '0' : x + 1);
}
// 枚举 status 通过一次旋转得到的数字
vector<string> get(string &status){
vector<string> ret;
for (int i = 0; i < 4; ++i) {
char num=status[i];
status[i]=num_prev(num);
ret.push_back(status);
status[i]=num_succ(num);
ret.push_back(status);
status[i] = num; //千万不能少因为我逐位变化第二位变化的时候第一位不能给他变了啊
}
return ret;
}
//进入主函数
int openLock(vector<string>& deadends, string target) {
unordered_set<string> dead(deadends.begin(), deadends.end()); //这个方式有点特别啊
unordered_set<string> seen={"0000"}; //和第一种对比
if(target=="0000")
return 0;
if(dead.count("0000"))
return -1;
queue<pair<string,int>> q;
q.emplace("0000",0);
while(!q.empty()){
auto [status,step]=q.front();
q.pop();
for(auto && nextstatus:get(status)){
if(!seen.count(nextstatus) && !dead.count(nextstatus)){
if(nextstatus==target)
return step+1;
seen.insert(nextstatus);
q.emplace(nextstatus,step+1);
}
}
}
return -1;
}
};
说到这种搜索式的题目不得不提 滑动谜题,也是用bfs进行搜索,有点像上面的题目。感觉这两道题经常复习,有可能会出现在笔试中。
思路:
注意到我们传入的参数step是当前已经完成的step,刚开始传入0,就是完成了0次,然后完成了一次后传入step+1;直接上代码:
class Solution {
vector<vector<int>> neighbors = {{1, 3}, {0, 2, 4}, {1, 5}, {0, 4}, {1, 3, 5}, {2, 4}};
public:
// 枚举 status 通过一次交换操作得到的状态
vector<string> get (string& status) {
vector<string> ret;
int x = status.find('0');
for (int y: neighbors[x]) {
swap(status[x], status[y]);
ret.push_back(status);
swap(status[x], status[y]);
}
return ret;
}
int slidingPuzzle(vector<vector<int>>& board) {
string initial;
for (int i = 0; i < 2; ++i) {
for (int j = 0; j < 3; ++j) {
initial += char(board[i][j] + '0');
}
}
if (initial == "123450") {
return 0;
}
queue<pair<string, int>> q;
q.emplace(initial, 0);
unordered_set<string> seen = {initial};
while (!q.empty()) {
auto [status, step] = q.front();
q.pop();
for (auto&& next_status: get(status)) {
if (!seen.count(next_status)) {
if (next_status == "123450") {
return step + 1;
}
q.emplace(next_status, step + 1);
seen.insert(move(next_status));
}
}
}
return -1;
}
};
同样的,还是一道bfs题目:
class Solution {
pair<int, int> id2rc(int id, int n) {
int r = (id - 1) / n, c = (id - 1) % n;
if (r % 2 == 1) {
c = n - 1 - c;
}
return {n - 1 - r, c};
}
public:
int snakesAndLadders(vector<vector<int>> &board) {
int n = board.size();
vector<int> vis(n * n + 1);
queue<pair<int, int>> q;
q.emplace(1, 0);
while (!q.empty()) {
auto p = q.front();
q.pop();
for (int i = 1; i <= 6; ++i) {
int nxt = p.first + i;
if (nxt > n * n) { // 超出边界
break;
}
auto rc = id2rc(nxt, n); // 得到下一步的行列
if (board[rc.first][rc.second] > 0) { // 存在蛇或梯子
nxt = board[rc.first][rc.second]; //蛇或者梯子最终还是让他走后面那条路所以记录后面的。
}
if (nxt == n * n) { // 到达终点
return p.second + 1;
}
if (!vis[nxt]) {
vis[nxt] = true; //这个没问题,先来到这里的,肯定是最短路径,因为后面走的路都一样了!true完事!!!!!!
q.emplace(nxt, p.second + 1); // 扩展新状态
}
}
}
return -1;
}
};
—————————————————————————————————————————————
思路:
这是和广度优先遍历干上了啊,这是和图能联系起来的问题,这种问题是一步步,所以是层序遍历。但是在层序遍历之前需要建立图关系。 有两种方法求解这个题目,官方题解是标准的图法,在B站看到一种比较好理解的思路。都给他记录下吧。上面的图是为了方便理解的。同样颜色的线代表移量公交车能走的区域。
B站的思路:构建图只是为了方便思路,而不是利用纯粹图知识。首先建造一个哈希表用来存储对应站点邻接的公交车,比如1站点可以有2个公交车来操作,6站点只有一个公交车来操作。
其次,建立一个关于公交车的set或者在这里是数组,来记录是否访问过某辆公交车。然后建立一个记录站点的队列和bus记录走过的公交车数量。初始值为0
我们首先将初始站点加入队列,然后进入队列的while,注意到此时bus++,在当前队列中表明上车了。
如何扩展队列呢?我们通过最开始建造的哈希表查找这个站点对应的公交车,1站点对应1 2两种公交车,我们可以从题目给的条件中获得对应公交车存在的站点。1 2 公交车存在的站点是1273,然后我们把站点扩展到队列中,下次以他们为中心搜索是否没走过的公交车。
如果公交车走过了,我们就终止这边的扩展,因为同一种的公交车经过的站点我们已经在之前全部加入队列了。在加入的同时我们也判断了站点和目标站点是否相同了。
注意到每次进入队列我们的bus数量就会+1,为了体现层序遍历,每次都要把队列清空!所以不用担心bus的重复,
同一次能乘坐的公交车站点如果是走过的公交车他会continue,每个站点的价值在于是否能直到能扩展新的公交车,那么当能扩展新的公交车时,我们会标记公交车走过,然后查找对应公交车的新站点,判断,然后加入队列。表示再次搭了一轮车了。如果队列所有站点都无法扩展新的公交车,此时队列为空,然后返回-1了。
B站题解:
class Solution {
public:
int numBusesToDestination(vector<vector<int>>& routes, int source, int target) {
if(source==target)
return 0;
unordered_map<int,vector<int>>m;//某站点对应的公交车
for(int i=0;i<routes.size();i++)
for(const int stop: routes[i])
m[stop].push_back(i);
vector<int>ride(routes.size(),0);
queue<int>q;
q.emplace(source);
int buses=0;
while(!q.empty()){
int size=q.size();
buses++;
for(int i=0;i<size;i++){ //注意到,前面不要在这里加q.size(),因为每次都是pop了!!
int curr=q.front();q.pop();
for(const int bus: m[curr]){ //找出站点对应的公交车
if(ride[bus]) continue;
ride[bus]=1;
for(int stop:routes[bus]){//找出公交车对应的站点
if(stop==target) return buses;
q.emplace(stop); //担心这里会不会重复增加进去呢 的确,重复了,但是没事接下来pop都做过的就会continue
}
}
}
}
return -1;
}
};
官方题解:
class Solution {
public:
int numBusesToDestination(vector<vector<int>>& routes, int source, int target) {
if (source == target) {
return 0;
}
int n = routes.size();
vector<vector<int>> edge(n, vector<int>(n));
unordered_map<int, vector<int>> rec;
for (int i = 0; i < n; i++) {
for (auto& site : routes[i]) {
for (auto& j : rec[site]) {
edge[i][j] = edge[j][i] = true; //每个公交车的连通情况 i是初始公交车,j是
}
rec[site].push_back(i);
}
}
vector<int> dis(n, -1);
queue<int> que;
for (auto& site : rec[source]) {
dis[site] = 1;
que.push(site);
}
while (!que.empty()) {
int x = que.front();
que.pop();
for (int y = 0; y < n; y++) {
if (edge[x][y] && dis[y] == -1) {
dis[y] = dis[x] + 1;
que.push(y);
}
}
}
int ret = INT_MAX;
for (auto& site : rec[target]) {
if (dis[site] != -1) {
ret = min(ret, dis[site]);
}
}
return ret == INT_MAX ? -1 : ret;
}
};