title: word ladder总结

categories: LeetCode

tags:

  • 算法
  • LeetCode

    comments: true

    date: 2016-10-16 09:42:30

题意

Given two words (beginWord and endWord), and a dictionary's word list, find the length of shortest transformation sequence from beginWord to endWord, such that:

Only one letter can be changed at a time

Each intermediate word must exist in the word list

For example,

Given:

beginWord = "hit"

endWord = "cog"

wordList = ["hot","dot","dog","lot","log"]

As one shortest transformation is "hit" -> "hot" -> "dot" -> "dog" -> "cog",

return its length 5.

Note:

Return 0 if there is no such transformation sequence.

All words have the same length.

All words contain only lowercase alphabetic characters.

思路

因为上一个词到下一个词之间,变化的只是一个字母,因此只需遍历所有字母,进行替换即可;

利用BFS的思想,将改变后的字符串存进队列中,进行下一次的循环。

题解

这个的题解有很多种,但是其实都是使用了最基本的BFS思想;

交换队列,计算层数

int ladderLength(string begin, string end, unordered_set<string>& wordList) {
// 当前层,下一层
queue<string> current, next;
// 判重
unordered_set<string> visited;
// 层次
int level = 0;
bool found = false; // 有点像内联函数
auto state_is_target = [&](const string &s) {
return s == end;
};
auto state_extend = [&](const string &s) {
vector<string> result; for (size_t i = 0; i < s.size(); ++i) {
string new_word(s);
for (char c = 'a'; c <= 'z'; c++) {
if (c == new_word[i]) continue;
swap(c, new_word[i]);
if (wordList.count(new_word) > 0 && !visited.count(new_word)) {
result.push_back(new_word);
visited.insert(new_word);
}
swap(c, new_word[i]);
}
} return result;
}; current.push(begin); while (!current.empty() && !found) {
++level;
while (!current.empty() && !found) {
const string str = current.front();
current.pop(); const auto& new_states = state_extend(str);
for (const auto& state : new_states) {
next.push(state);
if (state_is_target(state)) {
//找到
found = true;
break;
}
}
}
swap(next, current);
} if (found) return level + 1;
else return 0;
}

总结:

使用了匿名函数,其中命名的state_is_target,state_extend是函数模版,分别做的操作就是判断字符串是否相等,返回字符串BFS遍历的下一层的数组;

同时用了两层循环,外循环用于计算层数和交换队列,内循环用于计算找到结尾字符串;

一个队列

/**
* 改变单词中的字母, 查看是否存在
*
* @param word <#word description#>
* @param wordDict <#wordDict description#>
* @param toVisit <#toVisit description#>
*/
void addNextWords(string word, unordered_set<string>& wordDict, queue<string>& toVisit) {
// 删除wordDict中的word,因为相同的没有用
wordDict.erase(word); for (int i = 0; i < (int)word.length(); i++) {
// 记录下word的字母
char letter = word[i];
// 变换word中每个字母,一个一个字母的查找是否匹配,因为只能替换一个字母
for (int j = 0; j < 26; j++) {
word[i] = 'a' + j;
// 找到后加入访问过的队列,并从wordDict中删除
if (wordDict.find(word) != wordDict.end()) {
toVisit.push(word);
wordDict.erase(word);
}
}
// 变换回去
word[i] = letter;
}
}
/**
* 使用BFS
*
* @param beginWord <#beginWord description#>
* @param endWord <#endWord description#>
* @param wordDict <#wordDict description#>
*
* @return <#return value description#>
*/
int ladderLength2(string beginWord, string endWord, unordered_set<string>& wordDict) {
// 加载最后的单词
wordDict.insert(endWord);
queue<string> toVisit;
addNextWords(beginWord, wordDict, toVisit); // 从2开始,是因为需要经历开始和结尾的两个字母
int dist = 2;
// toVisit中存储的是通过改变一个字母就可以到达wordDict中存在的字符串
while (!toVisit.empty()) {
int num = (int)toVisit.size();
for (int i = 0; i < num; i++) {
string word = toVisit.front();
toVisit.pop();
if (word == endWord) return dist;
addNextWords(word, wordDict, toVisit);
}
dist++;
} return 0;
}

总结:

同样是计算BFS的层数;

两个指针

