Skip to content
Open
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
9 changes: 9 additions & 0 deletions Arai60/08. Next Permutation/Makefile
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
NAME = a.out
SRC = main.cpp
CXX = g++
CXXFLAGS = -Wall -Wextra -Werror -std=c++11

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

ローカルの実行環境はLeetCodeに合わせるのはどうでしょうか?

clang 19 using the latest C++ 23 standard, and libstdc++ provided by GCC 14.

https://support.leetcode.com/hc/en-us/articles/360011833974-What-are-the-environments-for-the-programming-languages

Copy link
Copy Markdown
Owner Author

Choose a reason for hiding this comment

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

ありがとうございます。なるほどそういう手がありましたか・・・!やってみます!

Copy link
Copy Markdown
Owner Author

Choose a reason for hiding this comment

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

自分はローカルの環境構築をdevenv.shで行っているのですが、1時間ほどこの設定にするために時間を溶かしてしまってそれでもまだできなさそうなので、一旦諦めます><


all: $(NAME)

$(NAME): $(SRC) step3.hpp
$(CXX) -o $(NAME) $(SRC) $(CXXFLAGS)
30 changes: 30 additions & 0 deletions Arai60/08. Next Permutation/main.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
#include "step3.hpp"
#include <iostream>
#include <vector>

#define T(...) \
[&] { \
std::cout << #__VA_ARGS__ " -> "; \

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

可変引数マクロですね。確かに、そのまま文字列化するならば、こうするしかないですが、逆に変数を引数に与えると
{i, j, k} -> [1,2,3]
と表示されるはずで、これは意図通りなのか、あくまでも、並んでいる数字がみたいのかは考えどころでしょう。
std::initializer_list でもいいですし、可変引数テンプレートでもよいでしょう。

({;;}) は、GCC 拡張だったかと思います。
また、返り値が最後の式の評価になるので、最後の行で cout << endl; すると嵌ります。
https://gcc.gnu.org/onlinedocs/gcc/Statement-Exprs.html

単に {;;} とするのも手ですが、{;;}; と展開されるので、

if () T(); else {}

で事故りそうです。

ラムダの呼び出しに変えるのは一つです。[&]{std::cout << "";}()
古典的な do{}while(0) を使う手もあります。

テスト目的なのでよいという考えもありますが、結構嵌りそうなコードと感じました。

Copy link
Copy Markdown
Owner Author

Choose a reason for hiding this comment

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

今回は以前使ったことがあったStringizing operator (#)をあまり考えることなく使ってしまいましたが、std::initializer_listという手もあるのですね。
https://learn.microsoft.com/en-us/cpp/preprocessor/stringizing-operator-hash?view=msvc-170
https://en.cppreference.com/w/cpp/preprocessor/replace#.23_and_.23.23_operators
https://en.cppreference.com/w/cpp/utility/initializer_list

返り値が最後の式の評価になってしまうとは知りませんでした。単純にT(){;;};と展開されてしまうのがいやだなーと思っていたのですが、それ以外にも問題があるのですね。複数行マクロの引き出しが増えて、とても勉強になりました。

  • {;;}
  • ラムダ呼び出し
  • do {} while (0)

std::vector<int> nums{__VA_ARGS__}; \
solution.nextPermutation(nums); \
print_arr(nums); \
}()
void print_arr(std::vector<int> &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});
}
64 changes: 64 additions & 0 deletions Arai60/08. Next Permutation/step1.hpp
Original file line number Diff line number Diff line change
@@ -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<int> &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]

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

略語は読むにあたり、やや認知負荷が高く感じます。単語はできる限りフルスペルで書いたほうが良いと思います。

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());
}
};
55 changes: 55 additions & 0 deletions Arai60/08. Next Permutation/step2.hpp
Original file line number Diff line number Diff line change
@@ -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しなくても一箇所にまとめられる。
*/

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

https://en.cppreference.com/w/cpp/algorithm/next_permutation
一応こちらに参考実装がございます。

Copy link
Copy Markdown
Owner Author

Choose a reason for hiding this comment

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

なるほどこんなものが標準ライブラリで提供されていたんですね。

以下のように使えるとchatgptに教えてもらってなるほど、確かにこれがあると使いやすいなと理解できました。

std::vector<int> v = {1, 2, 3};

// まずは v を昇順にソートしておく(これが「最初の順列」)
std::sort(v.begin(), v.end());

// 辞書順ですべての順列を列挙
do {
    // v を使った処理
    // (ここで順列 v を出力したり、何かしらのチェックを行う)
} while (std::next_permutation(v.begin(), v.end()));

Copy link
Copy Markdown
Owner Author

Choose a reason for hiding this comment

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

以下の二つについても使ったことがなかったので、参考実装を見て勉強になりました。

std::make_reverse_iterator
http://en.cppreference.com/w/cpp/iterator/make_reverse_iterator

std::is_sorted_until
http://en.cppreference.com/w/cpp/algorithm/is_sorted_until

class Solution {
public:
void nextPermutation(std::vector<int> &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<int>());
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);
}
};
93 changes: 93 additions & 0 deletions Arai60/08. Next Permutation/step3.hpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,93 @@
/*
3問連続で正解するのにかかった時間 3:28

時間計算量: O(N)
空間計算量: O(1)
*/
#ifndef STEP3_H
#define STEP3_H
#include <algorithm>
#include <iostream>
#include <vector>
class Solution {
public:
void nextPermutation(std::vector<int> &nums) {
auto pivot =
std::adjacent_find(nums.rbegin(), nums.rend(), std::greater<int>());
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 <class ForwardIt, class BinaryPred>
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 <class ForwardIt,
class T = typename std::iterator_traits<ForwardIt>::value_type>
ForwardIt upper_bound(ForwardIt first, ForwardIt last, const T &value) {
return upper_bound(first, last, value, std::less<T>());
}

template <class ForwardIt,
class T = typename std::iterator_traits<ForwardIt>::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 <class ForwardIt1, class ForwardIt2>
void iter_swap(ForwardIt1 a, ForwardIt2 b) {
std::swap(*a, *b);
}

// https://en.cppreference.com/w/cpp/algorithm/reverse
template <class BidirIt> void reverse(BidirIt first, BidirIt last) {
--last;
while (first < last) {
std::swap(*first, *last);
++first;
--last;
}
}

} // namespace playground
#endif