leetcode - 126. Word Ladder II

算法系列博客之BFS

BFS在图的算法中也是比较重要的一种,一般寻找具有某种特征的路径算法都会采用BFS的思想。
本篇博客将利用这种思想解决一个寻找最短路径的问题

题目描述

Given two words (beginWord and endWord), and a dictionary’s word list, find all shortest transformation sequence(s) from beginWord to endWord, such that:

Only one letter can be changed at a time
Each transformed word must exist in the word list. Note that beginWord is not a transformed word.
For example,

Given:
beginWord = “hit”
endWord = “cog”
wordList = [“hot”,”dot”,”dog”,”lot”,”log”,”cog”]
Return
[
[“hit”,”hot”,”dot”,”dog”,”cog”],
[“hit”,”hot”,”lot”,”log”,”cog”]
]

函数声明形式:
vector< vector< string > > findLadders(string beginWord, string endWord, vector< string > & wordList)

这不是一个一般意义的图,但仍然可以抽象为图结构来解决,求解的过程就变化成为了求两个节点之间所有的最短路径(即可能有多条路径的长度均等于最短路径值)。
其次题目没有明显的给出图的结构,也没有明确规定使用什么样的结构来存储图,因而发挥的空间比较大,可以完全按照自己的意愿来设计数据结构存储。

将每个单词看作图的节点,两个节点之间是否邻接取决于它们是否有且只有一个字母不一样。
另外,为了适应我们习惯上的使用整数来做图算法,先将每个节点用一个唯一的整数代替,得到整数形式的结果之后再将赋予结果对应的单词

抽象出图结构和问题模型之后,考虑具体的算法
寻找两节点间最短路径最常用的是Dijkstra算法和Floyd算法,但是这两个算法都是特别针对带权图的,并且Dijkstra算法不能找出所有的最短路径(即可能有多条路径的长度等于最小值),而Floyd算法的复杂度又高达O(V^3),因此有必要考虑可行并且高效的算法
从Floyd的思想出发很难再有大的优化,而限制Dijkstra找出所有最短路径的关键在于它每一次都只加入一个点。

回到Dijkstra最初的起点BFS,由于所有权值均等,因此我们可以每次向前走一步,记录每一步能够到达的节点以及其前驱(前面已经走到的节点不再走,但是同一步数允许从不同路径到达同一个节点),当某一步其中某条路径到达了终点的时候,将这一步走完,即不再继续向下搜索,转而从终点向前寻找所有答案

接下来再来考虑另一个问题,这个过程中所需要设计的所有数据结构
首先,为了保证每步都能从上一步所有节点开始走一步,需要建一个队列,每向前走一步之前先获取这个队列的长度L,然后迭代L次取出首节点并把与这个节点相邻的节点放入队列尾部的操作;再有,以前步数走过的节点不能再走,而同一步数允许从不同路径走到同一节点,这就是说,我们还需要一个visited数组,并且这个数组是在每一步结束的时候将队列中多有节点变为已访问,这就要求队列是可做常量遍历的,此外为了防止下一步多次记录某个前驱,在一整步完成之后,必须将这个list里的节点唯一化,在C++STL中,list结合了队列和可遍历的优势,添加删除元素时间复杂度也O(1),并且有唯一化的函数接口,是不二的选择
然后,是需要记录每个节点的前驱,而且最后寻找答案是在从终点向前寻找,路径的多样化表明前驱的个数是不定的,因而可以给每个节点对应绑定一个前驱向量数组,BFS做完之后就相当于节点和前驱向量数组相当于又形成了特殊的从终点到起点的图,再从终点做一遍DFS即可得出答案
最后由于图是无向的,所以从起点到终点做BFS和从终点到起点做BFS都没有关系,如果选择从终点到起点做DFS,前驱就正好变成了答案中的后继,这样做DFS生成答案的时候反而会更加方便

class Solution {
private:
    typedef vector<vector<int> > int_2d;
    bool isAdjc(string& s1, string& s2) {
        if (s1.size() == s2.size()) {
            int num = 0;
            for (int i = 0; i < s1.size(); i++)
                if (s1[i] != s2[i]) num++;
            if (num == 1) return true;
        }
        return false;
    }
    int_2d BFS(const int begin, const int end, const int_2d& adjc) {
        int_2d node_set(adjc.size()+1);
        vector<bool> visited(adjc.size()+1, false);
        list<int> nodeNums(1, begin);
        visited[begin] = true;
        int levelsize = 1;
        bool found = false;
        while (!found) {
            while (levelsize--) {
                int nodeNum = nodeNums.front();
                nodeNums.pop_front();
                for (int i = 0; i < adjc[nodeNum].size(); i++) {
                    int next_mov = adjc[nodeNum][i];
                    if (!visited[next_mov]) {
                        nodeNums.push_back(next_mov);
                        node_set[next_mov].push_back(nodeNum);
                    }
                    if (next_mov == end)
                        found = true;
                }
            }
            nodeNums.unique();
            levelsize = nodeNums.size();
            for (list<int>::iterator it = nodeNums.begin(); it != nodeNums.end(); it++)
                visited[*it] = true;
        }

        int_2d res;
        vector<int> eve_res(1, end);
        getAnswer(node_set, res, eve_res, end, begin);
        return res;
    }
    void getAnswer(int_2d& node_set, int_2d& res, vector<int>& eve_res, int begin, int end) {
        if (begin == end) {
            res.push_back(eve_res);
            return;
        }
        int next_mov;
        for (int i = 0; i < node_set[begin].size(); i++) {
            next_mov = node_set[begin][i];
            eve_res.push_back(next_mov);
            getAnswer(node_set, res, eve_res, next_mov, end);
            eve_res.pop_back();
        }
    }
public:
    vector<vector<string>> findLadders(string beginWord, string endWord, vector<string>& wordList) {
        int_2d adjc(wordList.size());
        wordList.push_back(beginWord);
        int beginWordNum = wordList.size() - 1;
        int endWordNum;

        for (int i = 0; i < wordList.size() - 1; i++) {
            if (endWord == wordList[i]) endWordNum = i;
            for (int j = 0; j < wordList.size(); j++)
                if (isAdjc(wordList[i], wordList[j]))
                    adjc[i].push_back(j);
        } // 生成邻接表

        int_2d res_numform = BFS(endWordNum, beginWordNum, adjc);
        vector<vector<string> > res(res_numform.size());
        for (int i = 0; i < res_numform.size(); i++)
            for (int j = 0; j < res_numform[i].size(); j++)
                res[i].push_back(wordList[res_numform[i][j]]);
        wordList.pop_back();
        return res;
    }
};

最后,算法复杂度的分析,顶点数V,边数E

  • 时间复杂度上
    BFS过程每个节点都只向下遍历一次,因而是O(V)
    DFS来getAnswer的过程,两点之间的最短路径数必然是个常数a,每个节点最多被访问a次,因而仍然是O(V)
    生成邻接表的过程两层循环了V次,因而是O(V^2)
    可以看出如果抛开生产呢个邻接表这个过程,时间复杂度是线性的

  • 空间复杂度
    邻接表O(V+E),list形式的队列O(V),visited数组O(V)
    生成答案的前驱向量数组O(V+E)
    总体上来说空间复杂度O(V+E),也是令人满意的

这个题解过程贯穿了BFS的思想,穿插了DFS算法,总体上复杂度是非常令人满意的

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值