/**
* 使用两个指针和BFS
*
* @param beginWord <#beginWord description#>
* @param endWord <#endWord description#>
* @param wordDict <#wordDict description#>
*
* @return <#return value description#>
*/
int ladderLength3(string beginWord, string endWord, unordered_set<string>& wordDict) {
unordered_set<string> head, tail, *phead, *ptail;
head.insert(beginWord);
tail.insert(endWord); int dist = 2;
// 将phead指向更小size的unordered_set以便减少运行时间
while (!head.empty() && !tail.empty()) {
if (head.size() < tail.size()) {
phead = &head;
ptail = &tail;
}
else {
phead = &tail;
ptail = &head;
}
unordered_set<string> temp;
for (auto iterator = phead->begin(); iterator != phead->end(); iterator++) {
string word = *iterator;
wordDict.erase(word); // 原理和上面一样,更换其中的字符,
for (int i = 0; i < (int)word.length(); i++) {
char letter = word[i];
for (int k = 0; k < 26; k++) {
word[i] = 'a' + k;
if (ptail->find(word) != ptail->end())
return dist;
if (wordDict.find(word) != wordDict.end()) {
temp.insert(word);
wordDict.erase(word);
}
}
word[i] = letter;
}
}
dist++;
swap(*phead, temp);
}
return 0;
}

总结:

两个指针指向了unordered_set的head和tail,其做法和第一种方法是相通的,只不过一个用了unordered_set,同时在循环前面还加上了根据数组大小判断指针指向不同的数组;

使用类

class WordNode {
public:
string word;
int numSteps; public:
WordNode(string w, int n) {
this->word = w;
this->numSteps = n;
}
};
/**
* 使用类去处理记录数据,和前面的思想是类似的
*
* @param beginWord <#beginWord description#>
* @param endWord <#endWord description#>
* @param wordDict <#wordDict description#>
*
* @return <#return value description#>
*/
int ladderLength4(string beginWord, string endWord, unordered_set<string>& wordDict) {
queue<WordNode*> queue;
queue.push(new WordNode(beginWord, 1)); wordDict.insert(endWord); while (!queue.empty()) {
WordNode * top = queue.front();
queue.pop(); string word = top->word;
if (word == endWord) {
return top->numSteps;
} for (int i = 0; i < word.length(); i++) {
char temp = word[i]; //先记录后面还有改回来
for (char c = 'a'; c <= 'z'; c++) {
if (temp != c) {
word[i] = c;
} if (wordDict.find(word) != wordDict.end()) {
queue.push(new WordNode(word, top->numSteps + 1));
wordDict.erase(word);
} }
word[i] = temp;
}
}
return 0;
}

总结:

使用类去记录每个字符串的单前遍历的层数,本质还是计算层数,只不过用类去保存,这样的好处就是减少了一些计算代码;

题意

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 intermediate word must exist in the word list

For example,

Given:

beginWord = "hit"

endWord = "cog"

wordList = ["hot","dot","dog","lot","log"]

Return

[

["hit","hot","dot","dog","cog"],

["hit","hot","lot","log","cog"]

]

Note:

All words have the same length.

All words contain only lowercase alphabetic characters.

思路

肯定需要两个数组,一个用来存储当前已经存在的字符串,一个用来存放遍历结果的字符串,最后两者进行交换,直到遍历当前数组为空;还有一个重点在于可以利用邻接矩阵的思想,用map存储字符串和数组一一对应关系,最后根据顺序进行输出map即可;

代码

