diff --git a/leetcode/17/memo.md b/leetcode/17/memo.md new file mode 100644 index 0000000..b937b5d --- /dev/null +++ b/leetcode/17/memo.md @@ -0,0 +1,28 @@ +# Step 1 + +各桁の間に関係性がなさそうなので、素直に全通り生成していく問題かなと思った。 + +> `1 <= digits.length <= 4` + +で、1 つの digit が最大 4 つの文字を取りうるので、`4 ^ 4 = 2 ^ 8 = 2 ^ 10 / 2 ^ 2 = 1024 / 4 = 256` 通りの結果を最悪作ることになる。 +木を考えると、一段目が 4、次が16、64、256 と続いて、全部で 340 のノードが存在し得るが、これは C++ が 1秒間で 10^8 - 10^9 回の処理を行えることを考えると、実行時間は問題にならないように思う。 +最終的に長さ 4 のstring を 256 個持つことになるが、char が 1 byte、4 つで 4 bytes、std::stringのサイズに詳しくないので一旦そこは無視しても、256 * 4 bytes = 1 KB。特に問題になるような大きさではないだろう。 +(追記: std::string は、この文字列の長さなら私の環境で 1 つ 24 bytes, なので最終結果の保持に 256 * 24 bytes = 6KB) + +時間計算量: O(n * 4 ^ n) (string construction + generate all possible combinations) +空間計算量: O(n * 4 ^ n) (the result holds 4 ^ n strings with length of n) + +テストケースは無事にパスしたのだが、LeetCode 上の実行時間・補助空間使用量の順位が最下位に近い。constant 倍くらいしか違わなそうだが、私の解法よりも効率の良い解法がありそうだ。~Step 2 で見てみることにする。~ と思ったのだが、これ backtracking で行けるのでは? C++のsyntaxを思い出すことにいっぱいいっぱいになってしまっていたが、Pythonだったらnested functions を書くのが楽なので helper 関数を用意して backtracking という方法がすぐに思いついたのにな、悔しい。 + +-> `step1_backtracking.py` + +途中結果をもつ `list[str]` を使い回すことで、`step1.cpp`のように途中結果を全て保持しなくて済んでいる。 + +ところで、digit -> characters の hashmap は書き下した方がわかりやすいと思う。これ以上の工夫は思いつかず、書き下している方が将来キー配置の変更があったとしても対応しやすく、差分が理解しやすいように思う。 + +# Step 2 + +ざっと std::string のサイズについてGoogleした限りこれだ!と思うリファレンスはなかったが、色々見た上での私の理解は以下の通り。 +環境によって std::string のオブジェクトサイズは 24\~32 bytes。(私の手元の環境だと24 bytes だった) +短い文字列は Short String Optimization (SSO) によりオブジェクト内部に格納されるが、 +一定の長さを超えると SSO が適用されなくなり、ヒープ領域に文字列用のメモリが別途確保される。 diff --git a/leetcode/17/step1.cpp b/leetcode/17/step1.cpp new file mode 100644 index 0000000..7407cfe --- /dev/null +++ b/leetcode/17/step1.cpp @@ -0,0 +1,46 @@ +#include +#include +#include +#include +#include + +class Solution { +public: + std::vector letterCombinations(std::string digits) { + std::map> digit_to_characters = { + {'2', {'a', 'b', 'c'}}, + {'3', {'d', 'e', 'f'}}, + {'4', {'g', 'h', 'i'}}, + {'5', {'j', 'k', 'l'}}, + {'6', {'m', 'n', 'o'}}, + {'7', {'p', 'q', 'r', 's'}}, + {'8', {'t', 'u', 'v'}}, + {'9', {'w', 'x', 'y', 'z'}}, + }; + + std::vector possible_combinations; + std::stack>> combinations_in_progress; + combinations_in_progress.push({0, {}}); + while (!combinations_in_progress.empty()) { + auto [digit_index, combination_in_progress] = combinations_in_progress.top(); + combinations_in_progress.pop(); + + if (digit_index == digits.size()) { + std::string possible_combination( + combination_in_progress.begin(), + combination_in_progress.end() + ); + possible_combinations.push_back(possible_combination); + continue; + } + + for (char possible_character : digit_to_characters[digits[digit_index]]) { + std::vector next_combination(combination_in_progress); + next_combination.push_back(possible_character); + combinations_in_progress.push({digit_index + 1, next_combination}); + } + } + + return possible_combinations; + } +}; diff --git a/leetcode/17/step1_backtracking.py b/leetcode/17/step1_backtracking.py new file mode 100644 index 0000000..8004753 --- /dev/null +++ b/leetcode/17/step1_backtracking.py @@ -0,0 +1,27 @@ +class Solution: + def letterCombinations(self, digits: str) -> list[str]: + digit_to_characters: dict[str, list[str]] = { + "2": ["a", "b", "c"], + "3": ["d", "e", "f"], + "4": ["g", "h", "i"], + "5": ["j", "k", "l"], + "6": ["m", "n", "o"], + "7": ["p", "q", "r", "s"], + "8": ["t", "u", "v"], + "9": ["w", "x", "y", "z"], + } + + all_combinations = [] + + def generate_combinations(digit_index: int, combination: list[str]) -> None: + if digit_index == len(digits): + all_combinations.append("".join(combination)) + return + + for character in digit_to_characters[digits[digit_index]]: + combination.append(character) + generate_combinations(digit_index + 1, combination) + combination.pop() + + generate_combinations(0, []) + return all_combinations diff --git a/leetcode/17/step2.py b/leetcode/17/step2.py new file mode 100644 index 0000000..3b6eed7 --- /dev/null +++ b/leetcode/17/step2.py @@ -0,0 +1,27 @@ +class Solution: + def letterCombinations(self, digits: str) -> list[str]: + digit_to_characters: dict[str, list[str]] = { + "2": ["a", "b", "c"], + "3": ["d", "e", "f"], + "4": ["g", "h", "i"], + "5": ["j", "k", "l"], + "6": ["m", "n", "o"], + "7": ["p", "q", "r", "s"], + "8": ["t", "u", "v"], + "9": ["w", "x", "y", "z"], + } + + generated = [] + + def generate_combinations(digit_index: int, generating: list[str]) -> None: + if digit_index == len(digits): + generated.append("".join(generating)) + return + + for character in digit_to_characters[digits[digit_index]]: + generating.append(character) + generate_combinations(digit_index + 1, generating) + generating.pop() + + generate_combinations(0, []) + return generated diff --git a/leetcode/17/step3.py b/leetcode/17/step3.py new file mode 100644 index 0000000..d442f6a --- /dev/null +++ b/leetcode/17/step3.py @@ -0,0 +1,28 @@ +class Solution: + def letterCombinations(self, digits: str) -> list[str]: + digit_to_characters = { + "2": "abc", + "3": "def", + "4": "ghi", + "5": "jkl", + "6": "mno", + "7": "pqrs", + "8": "tuv", + "9": "wxyz", + } + + def generate_combinations( + digit_index: int, generating: list[str], generated: list[str] + ) -> None: + if digit_index == len(digits): + generated.append("".join(generating)) + return + + for ch in digit_to_characters[digits[digit_index]]: + generating.append(ch) + generate_combinations(digit_index + 1, generating, generated) + generating.pop() + + generated = [] + generate_combinations(0, [], generated) + return generated diff --git a/leetcode/17/step4.cpp b/leetcode/17/step4.cpp new file mode 100644 index 0000000..0348822 --- /dev/null +++ b/leetcode/17/step4.cpp @@ -0,0 +1,47 @@ +#include +#include +#include +#include +#include + +class Solution { +public: + std::vector letterCombinations(std::string digits) { + if (digits.empty()) { return {}; } + + std::map> digit_to_characters = { + {'2', {'a', 'b', 'c'}}, + {'3', {'d', 'e', 'f'}}, + {'4', {'g', 'h', 'i'}}, + {'5', {'j', 'k', 'l'}}, + {'6', {'m', 'n', 'o'}}, + {'7', {'p', 'q', 'r', 's'}}, + {'8', {'t', 'u', 'v'}}, + {'9', {'w', 'x', 'y', 'z'}}, + }; + + std::vector possible_combinations; + std::stack>> combinations_in_progress; + combinations_in_progress.push({0, {}}); + while (!combinations_in_progress.empty()) { + auto top = std::move(combinations_in_progress.top()); + combinations_in_progress.pop(); + auto [digit_index, combination_in_progress] = std::move(top); + + if (digit_index == digits.size()) { + possible_combinations.emplace_back( + combination_in_progress.begin(), combination_in_progress.end() + ); + continue; + } + + for (char possible_character : digit_to_characters[digits[digit_index]]) { + std::vector next_combination(combination_in_progress); + next_combination.push_back(possible_character); + combinations_in_progress.emplace(digit_index + 1, std::move(next_combination)); + } + } + + return possible_combinations; + } +};