单词演变
在字典(单词列表) wordList 中,从单词 beginWord 和 endWord 的 转换序列 是一个按下述规格形成的序列:
序列中第一个单词是 beginWord 。
序列中最后一个单词是 endWord 。
每次转换只能改变一个字母。
转换过程中的中间单词必须是字典 wordList 中的单词。
给定两个长度相同但内容不同的单词 beginWord 和 endWord 和一个字典 wordList ,找到从 beginWord 到 endWord 的 最短转换序列 中的 单词数目 。如果不存在这样的转换序列,返回 0。
- 从beginWord开始bfs每层res++,bfs本身就是寻找最短路的过程
class Solution {
public:
void getNextWolds(unordered_set<string>& visited,string& currentWord,queue<string>& q1){
for(int i=0;i!=currentWord.size();i++){
// 挨个换字母匹配 回溯的思想
char temp = currentWord[i];
for(char ch='a';ch<='z';ch++){
currentWord[i] = ch;
if(ch !=temp && visited.count(currentWord)){
q1.emplace(currentWord);
}
}
currentWord[i] = temp;
}
}
int ladderLength(string beginWord, string endWord, vector<string>& wordList) {
unordered_set<string> visited;
// 建表
for(auto word:wordList){
visited.emplace(word);
}
// 查endWord
if(!visited.count(endWord)){
return 0;
}
queue<string> q1;
q1.emplace(beginWord);
int res = 0;
while(!q1.empty()){
// bfs
int size = q1.size();
res++;
while(size--){
string currentWord = q1.front();
q1.pop();
visited.erase(currentWord); //删除元素防止重复转换,bfs本身就是求最小路径的过程
if(currentWord == endWord){
return res;
}
getNextWolds(visited,currentWord,q1);
}
}
return 0;
}
};
改进:
- 因为是单一起点、单一目标节点的最短距离问题。可以采用双向bfs。一棵树的增长比较慢因为每一层的都越来越大,但如果两棵树同时扩展就会快一点。
- 每次遍历层size比较小的那数,如果在另一颗树找到了,就说明找打了最短路
class Solution {
public:
bool getNeighbor(unordered_set<string>& visited, unordered_set<string>& st1, unordered_set<string>& st2, unordered_set<string>& st3, string& word) {
for (int i = 0; i < word.size(); ++i) {
char temp = word[i];
for (char ch = 'a'; ch <= 'z'; ++ch) {
word[i] = ch;
if (ch != temp && visited.count(word)) {
if (st2.count(word)) {
return true; // 在另一个bfs层中找到相同的单词,说明已经串成一条路
}
st3.insert(word);
}
}
word[i] = temp;
}
return false;
}
int ladderLength(string beginWord, string endWord, vector<string>& wordList) {
unordered_set<string> visited;
// 建表
for(auto word:wordList){
visited.emplace(word);
}
// 查endWord
if(!visited.count(endWord)){
return 0;
}
unordered_set<string> st1;
unordered_set<string> st2;
st1.emplace(beginWord);
st2.emplace(endWord);
int res = 2;
while(!st1.empty() && !st2.empty()){
// 每次bfs较小的
if(st1.size()>st2.size()){
swap(st1,st2);
}
// bfs
unordered_set<string> st3;
for(auto it=st1.begin(); it!=st1.end(); it++){
string currentWord = *it;
visited.erase(currentWord); //删除元素防止重复转换,bfs本身就是求最小路径的过程
if (getNeighbor(visited, st1, st2, st3, currentWord)) {
return res;
}
}
// 修改层
st1 = st3;
res++;
}
return 0;
}
};
开密码锁
一个密码锁由 4 个环形拨轮组成,每个拨轮都有 10 个数字: ‘0’, ‘1’, ‘2’, ‘3’, ‘4’, ‘5’, ‘6’, ‘7’, ‘8’, ‘9’ 。每个拨轮可以自由旋转:例如把 ‘9’ 变为 ‘0’,‘0’ 变为 ‘9’ 。每次旋转都只能旋转一个拨轮的一位数字。
锁的初始数字为 ‘0000’ ,一个代表四个拨轮的数字的字符串。
列表 deadends 包含了一组死亡数字,一旦拨轮的数字和列表里的任何一个元素相同,这个锁将会被永久锁定,无法再被旋转。
字符串 target 代表可以解锁的数字,请给出解锁需要的最小旋转次数,如果无论如何不能解锁,返回 -1 。
- bfs 每次转一个,加了死亡数组和不走回头路的条件
- bfs套路都差不多,就是写的时候小心点
- 回溯思想有continu和break的别忘了恢复初值,bug找了半天
class Solution {
public:
int openLock(vector<string>& deadends, string target) {
unordered_set<string> st_deadends(deadends.begin(),deadends.end());
queue<string> q1;
string start = "0000";
if(st_deadends.count(start)){
return -1;
}else if(start == target){
return 0;
}
q1.emplace(start);
st_deadends.emplace(start);
int res = 0;
while(!q1.empty()){
int size = q1.size();
res++;
while(size--){
string currentNum = q1.front();
q1.pop();
// 转动一次的所有情况
//cout<<"开始转动一次"<<currentNum<<endl;
for(int i=0;i!=4;++i){
//cout<<"选择转动的是第"<<i<<"个数"<<endl;
char temp = currentNum[i];
for(int j=-1;j<=1;j+=2){
currentNum[i] = (temp-'0'+j+10)%10+'0';
// cout<<"修改后的值是"<<currentNum<<endl;
if(st_deadends.count(currentNum)){
//cout<<"不满足条件"<<currentNum<<endl;
currentNum[i] = temp;// 回溯思想有continu和break的别忘了恢复初值,bug找了半天
continue;
}
if(currentNum == target){
//cout<<"得到结果"<<currentNum<<endl;
return res;
}
q1.emplace(currentNum);
//cout<<"添加这个数"<<currentNum<<endl;
st_deadends.emplace(currentNum); // 不走回头路
currentNum[i] = temp;
//cout<<"还原这个数"<<currentNum<<endl;
}
}
}
}
return -1;
}
};