vector<string> temp_path;
vector<vector<string>> result_path; /**
* 生成路径(从后往前)
*
* @param unordered_map<string <#unordered_map<string description#>
* @param path <#path description#>
* @param start <#start description#>
* @param end <#end description#>
*/
void generatePath(unordered_map<string, unordered_set<string>>& path, const string& start, const string& end) {
temp_path.push_back(start); if (start == end) {
vector<string> ret = temp_path;
reverse(ret.begin(), ret.end());
result_path.push_back(ret);
return ;
} for (auto it = path[start].begin(); it != path[start].end(); it++) {
generatePath(path, *it, end);
temp_path.pop_back();
}
} /**
*
*
* @param beginWord <#beginWord description#>
* @param endWord <#endWord description#>
* @param wordList <#wordList description#>
*
* @return <#return value description#>
*/
vector<vector<string>> findLadders(string beginWord, string endWord, unordered_set<string> &wordList) {
temp_path.clear();
result_path.clear(); unordered_set<string> current_step;
unordered_set<string> next_step; unordered_map<string, unordered_set<string>> path; unordered_set<string> unvisited = wordList; // 记录还没有访问过的结点 if (unvisited.count(beginWord) > 0)
unvisited.erase(beginWord); // 未访问过的值增加结尾字符串,开始处加入起点字符串
unvisited.insert(endWord);
current_step.insert(beginWord); while (current_step.count(endWord) == 0 && unvisited.size() > 0) {
// 遍历当前层的所有元素
for (auto cur = current_step.begin(); cur != current_step.end(); cur++) {
string word = *cur;
// 给字符串中的每个字符都替换字母
for (int i = 0; i < beginWord.size(); i++) {
for (int j = 0; j < 26; j++) {
string tmp = word;
// 相等情况下
if (tmp[i] == 'a' + j) {
continue;
} tmp[i] = 'a' + j;
// 表明字典中存在该字符串,可以到达下一步
if (unvisited.count(tmp) > 0) {
next_step.insert(tmp);
path[tmp].insert(word);
}
}
}
} // 表明下一层中已经不存在任何没有访问过的
if (next_step.empty()) {
break;
} // 清除所有没有访问过的值
for (auto it = next_step.begin(); it != next_step.end(); it++) {
unvisited.erase(*it);
} current_step = next_step;
next_step.clear();
} // 存在结果
if (current_step.count(endWord) > 0) {
generatePath(path, endWord, beginWord);
} return result_path;
} /**
* 建立树
*
* @param forward <#forward description#>
* @param backward <#backward description#>
* @param dict <#dict description#>
* @param unordered_map<string <#unordered_map<string description#>
* @param tree <#tree description#>
* @param reversed <#reversed description#>
*
* @return <#return value description#>
*/
bool buildTree(unordered_set<string> &forward, unordered_set<string> &backward, unordered_set<string> &dict, unordered_map<string, vector<string> > &tree, bool reversed)
{
if (forward.empty()) return false;
// 如果开头的长度比结尾长,则从结尾开始找到开头
if (forward.size() > backward.size())
return buildTree(backward, forward, dict, tree, !reversed); // 存在开头和结尾则删除
for (auto &word: forward) dict.erase(word);
for (auto &word: backward) dict.erase(word); unordered_set<string> nextLevel; // 存储树的下一层
bool done = false; //是否进一步搜索
for (auto &it: forward) //从树的当前层进行遍历
{
string word = it;
for (auto &c: word)
{
char c0 = c; //存储初始值
for (c = 'a'; c <= 'z'; ++c)
{
if (c != c0)
{
// 说明找到最终结果
if (backward.count(word))
{
done = true; //找到
!reversed ? tree[it].push_back(word) : tree[word].push_back(it); //keep the tree generation direction consistent;
}
// 说明还没找到最终结果但是在字典中找到
else if (!done && dict.count(word))
{
nextLevel.insert(word);
!reversed ? tree[it].push_back(word) : tree[word].push_back(it);
}
}
}
c = c0; //restore the word;
}
}
return done || buildTree(nextLevel, backward, dict, tree, reversed);
}
/**
* 生成路径
*
* @param beginWord <#beginWord description#>
* @param endWord <#endWord description#>
* @param unordered_map<string <#unordered_map<string description#>
* @param tree <#tree description#>
* @param path <#path description#>
* @param paths <#paths description#>
*/
void getPath(string &beginWord, string &endWord, unordered_map<string, vector<string> > &tree, vector<string> &path, vector<vector<string> > &paths) //using reference can accelerate;
{
if (beginWord == endWord) paths.push_back(path); //till the end;
else
{
for (auto &it: tree[beginWord])
{
path.push_back(it);
getPath(it, endWord, tree, path, paths); //DFS retrieving the path;
path.pop_back();
}
}
} vector<vector<string>> findLadders2(string beginWord, string endWord, unordered_set<string> &dict) {
vector<vector<string> > paths;
vector<string> path(1, beginWord);
if (beginWord == endWord)
{
paths.push_back(path);
return paths;
} // 分别存储开头和结尾
unordered_set<string> forward, backward;
forward.insert(beginWord);
backward.insert(endWord); unordered_map<string, vector<string> > tree;
// 用来判断是从开头找到结尾还是从结尾找到开头,因为会从长度短的一方开始进行查找
bool reversed = false;
if (buildTree(forward, backward, dict, tree, reversed))
getPath(beginWord, endWord, tree, path, paths);
return paths;
} bool test() {
unordered_set<string> words = {"hot","dot","dog","lot","log"};
string start = "hit", end = "cog"; vector<vector<string>> result = findLadders2(start, end, words); for (auto i = 0; i < result.size(); ++i) {
for (auto j = 0; j < result[0].size(); ++j) {
cout << result[i][j] << "->";
}
cout << endl;
}
return true;
}

