thuyduong2007
Member
Theo t thì bài này nó quan trọng lúc xử lý để build lên cái graph thôi. Cách build graph của bạn hiện tại chưa tối ưu. Độ phức tạp hiện tại của bạn là O(n*n*len(beginWord))Bài word-ladder mình dùng graph với BFS mà sao nó chỉ nhanh hơn 20% thôi nhỉ, Bác xem giúp có thể tối ưu thêm đoạn nào ko nhỉ?
C++:#include <queue> #include <list> class Solution { public: struct Node { std::string str; int value = -1; bool visited = false; std::list<Node*> adj; }; bool match(const string& str1, const string& str2) { int count = 0; for(int i = 0; i < str1.size(); ++i) { if(str1[i] != str2[i]) count++; } return count <= 1; } int ladderLength(string beginWord, string endWord, vector<string>& wordList) { std::list<Node*> list; Node* begin = new Node; begin->str = beginWord; begin->value = -1; begin->visited = false; list.push_back(begin); for(const auto& iter : wordList) { if(iter == beginWord) continue; Node* node = new Node; node->str = iter; node->value = -1; node->visited = false; list.push_back(node); } for(const auto& iter : list) { for(const auto& iterInner : list) { if(iter != iterInner && match(iter->str, iterInner->str)) { iter->adj.push_back(iterInner); } } } std::queue<Node*> queue; begin->value = 1; queue.push(begin); while(!queue.empty()) { Node* node = queue.front(); queue.pop(); if(node->visited) continue; node->visited = true; for(auto& iter : node->adj) { if(iter->visited) continue; if(iter->value == -1) { iter->value = node->value + 1; } else { iter->value = std::min(iter->value, node->value + 1); } queue.push(iter); } } int nRet = 0; for(auto& iter : list) { if(iter->str == endWord) { nRet = iter->value; break; } } for(auto& iter : list) { if(iter) { delete iter; iter = nullptr; } } list.clear(); return nRet < 0 ? 0 : nRet; } };
Ý tưởng để build graph của t là sẽ dùng một bảng hash, để lưu các từ trong wordList có thể transfom qua lại. Bằng cách lần lượt replace các chữ cái trong từng word xuất hiện trong wordList bằng một chữ Z (do chữ Z k nằm trong wordList). Cụ thể như sau.
vd:
hot => ["Zot" : ["hot"], "hZt" : ["hot"], "hoZ" : ["hot"], ]
dot => ["Zot" : ["hot", "dot"], "hZt" : ["hot"], "hoZ" : ["hot"], "dZt" : ["dot"], "doZ" : ["dot"]]
đến đây => hot, dot có thể transform qua lại bằng cách thay đổi 1 chữ cái.
Ngoài ra graph thì mình sẽ lưu index của word nằm trong wordList. Điều này giúp giảm memory cũng như thời gian chạy. Do khi dùng bfs thì điều kiện để kết thúc thay vì phải so sánh 2 chuỗi thì chỉ còn là so sánh 2 số.
C++:
class Solution {
public:
int ladderLength(string beginWord, string endWord, vector<string>& wordList) {
int beginIdx = -1, endIdx = -1;
for (int i = 0; i < wordList.size(); i++){
if (wordList[i] == beginWord){
beginIdx = i;
} else if (wordList[i] == endWord){
endIdx = i;
}
}
if (endIdx < 0) return 0;
if (beginIdx < 0){
wordList.push_back(beginWord);
beginIdx = wordList.size()-1;
}
unordered_map<int,vector<int>> table(wordList.size());
buildGraph(wordList, table);
vector<int> myQueue;
myQueue.push_back(beginIdx);
int numTrans = 1;
while(!myQueue.empty()) {
numTrans++;
vector<int> tmpQueue;
for (auto fromIdx : myQueue) {
vector<int>& vec = table[fromIdx];
for (auto toIdx : vec){
if (toIdx == endIdx) return numTrans;
tmpQueue.push_back(toIdx);
}
vec.clear(); //invalid the visted node
}
tmpQueue.swap(myQueue);
}
return 0;
}
void buildGraph(const vector<string>& wordList,
unordered_map<int,vector<int>> &table){
unordered_map<string,vector<int>> tmpTable(wordList.size()*wordList.front().size());
int len = wordList.front().size();
for (int i = 0; i < wordList.size(); ++i){
for (int j = 0; j < len; ++j){
auto tmp = wordList[i];
tmp[j] = 'Z';
tmpTable[tmp].push_back(i);
}
}
for (auto x : tmpTable){
for (int i = 0; i < x.second.size(); ++i) {
for (int j = 0; j < x.second.size(); ++j) {
if (i == j) continue;
table[x.second[i]].push_back(x.second[j]);
}
}
}
}
};
Độ phức tạp bài này nằm ở chỗ build graph: O(n*len(beginWord)*len(beginWord)).
Mà bài này lúc làm bài test t cũng không làm kịp.


.