Skip to content
Open
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
124 changes: 124 additions & 0 deletions 695_max_area_of_island/answer.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,124 @@
問題:https://leetcode.com/problems/max-area-of-island/description/
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

ちなみにStep 3で3回書き直すのはやっていますか?

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.

はい、やっています。今回は再帰でやりました。


## step1

問題は
 1.vector<vector<int>>のm行n列の配列に1,0が入っている。
 2.1を島とし、0を海とする。島は上下左右につながって一つの島として数える。斜めにつながる島は一つの島と数えない。
 3.最も大きい島に何個の1が含まれるか、面積を答えよ
というもの。

200.number of islandsで使用したのと同様のDFSの手段を使用したがデータの破壊を避けるために島を沈没させずにvisitedにて訪問を記録した。
 1.まずcount_island関数を作る。これは配列の(i,j)地点を与えられると、それが島なら一続きの島の面積を返し、海なら0を返す。
 2.これの中身は再帰関数になっており、訪問記録visitedと配列gridを(i,j)と共に引数にする。もし(i,j)地点が島なら、(i,j)をvisitedに追加し、(i,j)の四方一マスについて自分自身を呼び出して探索する。
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)をvisitedに追加し

とありますが、visitedはvector<bool>ではないですか?

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.

そうですね、(i,j)を訪れたのを記録するという意味で書きましたが不正確でした。
visited[i][j]をtrueにする、と書いたほうが良かったです。

 3.再帰の一回ごとに現在地点が島ならカウンターに+1をする。島の面積がわかるのであとはgridを走査する。
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

島の面積がわかるのであとはgridを走査する

論理関係がおかしい記述になっていそうでしょうか?

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.

はい、なっています。

そうすることである島の面積がわかるので、gridを走査すると最大の面積の島がわかる

と書いた方がよいです。

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

読んでアルゴリズムがイメージできる説明ではなさそうに見えました。

「gridを走査し、未探索の島ならcount_island()で面積を求め、最大値を探す。」とかでどうでしょう?

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.

そうですね、プログラムの動作としてはこの記述が正しいです。元の記述は思いつくまでの過程、思考を書きすぎています。


時間計算量は走査するのでO(MN)で、空間計算量はO(MN)である。スタック領域に再帰が積み重なることで最悪の場合はgridの要素一つ一つに対してメモリを使用する。

```cpp
class Solution {
private:
int count_island(vector<vector<bool>>& visited,
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

vector<bool> は特殊化されたコンテナであり、通常の vector<T> とは挙動が一部異なります。要素アクセスにはプロキシオブジェクトが用いられるため、場合によっては動作がやや複雑になったり、オーバーヘッドが発生したりする可能性があります。
また、要素のアドレスを取得できないなど、通常の配列的な扱いができない点にも注意が必要です。
このような特殊性から、vector<bool> の使用を避ける方針のチームもあります。特別な理由がない限りは、vector<char> や vector<uint8_t> などを使うほうが無難だと思います。

const vector<vector<int>>& grid, int i, int j) {
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

こちらのコメントをご参照ください。
mamo3gr/arai60#16 (comment)

int count = 0;
if (0 <= i && i < grid.size() && 0 <= j && j < grid[0].size() &&
grid[i][j] == 1 && !visited[i][j]) {
visited[i][j] = true;
count = 1 + count_island(visited, grid, i - 1, j) +
count_island(visited, grid, i + 1, j) +
count_island(visited, grid, i, j - 1) +
count_island(visited, grid, i, j + 1);
}
return count;
Comment on lines +23 to +32
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Early returnで書き直すとどうなりますか?

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.

    int island(vector<vector<int>>& grid, vector<vector<bool>>& visited, int i,
               int j) {
        int count = 0;
        if (0 > i || i >= grid.size() || 0 > j || j >= grid[0].size())
            return 0;
        if (grid[i][j] == 0 || visited[i][j])
            return 0;
        visited[i][j] = true;
        return island(grid, visited, i - 1, j) +
               island(grid, visited, i + 1, j) +
               island(grid, visited, i, j - 1) +
               island(grid, visited, i, j + 1) + 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.

int count = 0;は不要です。

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

if (0 > i || i >= grid.size() || 0 > j || j >= grid[0].size())

この部分が読みにくく感じました。元のコード同様、数直線上に一直線に並ぶよう並べたほうが読みやすいと思います。

if (!(0 <= i && i < grid.size() && 0 <= j && j < grid[0].size())) {

}

public:
int maxAreaOfIsland(vector<vector<int>>& grid) {
int m = grid.size();
int n = grid[0].size();
Comment on lines +37 to +38
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

変数で置いていますが、下のforループで使うのを忘れているようです。

vector<vector<bool>> visited(m, vector<bool>(n, false));
int count = 0;
for (int i = 0; i < grid.size(); i++) {
for (int j = 0; j < grid[0].size(); j++) {
if (grid[i][j] == 1 && !visited[i][j]) {
int temp = count_island(visited, grid, i, j);
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.

うーん、思いつきません。
temp_countはどうでしょうか。ご意見をお聞かせください。

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

area, max_areaとすると良いと思います。

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.

ありがとうございます。各島の面積<=最大の島の面積なので意味とやっていることが一致しますね。

count = (count < temp) ? temp : count;
}
}
}
return count;
}
};
```

