-
Notifications
You must be signed in to change notification settings - Fork 0
127. Word Ladder #21
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
127. Word Ladder #21
Conversation
| - 過去に解いたことあり | ||
| - グラフを構築して、各エッジの重みが1の単一始点最短経路問題に落とし込む | ||
| - BFS | ||
| - time: O(mn), space: O(mn) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
1stではword_list_set.findしているのでtime: O(m * n * log(m))ではないでしょうか
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
コメントありがとうございます。C++のunordered_mapは中身がHashMapなので、検索は average O(1) で考えてます。
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
これhashMapなのですね、すみません...
| string word = move(word_to_visit.front()); | ||
| word_to_visit.pop(); | ||
| for (int i = 0; i < word.size(); i++) { | ||
| string next_word = word; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
これってwordの文字ごとにwordのコピー発生してますかね?
もしそうなら時間計算量O(mn^2)になる気がしました
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
あ、これコピー発生しているので、O(mn^2)になりますね。気付きませんでした、ありがとうございます!
| while (!frontier.empty()) { | ||
| auto [distance, word] = frontier.top(); | ||
| frontier.pop(); | ||
| if (distance > word_to_distance[word]) continue; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
[質問]
ここでcontinueするケースってありますか? (あるならどんなときですか?)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
一般に、|E| > |V|の際に計算量をO(|E| log |E|) -> O(|E| log |V|) へと落とすためにこの枝狩りを入れていました。
しかし、よく考えるとこの問題ではグラフにサイクルがあると仮定すると矛盾が導けるので構造としては森になり、この枝狩りは必要ないように思いました。
あとこのpriority_queueは実質queueと同じ働きをしていることに今気付きました。
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
あまり理解できてないので、変なこと言っていたらすみません (スルーでも大丈夫です)。
一般に、|E| > |V|の際に計算量をO(|E| log |E|) -> O(|E| log |V|) へと落とすためにこの枝狩りを入れていました。
一般のダイクストラで二分ヒープを用いるときの計算量は
しかし、よく考えるとこの問題ではグラフにサイクルがあると仮定すると矛盾が導けるので構造としては森になり、この枝狩りは必要ないように思いました。
wordがノードのラベル、word同士の編集距離が1のノード間にエッジが張られるとすると、たとえば cat, cut, cot のノード間は全てエッジが貼られるのでサイクルになりそう。ただ distance + 1 < word_to_distance[next_word] の条件でbeginWordからの最短でないpathは通らないようにしている、という話なのかと思ってました。
あとこのpriority_queueは実質queueと同じ働きをしていることに今気付きました。
エッジの重みがすべて等しいので実質幅優先、ということですかね。
前提が違ったり解釈が間違っていたらすみません。
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
wordがノードのラベル、word同士の編集距離が1のノード間にエッジが張られるとすると、たとえば cat, cut, cot のノード間は全てエッジが貼られるのでサイクルになりそう。
たしかに同じ位置を変えればサイクル作れますね。勘違いしていました、失礼しました。
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
一般のダイクストラで二分ヒープを用いるときの計算量は
$O((|V|+|E|)\log(|V|))$ じゃないですか?
ダイクストラの計算量についてですが、ちょっと整理してみます。自分もあまり自信ないため間違っているところがあれば指摘して欲しいです。
この形の実装では、計算量は O(E log V) と考えてます。
理由ですが、距離が確定した頂点に対して合計E回エッジの緩和操作をしており、同じノードが複数回入る場合もあるので、
heappushに関しては最悪の場合 O(E log E)となりそうです。また、pqの中が空になるまで回してるので、同じくheappopも最悪 O(E log E)となりそうです。
単純グラフの場合、エッジの数はV^2で上から抑えられるので、O(E log E) = O(2E log V) = O(E log V)となると思います。
wikipediaにのっている O((V + E) log V)の実装は、ヒープに対してdecrease-key操作を使ったまた違う実装ではないかと思ってます。https://en.wikipedia.org/wiki/Dijkstra%27s_algorithm
同じノードに対して、pqに追加する代わりに、updateをかけるようにすればpqの中にノードの重複がなくなり、このような計算量になると思います。
自分の知っている範囲では、C++のpriority_queueやPythonのheapqでこの操作はサポートされていないように思います。
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
エッジの重みがすべて等しいので実質幅優先、ということですかね。
そうですね、priority_queueのところをqueueに書き換えても動いたので、
幅優先と全く同じ動きをしていると思いました。
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
単純グラフの場合、エッジの数はV^2で上から抑えられるので、O(E log E) = O(2E log V) = O(E log V)となると思います。
たしかに。単純グラフであればそうなりますね
wikipediaにのっている O((V + E) log V)の実装は、ヒープに対してdecrease-key操作を使ったまた違う実装ではないかと思ってます。https://en.wikipedia.org/wiki/Dijkstra%27s_algorithm
同じノードに対して、pqに追加する代わりに、updateをかけるようにすればpqの中にノードの重複がなくなり、このような計算量になると思います。
うーむなるほど...wikipediaの記事見てました。自信はないですが正しそうな気がします。
| if (word_list_set.find(next_word) == word_list_set.end()) continue; | ||
| if (word_to_distance[next_word] != -1) continue; | ||
|
|
||
| if (next_word == end_word) return word_to_distance[word] + 1; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
word_to_visitからwordを取ってきたときにend_wordと比較してもいいですね
(returnのタイミングは少し遅れるけど比較の回数は減る)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
たしかに良さそうに思いました。pop時に比較することで考え方もダイクストラと共通化できます。
| queue<string> word_to_visit; | ||
| word_to_visit.emplace(beginWord); | ||
| while (!word_to_visit.empty()) { | ||
| string word = move(word_to_visit.front()); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
move() してから pop() するのは、 C++ の規約的に問題ないのでしょうか…?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
すみません、こちら返信が漏れておりました。
結論から言うと、move() してから pop() するのは問題なさそうです。
参照)
https://stackoverflow.com/questions/61468001/is-it-safe-to-move-from-the-top-front-of-a-stl-stack-queue
https://stackoverflow.com/questions/37173697/is-it-okay-to-move-an-object-from-a-queue-if-youre-about-to-pop-from-it
ただ、例えば priority_queue の top() のように const_reference を返すものに対しては期待した動作が行われないため注意が必要そうです。priority_queue の operator= を見ると const priority_queue&& を引数に取るものはないので、この書き方をするとおそらくコピー代入演算子の方が呼び出されると思います。https://en.cppreference.com/w/cpp/container/priority_queue/operator%3D
| } | ||
| word_to_distance[beginWord] = 1; | ||
|
|
||
| priority_queue<pair<int, string>, vector<pair<int, string>>, greater<pair<int, string>>> frontier; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
pair は、中にどのような値が含まれているのか認識するのが難しく、読んでいて認知負荷がかかるように思います。面倒ですが、 struct を定義し、 bool operator <() const などを定義し、 priority_queue に入れるとよいと思います。
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
ありがとうございます、そちらの方が確実に可読性高いですよね。怠惰でpairを使っていました。。
| class Solution { | ||
| public: | ||
| int ladderLength(string begin_word, string end_word, vector<string>& word_list) { | ||
| unordered_set<string> word_list_set(word_list.begin(), word_list.end()); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
string_viewを見てみてもいいかもしれません。
https://en.cppreference.com/w/cpp/string/basic_string_view
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
ありがとうございます。string_view知らなかったです。
不要なコピーをもう少し減らせそうです。
問題へのリンク
https://leetcode.com/problems/word-ladder/description/
次に解く問題
104. Maximum Depth of Binary Tree
README.mdへ頭の中の言語化と記録をしています。