diff --git a/Arai60/08. Next Permutation/Makefile b/Arai60/08. Next Permutation/Makefile new file mode 100644 index 0000000..4ec71d4 --- /dev/null +++ b/Arai60/08. Next Permutation/Makefile @@ -0,0 +1,9 @@ +NAME = a.out +SRC = main.cpp +CXX = g++ +CXXFLAGS = -Wall -Wextra -Werror -std=c++11 + +all: $(NAME) + +$(NAME): $(SRC) step3.hpp + $(CXX) -o $(NAME) $(SRC) $(CXXFLAGS) diff --git a/Arai60/08. Next Permutation/main.cpp b/Arai60/08. Next Permutation/main.cpp index e69de29..780142a 100644 --- a/Arai60/08. Next Permutation/main.cpp +++ b/Arai60/08. Next Permutation/main.cpp @@ -0,0 +1,30 @@ +#include "step3.hpp" +#include +#include + +#define T(...) \ + [&] { \ + std::cout << #__VA_ARGS__ " -> "; \ + std::vector nums{__VA_ARGS__}; \ + solution.nextPermutation(nums); \ + print_arr(nums); \ + }() +void print_arr(std::vector &nums) { + std::cout << "["; + for (auto it = nums.begin(), end = nums.end(); it != end; ++it) { + std::cout << *it; + if (it + 1 != nums.end()) { + std::cout << ","; + } + } + std::cout << "]" << std::endl; +} + +int main() { + Solution solution; + T({1, 2, 3}); + T({1, 3, 2}); + T({3, 2, 1}); + T({1, 1, 5}); + T({4, 5, 7, 6, 3, 2, 1}); +} diff --git a/Arai60/08. Next Permutation/step1.hpp b/Arai60/08. Next Permutation/step1.hpp new file mode 100644 index 0000000..c5cb28c --- /dev/null +++ b/Arai60/08. Next Permutation/step1.hpp @@ -0,0 +1,64 @@ +/* + 何がわからなかったか + - 次のpermutationを求めるためのステップの着想を得るのが難しかった + + 何を考えて解いていたか + 1. From the tail, find the first asc pair (i, i+1) + 2. find j where nums[j] > nums[i] and j in (i+1, tail) + 3. swap(i, j) + 4. sort(i+1, tail) + + 正解してから気づいたこと + - algorithm系(lower_bound, upper_bound, findなど)を使えば短く綺麗に書けそう + - いつもalgorithm系はcppreferenceを見て、何度か失敗しながらじゃないと書けない + - 後ろからiterateするにはrbeginとrendを使えばよさそう +*/ +class Solution { +public: + void nextPermutation(vector &nums) { + // [1,2,3,4] + // [1,2,4,3] + // [1,3,2,4] + // [1,3,4,2] + // [1,4,2,3] + // [1,4,3,2] + // [2,1,3,4] + // ... + + // 1. From the tail, find the first asc pair (i, i+1) + // 2. find j where nums[j] > nums[i] and j in (i+1, tail) + // 3. swap(i, j) + // 4. sort(i+1, tail) + + int tail = nums.size() - 1; + int i, j; + // 1. Find the first ASC pair nums[i] and nums[i+1] + bool found = false; + for (i = tail - 1; i >= 0; --i) { + if (nums[i] < nums[i + 1]) { + found = true; + break; + } + } + if (!found) { + std::sort(nums.begin(), nums.end()); + return; + } + // 2. Find j + found = false; + for (j = tail; j >= i + 1; --j) { + if (nums[j] > nums[i]) { + found = true; + break; + } + } + if (!found) { + std::sort(nums.begin(), nums.end()); + return; + } + // 3. swap + std::swap(nums[i], nums[j]); + // 4. sort + std::sort(nums.begin() + i + 1, nums.end()); + } +}; diff --git a/Arai60/08. Next Permutation/step2.hpp b/Arai60/08. Next Permutation/step2.hpp new file mode 100644 index 0000000..32057cc --- /dev/null +++ b/Arai60/08. Next Permutation/step2.hpp @@ -0,0 +1,55 @@ +/* + 講師陣はどのようなコメントを残すだろうか? + 他の人のコードを読んで考えたこと + - https://github.com/Yoshiki-Iwasa/Arai60/pull/63/files + 1. Rustのlet Some, unreachable!マクロわかりやすくていい + 2. pivotやsuccessorの名前がわかりやすい + 3. pivotやsuccessorを見つける際がワンライナーで書けている。 + C++だとどう書けるだろうか。 + 4. rpositionに当たるような右から見ていく関数はあっただろうか + https://doc.rust-lang.org/std/iter/trait.Iterator.html#method.rposition + - https://github.com/fhiyo/leetcode/pull/56/files + 1. 二重ループのコードは計算量的に無駄があるし、何をしているのか + 自分としては少しわかりにくいと感じたが、なるほど確かに短くは書けるのか。 + 2. 関数や変数名についてみんな迷っていたが、pivot, successorなどのようにその + 変数がそもそも何なのか、概念を見つけられれば名前がつけやしのかもしれない。 + そうではなく、手続きややっていることを説明しようとすると、長くてわかりに + くくなってしまうのだろう。 + (そもそもprogram自体が指示の羅列なのだから読めばわかるもの) + + 改善する時にかんがえたこと + 1. algorithm関連 : cppreferenceを見てみて、使えそうな関数を探してみる + https://en.cppreference.com/w/cpp/algorithm/find + https://en.cppreference.com/w/cpp/algorithm/find_end + https://en.cppreference.com/w/cpp/algorithm/adjacent_find + https://en.cppreference.com/w/cpp/algorithm/lower_bound + https://en.cppreference.com/w/cpp/algorithm/upper_bound + https://en.cppreference.com/w/cpp/algorithm/iter_swap + 2. Iterator関連 : cppreferenceを見てみて、使えそうな関数を探してみる + https://en.cppreference.com/w/cpp/iterator/rbegin + https://en.cppreference.com/w/cpp/iterator/rend + https://en.cppreference.com/w/cpp/iterator/prev + https://en.cppreference.com/w/cpp/iterator/next + https://en.cppreference.com/w/cpp/iterator/advance + https://en.cppreference.com/w/cpp/iterator/distance + 3. rbeginとrendを使えば後ろから舐めていける + 4. adjacent_findとupper_boundが今回の例にはピッタリ + 5. reverse(begin, pivot)としておけば最初に降順ペアが見つからなかった場合にも + 正しく動作するのでearly returnしなくても一箇所にまとめられる。 + */ +class Solution { +public: + void nextPermutation(std::vector &nums) { + // Find the first DSC pair from the tail : [4,(5,7),6,3,2,1] + auto pivot = + std::adjacent_find(nums.rbegin(), nums.rend(), std::greater()); + if (pivot != nums.rend()) { + ++pivot; // to point to the second element of the pair : + // [4,(*5,7),6,3,2,1] + // to find the successor of pivot : [4,(*5,7),**6,3,2,1] + auto successor = std::upper_bound(nums.rbegin(), pivot, *pivot); + std::iter_swap(pivot, successor); + } + std::reverse(nums.rbegin(), pivot); + } +}; diff --git a/Arai60/08. Next Permutation/step3.hpp b/Arai60/08. Next Permutation/step3.hpp new file mode 100644 index 0000000..9ee5355 --- /dev/null +++ b/Arai60/08. Next Permutation/step3.hpp @@ -0,0 +1,93 @@ +/* + 3問連続で正解するのにかかった時間 3:28 + + 時間計算量: O(N) + 空間計算量: O(1) +*/ +#ifndef STEP3_H +#define STEP3_H +#include +#include +#include +class Solution { +public: + void nextPermutation(std::vector &nums) { + auto pivot = + std::adjacent_find(nums.rbegin(), nums.rend(), std::greater()); + if (pivot != nums.rend()) { + ++pivot; + auto successor = std::upper_bound(nums.rbegin(), pivot, *pivot); + std::iter_swap(pivot, successor); + } + std::reverse(nums.rbegin(), pivot); + } +}; + +/* + 自作してみたデータ構造や関数 +*/ +namespace playground { +// https://en.cppreference.com/w/cpp/algorithm/adjacent_find +// 初めて使ったため +template +ForwardIt adjacent_find(ForwardIt first, ForwardIt last, BinaryPred p) { + if (first == last) + return last; + ForwardIt next = first; + ++next; + while (next != last) { + if (p(*first, *next)) + return first; + ++next; + ++first; + } + return last; +} + +// https://en.cppreference.com/w/cpp/algorithm/upper_bound +// Searches for the first element in the partitioned range [first, last) which +// is ordered AFTER value. いつもlower_boundと使い方を迷うため +template ::value_type> +ForwardIt upper_bound(ForwardIt first, ForwardIt last, const T &value) { + return upper_bound(first, last, value, std::less()); +} + +template ::value_type, + class Compare> +ForwardIt upper_bound(ForwardIt first, ForwardIt last, const T &value, + Compare comp) { + auto lo = first, hi = last; + while (lo < hi) { + auto mid = lo + (hi - lo) / 2; + if (!comp(value, *mid)) { + // *mid <= value + // i.e. !(*mid > value) + lo = mid + 1; + } else { + hi = mid; + } + } + return lo; +} + +// https://en.cppreference.com/w/cpp/algorithm/iter_swap +// 初めて使った +template +void iter_swap(ForwardIt1 a, ForwardIt2 b) { + std::swap(*a, *b); +} + +// https://en.cppreference.com/w/cpp/algorithm/reverse +template void reverse(BidirIt first, BidirIt last) { + --last; + while (first < last) { + std::swap(*first, *last); + ++first; + --last; + } +} + +} // namespace playground +#endif