这两种方法其实是一样的;

word-ladder总结的更多相关文章

  1. [LeetCode] Word Ladder 词语阶梯

    Given two words (beginWord and endWord), and a dictionary, find the length of shortest transformatio ...

  2. [LeetCode] Word Ladder II 词语阶梯之二

    Given two words (start and end), and a dictionary, find all shortest transformation sequence(s) from ...

  3. LeetCode:Word Ladder I II

    其他LeetCode题目欢迎访问:LeetCode结题报告索引 LeetCode:Word Ladder Given two words (start and end), and a dictiona ...

  4. 【leetcode】Word Ladder

    Word Ladder Total Accepted: 24823 Total Submissions: 135014My Submissions Given two words (start and ...

  5. 【leetcode】Word Ladder II

      Word Ladder II Given two words (start and end), and a dictionary, find all shortest transformation ...

  6. 18. Word Ladder && Word Ladder II

    Word Ladder Given two words (start and end), and a dictionary, find the length of shortest transform ...

  7. [Leetcode][JAVA] Word Ladder II

    Given two words (start and end), and a dictionary, find all shortest transformation sequence(s) from ...

  8. LeetCode127:Word Ladder II

    题目: Given two words (start and end), and a dictionary, find all shortest transformation sequence(s) ...

  9. 【LeetCode OJ】Word Ladder II

    Problem Link: http://oj.leetcode.com/problems/word-ladder-ii/ Basically, this problem is same to Wor ...

  10. 【题解】【字符串】【BFS】【Leetcode】Word Ladder

    Given two words (start and end), and a dictionary, find the length of shortest transformation sequen ...

随机推荐

  1. 【网页开发学习】Coursera课程《面向 Web 开发者的 HTML、CSS 与 Javascript》Week1课堂笔记

    Coursera课程<面向 Web 开发者的 HTML.CSS 与 Javascript> Johns Hopkins University Yaakov Chaikin Week1 In ...

  2. ntpdate[35450]: the NTP socket is in use, exiting

    当前主机已是NTP服务器,需关闭当前NTP服务,再同步其他NTP服务器的时间 service ntpd stop 然后ps -ef | grep ntp看进程是否已杀掉 然后再次ntpdate Ser ...

  3. Service(二):通信

    课程:http://www.jikexueyuan.com/course/715_3.html?ss=1 在activity和service之间通信. 首先使用的是启动服务来通信.注意是如何使用Int ...

  4. 洛谷P3367并查集

    传送门 #include <iostream> #include <cstdio> #include <cstring> #include <algorith ...

  5. MySQL基础 - Navicat及HeidiSQL可视化数据库管理工具

    你还在使用终端界面查看数据库吗?是的,用来用去还是觉得命令行好用.....这里先留个位子,改天再介绍下这俩工具的使用,虽然好像觉得没啥需要介绍的.

  6. TCP连接建立与断开

    tcp状态 LISTEN:侦听来自远方的TCP端口的连接请求 LISTEN:侦听来自远方的TCP端口的连接请求 SYN-SENT:再发送连接请求后等待匹配的连接请求 SYN-RECEIVED:再收到和 ...

  7. 在 SQL Server 2005 中配置数据库邮件

    一.            SQL Server发邮件原理和组件介绍: 数据库邮件有4个组件:配置文件.邮件处理组件.可执行文件以及“日志记录和审核组件”. l  配置组件包括: 1)数据库邮件帐户包 ...

  8. 整理一下关于Crypto加密的坑

    之前写接口一般不用加密(做了权限处理),最近公司要求接口加密,我开始了入坑之路 因为公司其他人用的AES和DES加密,我就在网上查了下关于这方面的使用方法. 首先安装Crypto pip instal ...

  9. Jenkins的授权和访问控制

    默认的Jenkins不包含任何的安全检查,任何人可以修改Jenkins设置,job和启动build等.显然地在大规模的公司需要多个部门一起协调工作的时候,没有任何安全检查会带来很多的问题. 在系统管理 ...

  10. 自己动手编译OpenSSL库

    因为工作需要,要实现一个基于SSL的通信程序.之前没有接触过SSL协议通讯,这次学习了一下如何自己编译OpenSSL库. 我使用的环境是Windows 10 + VS2015 1.首先打开VS2015 ...