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
198 changes: 198 additions & 0 deletions 695.-Max-Area-of-Island/memo.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,198 @@
# step1
前回の問題とほとんど並列に進みそう。
グリッドを走査して最初に未発見の島を見つけたところからカウントをする。BFSとDFSどちらでもできそうだがいったんBFSで書く。

前回はseen_gridを用意していたが、gridをコピーして書き換えながら探索したかチェックする。

```cpp:step1.cpp
```

# step2

## 他の人のコードを読む
- early returnする書き方も自然で取り入れたい。 https://github.com/akmhmgc/arai60/pull/15/files#r2347265871

## Union Find
前回スルーしてしまったのでやってみる。
- odaさんの解説 https://discord.com/channels/1084280443945353267/1183683738635346001/1197738650998415500
- 参考にした実装 https://algo-logic.info/union-find-tree/

時間計算量は(m*n*α(m*n)) (alphaは逆アッカーマン関数)

```cpp:step2_union_find.cpp
#include <ranges>
#include <vector>

class DisjointUnionSet {
private:
const int n;
std::vector<int> parents;
std::vector<int> size;
int find_root(int x) {
if (parents[x] == x) {
return x;
}
// path compression
return parents[x] = find_root(parents[x]);
}

public:
DisjointUnionSet(int n) : n(n), parents(n), size(n, 1) {
for (auto i : std::views::iota(0, n)) {
parents[i] = i;
}
}
bool same(int x, int y) { return find_root(x) == find_root(y); }
// return root of components of x and y after unite
int unite(int x, int y) {
x = find_root(x);
y = find_root(y);
if (x == y) {
return x;
}
// union by size
if (size[x] < size[y]) {
std::swap(x, y);
}
parents[y] = x;
size[x] += size[y];
return x;
}
int size_of_components(int x) { return size[find_root(x)]; }
};

class Solution {
public:
int maxAreaOfIsland(const std::vector<std::vector<int>>& grid) {
if (grid.empty() || grid[0].empty()) {
return -1;
}
int num_rows = grid.size();
int num_cols = grid[0].size();

DisjointUnionSet connected_component_manager(num_rows * num_cols);
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

manager という単語はよく見かけるのですが、この場合はどれくらい情報があるのが微妙に感じます。 connected_components はいかがでしょうか?

for (auto row : std::views::iota(0, num_rows)) {
for (auto col : std::views::iota(0, num_cols)) {
if ((row + 1 < num_rows) && grid[row][col] == grid[row + 1][col]) {
connected_component_manager.unite(row * num_cols + col, (row + 1) * num_cols + col);
}
if ((col + 1 < num_cols) && grid[row][col] == grid[row][col + 1]) {
connected_component_manager.unite(row * num_cols + col, row * num_cols + col + 1);
}
}
}

int result = 0;
for (auto row : std::views::iota(0, num_rows)) {
for (auto col : std::views::iota(0, num_cols)) {
if (grid[row][col] == 0) {
continue;
}
result =
std::max(result, connected_component_manager.size_of_components(row * num_cols + col));
}
}
return result;
}
};

```

## 自分用メモ
- 再帰を非再帰で書き直す https://qiita.com/KowerKoint/items/870ea9ef7a39f3fe4ce3
- CPU命令の時間比較 http://ithare.com/infographics-operation-costs-in-cpu-clock-cycles/


