填充每个节点的下一个右侧节点指针 II(Leetcode-117)- BFS &双向BFS

题目

知识点

  • 广度优先搜索 & 双向BFS
  • 队列

思路

直接广度优先搜索

  • 虽然和字符串有关,本质其实就是最短路径问题。因此,使用BFS做肯定是没有问题的。
  • 简单思路:每一次把所有“到达”的单词放入队列中(符合“到达”的条件:单词只有一个字母不相等),每次看队头单词是否 endWord 相同,如果相同说明找到了“最段路径”。这里我使用了一个结构体记录状态,里面的step就是当前状态需要经过的变化的次数
  • 问题: 数据给的比较大!在最后一组测试数据过不去(怎么改都过不去),问题应该是找可以“到达”时候的效率太慢(从a到z查询替换没有试,或许可以QAQ),另外就是,生成的广度优先遍历树也太大了。
  • 关键代码如下:(最后一组时间超时 49 / 49 个通过测试用例,判重函数ok()可以在双向BFS代码中查看)
        int bfs(vector<string> &wordList) {
            q.push(path(bg, 1));
            while (!q.empty()) {
                path u = q.front();
                q.pop();
                if (u.s == ed) return u.step;
    
                for (const string& i : wordList) {  // 能转换 & 没有走到过的单词
                    if (ok(i, u.s) && !vis.count(i)) {
                        q.push(path(i, u.step + 1));
                        vis.insert(i);
                    }
                }
            }
            return 0;
    	 }
    

双向广度优先搜索

  • 好家伙,超时真的整的头大,只好双向BFS了。
  • 双向BFS画个图进行一下直观的解释:
    • 这是直接广度优先搜索示意图(老灵魂画手):
      在这里插入图片描述
    • 这是双向广度优先搜索示意图
      在这里插入图片描述
    • 双向BFS相比于直接BFS,同时从Begin和End出发进行BFS,直到相遇元素,最短路径就是这个元素从Begin的路径+从End到这个元素的路径。相比于直接BFS,搜索域小了很多(图中可能画的不是特别明显),因此可以提高效率。
    • 同时使用两个队列 queue<path> q_front, q_end,使用两个 unordered_set<string> vis_front, vis_end 用于记录两个方向的队列中的元素是否访问过。如果某个队列访问到该元素恰好在反方向也已经遍历过,那么结果就是从正向和从逆向到该元素路径距离之和即使最短“路径”(Line 31-38Line 59-58)。
    • PS:由于队列没法直接查找元素,因此需要在反方向的队列中,一个个遍历去访问找到这个节点。

代码

class Solution {
public:
    struct path {
        string s;
        int step;

        path(string ss, int sp) : s(ss), step(sp) {}
    };

    string ed;
    queue<path> q_front, q_end;
    unordered_set<string> vis_front, vis_end;

    bool ok(string after, string before) {  // 判断是否是可访问的“路径”,和BFS判断函数一致
        int cnt = 0;
        for (int i = 0; i < ed.size(); i++) {
            if (after[i] != before[i] && ++cnt > 1) {
                return false;
            }
        }
        return cnt == 1;
    }

    int bfs(string &bg, vector<string> &wordList) {
        while (!q_front.empty() || !q_end.empty()) {
            if (!q_front.empty()) {  // 先正向
                path u = q_front.front();
                q_front.pop();
                for (const string &i : wordList) {  // 能转换 & 没有走到过的单词
                    if (ok(i, u.s) && !vis_front.count(i)) {
                        if (vis_end.count(i)) {  // 逆序访问到过->找到
                            int end_step;
                            while (!q_end.empty()) {
                                path v = q_end.front();
                                q_end.pop();
                                if (v.s == i) end_step = v.step;
                            }
                            return u.step + end_step;
                        }
                        q_front.emplace(path(i, u.step + 1));
                        vis_front.insert(i);
                    }
                }
            }
            if (!q_end.empty()) {  // 反向
                path u = q_end.front();
                q_end.pop();
                for (const string &i : wordList) {  // 能转换 & 没有走到过的单词
                    if (ok(i, u.s) && !vis_end.count(i)) {
                        if (vis_front.count(i)) {  // 正序访问到过->找到
                            int front_step;
                            while (!q_front.empty()) {
                                path v = q_front.front();
                                q_front.pop();
                                if (v.s == i) front_step = v.step;
                            }
                            return u.step + front_step;
                        }
                        q_end.emplace(path(i, u.step + 1));
                        vis_end.insert(i);
                    }
                }
            }
        }
        return 0;
    }

    int ladderLength(string beginWord, string endWord, vector<string> &wordList) {
        ed = endWord;
        if (find(wordList.begin(), wordList.end(), ed) - wordList.begin() == wordList.size()) return 0;  // 没有这个结尾单词
        q_front.emplace(path(beginWord, 1)), q_end.emplace(path(endWord, 1));
        vis_front.emplace(beginWord), vis_end.emplace(endWord);
        return bfs(beginWord, wordList);
    }
};
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值