## step2

他の方のコードとコメント集みた。
以下の方を参考にユニオンファインドやってみる。
参考:https://github.com/quinn-sasha/leetcode/pull/30/changes

```cpp
class UnionFind {
vector<int> parent;
vector<int> size;

public:
UnionFind(int maxsize) : parent(maxsize), size(maxsize, 1) {
for (int i = 0; i < maxsize; i++) {
parent[i] = i;
}
Comment on lines +67 to +69
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

iota(parent.begin(), parent.end(), 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.

ありがとうございます。iotaという便利な関数があるのですね。

}
int find(int x) {
if (parent[x] == x) {
return x;
}
return parent[x] = find(parent[x]);
}
int unite(int x, int y) {
int x_root = find(x);
int y_root = find(y);
if (x_root == y_root) {
return x_root;
}
if (size[x_root] < size[y_root]) {
swap(x_root, y_root);
}
parent[y_root] = x_root;
size[x_root] += size[y_root];
return x_root;
}
int get_size(int x) { return size[find(x)]; }
};
class Solution {
public:
int maxAreaOfIsland(vector<vector<int>>& grid) {
int num_rows = grid.size();
int num_cols = grid[0].size();
UnionFind uf(num_rows * num_cols);
int max_area = 0;
for (int row = 0; row < num_rows; row++) {
for (int col = 0; col < num_cols; col++) {
if (grid[row][col] == 0) {
continue;
}
max_area = max(max_area, 1);
int id = row * num_cols + col;
if (col + 1 < num_cols && grid[row][col + 1] == 1) {
int root = uf.unite(id, row * num_cols + col + 1);
max_area = max(max_area, uf.get_size(root));
}
if (row + 1 < num_rows && grid[row + 1][col] == 1) {
int root = uf.unite(id, (row + 1) * num_cols + col);
max_area = max(max_area, uf.get_size(root));
}
}
}
return max_area;
}
};
```

ユニオンファインドは
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Union-Findというデータ構造自体の説明とこの問題固有の話を混同しているようです。別々に記述できますか?

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.

難しいです。ユニオンファインドに触れたのは今回が初めてなので、これが典型的に用いられる場面が分かっていません。
parent,root,unite,find辺りがデータ構造の話で、縦横に動くというのが固有の話でしょうか。

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

AIに聞いたり、Googleで検索したりしましたか?この話に限らずですが…

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

まずは、Union-Findとはどのようなデータ構造であるか、からですかね。

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.

@liquo-rice @h-masder
ご返信ありがとうございます。お時間を使ってくださっているのに「分からない」で済ませて申し訳ありません。
一応geminiとgoogleは使っています。

union-findはunion(x,y)とfind(x)をメンバー関数として持つアルゴリズムで、木やリストによって実装されます。今回はリストで実装しています。それぞれの要素の所属する集合は素集合(和集合=空集合)であるように配置されます。union(x,y)によりxとyの属する集合を結合します。find(x)によりxの属する集合を判明させます。結合の方法には効率的な方法があります。ある規則で複数の要素を数えるときなどに有効性があります。

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

その説明なのですが、素集合系に属する要素(集合)同士の和集合が空集合というのは誤りですね。和集合は、積集合の間違いかと思われます。全体的に言葉の意味を理解して使っているように見えないと読み取れる文章が多々あるように見受けられます。

腑に落ちない場合はとりあえず先に進んでも大丈夫だとは思いますが、分からないことと分かることを区別できるのは大事だと思います。

例えば、メモに「Union-Findの実装は理解したが、用途や数学的な意味はまだピンと来ていない」などと書いておいて、後で振り返って考えてみると良いかもしれません。

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.

@liquo-rice
ありがとうございます。積集合でした。
そうですね、プログラムの手順と理解度を分けて書いておこうと思います。

 1.親をそれぞれの配列の要素が持ち、その親の親の親……と累進した最後の場所の親であるrootをある島の代表として扱う。
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.

レビューありがとうございます。
こういう表現を見たことがあったので特に意図があってのことではないのですが、遡ると書いた方がいいですね。

 2.これがどのように決められるかというと横の走査と縦の走査を行うことによる。隣の一つも島である場合、自分と隣のrootを比較してより多くの子供がいる方にuniteしてrootを一種類にする。
 3.島のサイズはrootに紐づいて管理される。