```cpp:step2.cpp
#include <ranges>
#include <vector>

class Solution {
public:
/**
* 入力が空のグリッドの場合は-1を返す
*/
int maxAreaOfIsland(const std::vector<std::vector<int>>& grid) {
if (grid.empty() || grid[0].empty()) {
return -1;
}
num_rows = static_cast<int>(grid.size());
num_cols = static_cast<int>(grid[0].size());
unvisited_islands = grid;

int result = 0;
for (auto row : std::views::iota(0, num_rows)) {
for (auto col : std::views::iota(0, num_cols)) {
result = std::max(result, traverse_and_count_area(row, col));
}
}
return result;
}

private:
// 本来ならconstをつけてコンストラクタで初期化したほうがよい
int num_rows;
int num_cols;
std::vector<std::vector<int>> unvisited_islands;
Copy link
Copy Markdown

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.

たしかにSolutionクラスに複数スレッドからアクセスする場合引数に渡す必要がありますね…

本問題の場合最初にmaxAreaOfIslandが受け取るgrid自体は複数スレッドで共有しうると思うのですが、Solutionクラス自体は各スレッドで構築するシチュエーションが多いんじゃないかと思っていました。

複数スレッドをを想定する場合、メンバー変数にはconstなもののみ置いたほうがよいのでしょうか。

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

複数スレッドを想定する場合、 const なメンバー変数は置いても安全なことが多いと思います。ただし、 const std::shared_ptr 等、その変数の先にオブジェクトがある場合は注意が必要です。そのオブジェクトを書き換える必要がある場合は、ロックを取ってから読み書きする、といった実装にする必要があると思います。
また、非 const なメンバー変数を置くこともあります。ただし、変更する関数が一つでもある場合は、ロックを取ってから読み書きをする、といった実装にすることになると思います。

ロックを取る際は、ロックを取りたい範囲や条件にもよりますが、 std::mutex や std::lock_guard などが使われると思います。
https://cpprefjp.github.io/reference/mutex/mutex.html
https://cpprefjp.github.io/reference/mutex/lock_guard.html


int traverse_and_count_area(int row, int col) {
if (!(0 <= row && row < num_rows && 0 <= col && col < num_cols)) {
return 0;
}
if (!unvisited_islands[row][col]) {
return 0;
}
// 訪れた
unvisited_islands[row][col] = 0;
return 1 + traverse_and_count_area(row + 1, col) + traverse_and_count_area(row, col + 1) +
traverse_and_count_area(row - 1, col) + traverse_and_count_area(row, col - 1);
}
};

```


# step3
```cpp:step3.cpp
#include <ranges>
#include <vector>

class Solution {
public:
int maxAreaOfIsland(std::vector<std::vector<int>>& grid) {
if (grid.empty() || grid[0].empty()) {
return -1;
}

num_rows = grid.size();
num_cols = grid[0].size();
unvisited_islands = grid;

int result = 0;
for (auto row : std::views::iota(0, num_rows)) {
for (auto col : std::views::iota(0, num_cols)) {
result = std::max(result, traverse_and_count_area(row, col));
}
}
return result;
}

private:
int num_rows;
int num_cols;
std::vector<std::vector<int>> unvisited_islands;
int traverse_and_count_area(int row, int col) {
if (!(0 <= row && row < num_rows && 0 <= col && col < num_cols)) {
return 0;
}
if (unvisited_islands[row][col] == 0) {
return 0;
}

unvisited_islands[row][col] = 0;
return 1 + traverse_and_count_area(row + 1, col) + traverse_and_count_area(row, col + 1) +
traverse_and_count_area(row - 1, col) + traverse_and_count_area(row, col - 1);
}
};

```
63 changes: 63 additions & 0 deletions 695.-Max-Area-of-Island/step1.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
#include <array>
#include <queue>
#include <ranges>
#include <vector>

