Description
Difficulty: 4.0/5.0
URL: https://oj.leetcode.com/problems/word-ladder-ii/
Solution
We have to use DFS to find all shortest paths. One possible pruning is that we use earliest_visit[str]
to record the currently earlist time to reach str
for each valid word str
in the dictionary. If we reach str
later than this time, we prune it; if we reach earlier, then we update it with the earlier time. However, this solution is still TLE.
class Solution {
public:
vector<vector<string>> findLadders(string s1, string s2, unordered_set<string> &dict) {
vector<vector<string>>result;
vector<string>cur_strvec;
unordered_map<string, int> earliest_visit;
for (auto &str : dict)
earliest_visit[str] = INT_MAX;
int shortest_len = INT_MAX;
findLaddersHelper(shortest_len, dict, s1, s2, result, cur_strvec, earliest_visit);
return result;
}
void findLaddersHelper(int& shortest_len, unordered_set<string>&dict, string& s1, string& s2, vector<vector<string>>&result, vector<string>&cur_strvec, unordered_map<string, int>& earliest_visit){
if (!dict.count(s1) || cur_strvec.size() == shortest_len)//invalid word or reach the shortest length
return;
if (cur_strvec.size() >= earliest_visit[s1])
return;
earliest_visit[s1] = cur_strvec.size() + 1;
cur_strvec.push_back(s1);
if (s1 == s2){ // target!
if (cur_strvec.size() < shortest_len ){ //shorter length!
result.clear();
shortest_len = cur_strvec.size();
}
result.push_back(cur_strvec);
cur_strvec.pop_back();
return;
}
//find next word in the path
for (int i = 0, str_len = s1.size(); i < str_len; ++ i){
for (char c = 'a'; c <= 'z'; ++ c){
if (c == s1[i]) continue;
char tpc = s1[i]; s1[i] = c;
findLaddersHelper(shortest_len, dict, s1, s2, result, cur_strvec, earliest_visit);
s1[i] = tpc;
}
}
cur_strvec.pop_back();
}
};
Here we add a new step to avoid redundant computation, which is
- precompute the (distance-1) adjacency of words in the dictionary
What is really annoying is that the time limit is really tight. If we use the function constructAdjList_back
to compute the adj_list, we still get TLE. With function constructAdjList
, we can get it accepted in 1220s, which is still slow.
class Solution {
public:
vector<vector<string>> findLadders(string s1, string s2, unordered_set<string> &dict) {
vector<vector<string>>result;
unordered_map<string, vector<string>> adj_list;
unordered_map<string, int> earliest_visit;
for (auto &str : dict)
earliest_visit[str] = INT_MAX;
constructAdjList(adj_list, dict);
int shortest_len = findShortestLen(s1, s2, adj_list, earliest_visit);
vector<string>cur_path;
findShortestPaths(shortest_len, s1, s2, result, cur_path, adj_list, earliest_visit);
return result;
}
bool isAdj(string &s1, string &s2){
int n_diff = 0;
for (int i = 0, str_len = s1.size(); i < str_len; ++ i){
if (s1[i] != s2[i]) n_diff ++;
if (n_diff == 2) return false;
}
return (n_diff == 1);
}
void constructAdjList_back(unordered_map<string, vector<string>>& adj_list, unordered_set<string> & dict){
vector<string>str_list;
for (const auto & it : dict) // nlogn
str_list.push_back(it);
for (auto it1 = str_list.begin(); it1 < str_list.end(); ++ it1){
auto it2 = it1;
for (++it2; it2 < str_list.end(); ++ it2){
if (isAdj(*it1, *it2)){
adj_list[*it1].push_back(*it2);
adj_list[*it2].push_back(*it1);
}
}
}
}
void constructAdjList(unordered_map<string, vector<string>>& adj_list, unordered_set<string> & dict){
for (auto str : dict){
for (int i = 0, str_len = str.length(); i < str_len; ++ i){
string tp_str(str);
for (char c = 'a'; c <= 'z'; ++ c){
if (c == str[i]) continue;
tp_str[i]= c;
if (dict.count(tp_str))
adj_list[str].push_back(tp_str);
}
}
}
}
void findShortestPaths(int shortest_len, const string & s1, const string & s2, vector<vector<string>>&result, vector<string>&cur_path, unordered_map<string, vector<string>>& adj_list, unordered_map<string, int> &earliest_visit){
if (cur_path.size() >= earliest_visit[s1])
return;
earliest_visit[s1] = cur_path.size()+1;
if (cur_path.size() == shortest_len)
return;
cur_path.push_back(s1);
if (s1 == s2){
result.push_back(cur_path);
cur_path.pop_back();
return;
}
for (const auto &next_str : adj_list[s1])
findShortestPaths(shortest_len, next_str, s2, result, cur_path, adj_list, earliest_visit);
cur_path.pop_back();
}
int findShortestLen(string& s1, string& s2, unordered_map<string, vector<string>>& adj_list, unordered_map<string, int>& earliest_visit){
int str_len = s1.length();
queue<pair<string,int>>bfs_que;
unordered_set<string>visited;
visited.insert(s1);
bfs_que.push(pair<string,int>(s1, 0));
while(!bfs_que.empty()){
pair<string, int> candi = bfs_que.front();
bfs_que.pop();
earliest_visit[candi.first] = candi.second+1;
if (candi.first == s2)
return candi.second+1;
for (const auto& next_str : adj_list[candi.first]){
pair<string, int> new_candi(next_str, candi.second+1);
if (visited.count(next_str))
continue;
bfs_que.push(new_candi);
visited.insert(new_candi.first);
}
}
return INT_MAX;
}
};