Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
99 changes: 99 additions & 0 deletions 127/step1.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,99 @@
/*

Solve Time : over 1 hours

Time : O(N^2 * log N)
Space : O(N^2)

単語同士の距離から隣接リストを形成して探索すれば良い、というところまではすぐにたどり着いたが
微妙なミスを連発してしまい、時間がかかってしまった。

priority_queueのデフォルが最大を取り出す仕様を失念していた
最短距離を出す探索方法をしばらく失念しており、間違ったコードを書いていた

*/
class Solution {
public:
int ladderLength(string beginWord, string endWord, vector<string>& wordList) {
bool no_begin = std::none_of(wordList.begin(), wordList.end(), [beginWord](string s) {
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[beginWord] は呼び出しごとに毎回コピーされるのではないでしょうか。

Copy link
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

このあたりの挙動、ちゃんと理解できていなかったので調べました。
ラムダを引数として渡したタイミングでコピーされるので、呼び出しごとにコピーされる心配はなさそうと結論付けました。

初期化子なしのキャプチャに対応するそれらのデータメンバは、ラムダ式が評価されるときに直接初期化されます。

https://ja.cppreference.com/w/cpp/language/lambda#Lambda_capture

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

あ、そうですね。
呼び出しごとに関数呼び出しでコピーされているのは s のほうですか。
ここのキャプチャーは [&beginWord] か [&] くらいで十分なように思いました。

Copy link
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

なるほど、確かに、ありがとうございます。
sは毎回コピーされるので、参照にしたほうが良さそうですね

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

if (std::find(wordList.begin(), wordList.end(), beginWord) == wordList.end()) {
    return 0;
}

のほうがシンプルだと思います。

Copy link
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

その書き方は思いつきませんでした、ありがとうございます。

return s == beginWord;
});
if (no_begin) {
wordList.push_back(beginWord);
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

C++の仕様が分かってないので自信ないのですが、wordListを参照で引数に渡しているので、このコードは入力が破壊される形になってますでしょうか

Copy link
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

はい、入力を変更しています。

条件次第で1単語追加、程度であればまぁ許容するかなというのと
wordListをコピーした別データを用意するのは似たような変数を使用するため読みづらくなる
などを考えて今回はこのようにしています。

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

なるほど、説明くださりありがとうございます。(ProsConsを考えられているのでよさそうです)

}
bool no_end = std::none_of(wordList.begin(), wordList.end(), [endWord](string s) {
return s == endWord;
});
if (no_end) {
return 0;
}
vector<set<int>> adjcent_indexes = vector<set<int>>(wordList.size(), set<int>{});
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

vector<set<int>> adjcent_indexes(wordList.size()); と書いたほうがシンプルだと思いました。 vector のコンストラクターの第 2 引数を省略した場合、デフォルト値で初期化される点にご注意ください。

スペルミスがあるようです。 adjcent→adjacent

Copy link
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

なるほど、ありがとうございます!
個別に初期化するコードが手癖になってました。

for (int i = 0; i < wordList.size(); ++i) {
for (int j = 0; j < wordList.size(); ++j) {
string left = wordList[i];
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

コピーを減らすため、 const string& で受けたほうが良いと思います。 const auto& で受けるのもよいと思います。

Copy link
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ありがとうございます、このあたりコピーを減らす感覚がまだきちんとできていませんでした。

string right = wordList[j];
if (!IsNextWord(left, right)) {
continue;
}
adjcent_indexes[i].insert(j);
adjcent_indexes[j].insert(i);
}
}
int begin_index;
int end_index;
for (int i = 0; i < wordList.size(); ++i) {
if (wordList[i] == beginWord) {
begin_index = i;
}
if (wordList[i] == endWord) {
end_index = i;
}
}
return CountDistance(begin_index, end_index, adjcent_indexes);
}

private:
bool IsNextWord(const string left, const string right) {
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

こちらもコピーを減らすため、 const string& で受けたほうが良いと思います。詳しくは『Effective C++ 第3版』『20項 値渡しよりconst参照渡しを使おう』をご覧ください。
https://www.maruzen-publishing.co.jp/item/b294734.html

Copy link
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ありがとうございます、参考にします。

int count = 0;
for (int i = 0; i < left.size(); ++i) {
if (left[i] != right[i]) {
++count;
}
if (count > 1) {
return false;
}
}
if (count == 0) {
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

return count == 1; と書いたほうがシンプルだと思います。

Copy link
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ありがとうございます。
例外処理を意識しすぎて不要な分岐になってしまっていました。

return false;
}
return true;
}

int CountDistance(int begin_index, int end_index, vector<set<int>>& adjcent_indexes) {
priority_queue<pair<int, int>, vector<pair<int, int>>, greater<pair<int, int>>> next_indexes;
next_indexes.emplace(1, begin_index);
vector<int> distances(adjcent_indexes.size(), std::numeric_limits<int>::max());
distances[begin_index] = 1;
while (!next_indexes.empty()) {
auto [distance, index] = next_indexes.top();
next_indexes.pop();
if (distances[index] < distance) {
continue;
}
if (index == end_index) {
return distance;
}
distances[index] = distance;
for (auto next_index : adjcent_indexes[index]) {
if (distances[next_index] < distance + 1) {
continue;
}
next_indexes.emplace(distance + 1, next_index);
}
}
if (distances[end_index] == std::numeric_limits<int>::max()) {
return 0;
}
return distances[end_index];
}
};
91 changes: 91 additions & 0 deletions 127/step2_1.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,91 @@
/*

Time : O(L * N^2)
Space : O(N^2)

- 全てのノード間の距離が同じなのでpriority_queueは不要で、queueで十分
- 隣接リストを作る際に、i < jの条件を追加して計算量を減らす
- BFSにて、queueが空になったときは到達不能と判断して良いのでif分を削除
*/
class Solution {
public:
int ladderLength(string beginWord, string endWord, vector<string>& wordList) {
bool no_begin = std::none_of(wordList.begin(), wordList.end(), [beginWord](string s) {
return s == beginWord;
});
if (no_begin) {
wordList.push_back(beginWord);
}
bool no_end = std::none_of(wordList.begin(), wordList.end(), [endWord](string s) {
return s == endWord;
});
if (no_end) {
return 0;
}
Comment on lines +19 to +24
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

これ、現代的な書き方ですが、これは for で書いたほうが読みやすくないでしょうか。

Copy link
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ありがとうございます。
使用するメソッドで説明的にできると思いこちらを使いましたが、ちょっと考え直します。

vector<set<int>> adjacent_indexes = vector<set<int>>(wordList.size(), set<int>{});
for (int i = 0; i < wordList.size(); ++i) {
for (int j = i + 1; j < wordList.size(); ++j) {
string left = wordList[i];
string right = wordList[j];
if (!IsNextWord(left, right)) {
Comment on lines +28 to +30
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ここでコピーが4回走りますね。参照でよいでしょう。

Copy link
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ありがとうございます、無駄なコピーをしていました。
適宜参照を使うようにします。

continue;
}
adjacent_indexes[i].insert(j);
adjacent_indexes[j].insert(i);
}
}
int begin_index;
int end_index;
for (int i = 0; i < wordList.size(); ++i) {
if (wordList[i] == beginWord) {
begin_index = i;
}
if (wordList[i] == endWord) {
end_index = i;
}
}
return CountDistance(begin_index, end_index, adjacent_indexes);
}

private:
bool IsNextWord(const string left, const string right) {
int count = 0;
for (int i = 0; i < left.size(); ++i) {
if (left[i] != right[i]) {
++count;
}
if (count > 1) {
return false;
}
}
if (count == 0) {
return false;
}
return true;
}

int CountDistance(int begin_index, int end_index, vector<set<int>>& adjacent_indexes) {
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

distanceというと、ここで求めているものから1を引いたものを一般に想像してしまいそうです。(グラフにした時の距離の定義もそうだと思います)

Copy link
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ありがとうございます、確かに混乱を生む語彙でした

queue<pair<int, int>> next_indexes;
next_indexes.emplace(1, begin_index);
vector<int> distances(adjacent_indexes.size(), std::numeric_limits<int>::max());
distances[begin_index] = 1;
while (!next_indexes.empty()) {
auto [distance, index] = next_indexes.front();
next_indexes.pop();
if (distances[index] < distance) {
continue;
}
if (index == end_index) {
return distance;
}
distances[index] = distance;
for (auto next_index : adjacent_indexes[index]) {
if (distances[next_index] < distance + 1) {
continue;
}
next_indexes.emplace(distance + 1, next_index);
}
}
return 0;
}
};
90 changes: 90 additions & 0 deletions 127/step2_2.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,90 @@
/*
Copy link

@liquo-rice liquo-rice Jan 19, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

参考程度に私が書いたものを貼っておきます。

class Solution {
public:
    int ladderLength(string beginWord, string endWord, vector<string>& wordList) {
        unordered_set<string_view> words(wordList.begin(), wordList.end());
        if (!words.contains(endWord)) {
            return 0;
        }
        queue<string_view> que({beginWord});
        auto push_adjacent_words = [&](string word) {
            for (int i = 0; i < word.size(); ++i) {
                char orig = word[i];
                for (char c = 'a'; c <= 'z'; ++c) {
                    word[i] = c;
                    auto it = words.find(word);
                    if (it != words.end()) {
                        que.push(*it);
                        words.erase(it);
                    }
                }
                word[i] = orig;
            }
        };
        for (int length = 1; !que.empty(); ++length) {
            for (int size = que.size(); size; --size) {
                string_view word = que.front();
                que.pop();
                if (word == endWord) {
                    return length;
                }
                push_adjacent_words(string(word));
            }
        }
        return 0;
    }
};


Time : O(N * L * log N)
Space : O(N^2)

他の人の解法を参考に改良したもの
隣接リストの形成を改善、単語のリストを二重ループで探索するのではなく、各単語を1文字ずつa - zに変化させて、wordList内に一致する単語の有無を確認。

*/
class Solution {
public:
int ladderLength(string beginWord, string endWord, vector<string>& wordList) {
bool no_end = std::none_of(wordList.begin(), wordList.end(), [endWord](string s) {
return s == endWord;
});
if (no_end) {
return 0;
}
bool no_begin = std::none_of(wordList.begin(), wordList.end(), [beginWord](string s) {
return s == beginWord;
});
if (no_begin) {
wordList.push_back(beginWord);
}
vector<set<int>> adjacent_indexes = vector<set<int>>(wordList.size(), set<int>{});
ComposeAdjacentIndexes(adjacent_indexes, wordList);
int begin_index;
int end_index;
for (int i = 0; i < wordList.size(); ++i) {
if (wordList[i] == beginWord) {
begin_index = i;
}
if (wordList[i] == endWord) {
end_index = i;
}
}
return CountDistance(begin_index, end_index, adjacent_indexes);
}

private:
void ComposeAdjacentIndexes(vector<set<int>>& adjacent_indexes, const vector<string>& wordList) {
map<string, int> word_to_index;
for (int i = 0; i < wordList.size(); ++i) {
word_to_index[wordList[i]] = i;
}
for (int i = 0; i < wordList.size(); ++i) {
for (int char_index = 0; char_index < wordList[i].size(); ++char_index) {
string adjacent_word = wordList[i];
for (char c = 'a'; c <= 'z'; ++c) {
adjacent_word[char_index] = c;
if (adjacent_word == wordList[i]) {
continue;
}
if (!word_to_index.contains(adjacent_word)) {
continue;
}
const int left = i;
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

自分は定数であることを強調したいとき以外に、ローカルの変数に const は付けません。このあたりはチームの平均的な書き方に合わせることをお勧めいたします。

const int right = word_to_index[adjacent_word];
adjacent_indexes[left].insert(right);
adjacent_indexes[right].insert(left);
}
}
}
}

int CountDistance(int begin_index, int end_index, vector<set<int>>& adjacent_indexes) {
queue<pair<int, int>> next_indexes;
next_indexes.emplace(1, begin_index);
vector<int> distances(adjacent_indexes.size(), std::numeric_limits<int>::max());
distances[begin_index] = 1;
while (!next_indexes.empty()) {
auto [distance, index] = next_indexes.front();
next_indexes.pop();
if (distances[index] < distance) {
continue;
}
if (index == end_index) {
return distance;
}
distances[index] = distance;
for (auto next_index : adjacent_indexes[index]) {
if (distances[next_index] < distance + 1) {
continue;
}
next_indexes.emplace(distance + 1, next_index);
}
}
return 0;
}
};
70 changes: 70 additions & 0 deletions 127/step3.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
class Solution {
public:
int ladderLength(string begin_word, string end_word, vector<string>& word_list) {
bool no_end = none_of(word_list.begin(), word_list.end(), [end_word](string s) { return s == end_word;});
if (no_end) {
return 0;
}
bool no_begin = none_of(word_list.begin(), word_list.end(), [begin_word](string s) { return s == begin_word; });
if (no_begin) {
word_list.emplace_back(begin_word);
}
vector<vector<int>> adjacent_indexes = vector<vector<int>>(word_list.size(), vector<int>{});
ComposeAdjacentIndexes(adjacent_indexes, word_list);
int begin_index;
int end_index;
for (int i = 0; i < word_list.size(); ++i) {
if (word_list[i] == begin_word) {
begin_index = i;
}
if (word_list[i] == end_word) {
end_index = i;
}
}
return CountWordLadder(begin_index, end_index, adjacent_indexes);
}

private:
void ComposeAdjacentIndexes(vector<vector<int>>& adjacent_indexes, const vector<string>& word_list) {
for (int i = 0; i < word_list.size(); ++i) {
for (int j = i + 1; j < word_list.size(); ++j) {
if (!IsNextWords(word_list[i], word_list[j])) {
continue;
}
adjacent_indexes[i].emplace_back(j);
adjacent_indexes[j].emplace_back(i);
}
}
}

bool IsNextWords(const string left, const string right) {
int diff_count = 0;
for (int i = 0; i < left.size(); ++i) {
if (left[i] != right[i]) {
++diff_count;
}
}
return diff_count == 1;
}

int CountWordLadder(const int begin_index, const int end_index, const vector<vector<int>>& adjacent_indexes) {
queue<pair<int, int>> indexes_and_distances;
indexes_and_distances.emplace(begin_index, 1);
vector<int> distances = vector<int>(adjacent_indexes.size(), numeric_limits<int>::max());
while (!indexes_and_distances.empty()) {
auto [index, distance] = indexes_and_distances.front();
indexes_and_distances.pop();
if (distances[index] <= distance) {
continue;
}
distances[index] = distance;
for (int next_index : adjacent_indexes[index]) {
if (next_index == end_index) {
return distance + 1;
}
indexes_and_distances.emplace(next_index, distance + 1);
}
}
return 0;
}
};
Loading