class Solution {
public:
// 入力が不正な場合は-1を返す
int maxAreaOfIsland(const std::vector<std::vector<int>>& grid) {
if (grid.empty() || grid[0].empty()) {
return -1;
}
num_rows = static_cast<int>(grid.size());
num_cols = static_cast<int>(grid[0].size());

int max_area_of_islands = 0;
// 1はまだ探索していない島、0は海または探索済みの島
std::vector<std::vector<int>> unvisited_islands = grid;
for (const auto row : std::views::iota(0, num_rows)) {
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

自分は ranged-for 文でプリミティブ型には const を付けない派です。チームの平均的な書き方に合わせることをおすすめします。

for (const auto col : std::views::iota(0, num_cols)) {
if (unvisited_islands[row][col]) {
max_area_of_islands =
std::max(max_area_of_islands, measure_islands(row, col, unvisited_islands));
}
}
}

return max_area_of_islands;
}

private:
int num_rows;
int num_cols;
const std::array<std::pair<int, int>, 4> direction = {{{1, 0}, {0, 1}, {-1, 0}, {0, -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.

所属するチームによって変わると思いますが、Googleのコーディング規約だと、定数名の頭にkを付けるらしいです。
https://google.github.io/styleguide/cppguide.html#Constant_Names

また、定数定義するときは、C++11以降だとconstexprが使えます。


bool is_effective_grid(const int row, const int col) const {
return 0 <= row && row < num_rows && 0 <= col && col < num_cols;
}
// 島の連結成分の面積を計算し訪問済みにする
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

メンバー関数のあいだに空行を空けることをおすすめします。

int measure_islands(const int start_row, const int start_col,
std::vector<std::vector<int>>& unvisited_islands) {
int island_area = 0;
std::queue<std::pair<int, int>> visiting;
// BFS
unvisited_islands[start_row][start_col] = 0;
visiting.emplace(start_row, start_col);
++island_area;
while (!visiting.empty()) {
const auto [row, col] = visiting.front();
visiting.pop();
for (const auto [delta_row, delta_col] : direction) {
const auto next_row = row + delta_row;
const auto next_col = col + delta_col;
if (is_effective_grid(next_row, next_col) && unvisited_islands[next_row][next_col]) {
unvisited_islands[next_row][next_col] = 0;
visiting.emplace(next_row, next_col);
++island_area;
}
}
}
return island_area;
}
};
44 changes: 44 additions & 0 deletions 695.-Max-Area-of-Island/step2.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
#include <ranges>
#include <vector>

class Solution {
public:
/**
* 入力が空のグリッドの場合は-1を返す
*/
int maxAreaOfIsland(const std::vector<std::vector<int>>& grid) {
if (grid.empty() || grid[0].empty()) {
return -1;
}
num_rows = static_cast<int>(grid.size());
num_cols = static_cast<int>(grid[0].size());
unvisited_islands = grid;

int result = 0;
for (auto row : std::views::iota(0, num_rows)) {
for (auto col : std::views::iota(0, num_cols)) {
result = std::max(result, traverse_and_count_area(row, col));
}
}
return result;
}

private:
// 本来ならconstをつけてコンストラクタで初期化したほうがよい
int num_rows;
int num_cols;
std::vector<std::vector<int>> unvisited_islands;

int traverse_and_count_area(int row, int col) {
if (!(0 <= row && row < num_rows && 0 <= col && col < num_cols)) {
return 0;
}
if (!unvisited_islands[row][col]) {
return 0;
}
// 訪れた
unvisited_islands[row][col] = 0;
return 1 + traverse_and_count_area(row + 1, col) + traverse_and_count_area(row, col + 1) +
traverse_and_count_area(row - 1, col) + traverse_and_count_area(row, col - 1);
}
};
75 changes: 75 additions & 0 deletions 695.-Max-Area-of-Island/step2_union_find.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,75 @@
#include <ranges>
#include <vector>

class DisjointUnionSet {
private:
const int n;
std::vector<int> parents;
std::vector<int> size;
int find_root(int x) {
if (parents[x] == x) {
return x;
}
// path compression
return parents[x] = find_root(parents[x]);
}

public:
DisjointUnionSet(int n) : n(n), parents(n), size(n, 1) {
for (auto i : std::views::iota(0, n)) {
parents[i] = i;
}
}
bool same(int x, int y) { return find_root(x) == find_root(y); }
// return root of components of x and y after unite
int unite(int x, int y) {
x = find_root(x);
y = find_root(y);
if (x == y) {
return x;
}
// union by size
if (size[x] < size[y]) {
std::swap(x, y);
}
parents[y] = x;
size[x] += size[y];
return x;
}
int size_of_components(int x) { return size[find_root(x)]; }
};

class Solution {
public:
int maxAreaOfIsland(const std::vector<std::vector<int>>& grid) {
if (grid.empty() || grid[0].empty()) {
return -1;
}
int num_rows = grid.size();
int num_cols = grid[0].size();

DisjointUnionSet connected_component_manager(num_rows * num_cols);
for (auto row : std::views::iota(0, num_rows)) {
for (auto col : std::views::iota(0, num_cols)) {
if ((row + 1 < num_rows) && grid[row][col] == grid[row + 1][col]) {
connected_component_manager.unite(row * num_cols + col, (row + 1) * num_cols + col);
}
if ((col + 1 < num_cols) && grid[row][col] == grid[row][col + 1]) {
connected_component_manager.unite(row * num_cols + col, row * num_cols + col + 1);
}
}
}

int result = 0;
for (auto row : std::views::iota(0, num_rows)) {
for (auto col : std::views::iota(0, num_cols)) {
if (grid[row][col] == 0) {
continue;
}
result =
std::max(result, connected_component_manager.size_of_components(row * num_cols + col));
}
}
return result;
}
};
Loading