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
224 changes: 224 additions & 0 deletions 695_MaxAreaOfIsland/solution.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,224 @@
# 問題
https://leetcode.com/problems/max-area-of-island

`0`(水)と `1`(陸)からなる `m x n` の2次元グリッド `grid` が与えられる。島(island)は、上下左右の4方向で連結した陸セルの集まり。グリッドの四辺の外側はすべて水とみなす。島の**面積**(その島に属する陸セルの個数)のうち、**最大の面積**を返す。島が1つも無ければ `0` を返す。

- 例1:
```
grid = [
[0,0,1,0,0,0,0,1,0,0,0,0,0],
[0,0,0,0,0,0,0,1,1,1,0,0,0],
[0,1,1,0,1,0,0,0,0,0,0,0,0],
[0,1,0,0,1,1,0,0,1,0,1,0,0],
[0,1,0,0,1,1,0,0,1,1,1,0,0],
[0,0,0,0,0,0,0,0,0,0,1,0,0],
[0,0,0,0,0,0,0,1,1,1,0,0,0],
[0,0,0,0,0,0,0,1,1,0,0,0,0]
]
```
→ `6`(右側の塊が最大)
- 例2: `grid = [[0,0,0,0,0,0,0,0]]` → `0`(島が無い)
- 制約: `m == grid.length`、`n == grid[i].length`、`1 <= m, n <= 50`、`grid[i][j]` は `0` か `1`

# 前提
- 答えを見ずに考えて、5分考えて分からなかったら答えを見てください。答えを見て理解したと思ったら、答えを隠して書いてください。筆が進まず5分迷ったら答えを見てください。そして、見ちゃったら一回全部消してやり直しです。答えを送信して、正解になったら、まずは一段階目です。
- 次にコードを読みやすくするようにできるだけ整えましょう。これで動くコードになったら二段階目です。
- そしたらまた全部消しましょう。今度は、時間を測りながら、もう一回、書きましょう。書いてアクセプトされたら文字を消してもう一回書きましょう。これを10分以内に一回もエラーを出さずに書ける状態になるまで続けてください。3回続けてそれができたらその問題はひとまず丸です。
---

# 最初の見積もり
- 入力サイズ: m, n ≤ 50 なので セル数 = m*n ≤ 2.5*10^3
- 時間計算量: O(m*n)
- 実行時間の見積もり: 2.5*10^3 / 10^8 ≈ 2.5*10^-5 s = 0.025ms
- 空間計算量: O(m*n)
- 必要メモリの見積もり: grid ヘッダ(address(8)+cap(8)+len(8)=24) + i,j(16) = 40byte * m*n = 40 * 2.5*10^3 = 1*10^5 = 100kb

# 1回目
```go
// 以前といたNumber of Islandsと同じ解法が使える.
// つまり,全域を探索しつつ,1を見つけたらその四方を再帰探索して塗りつぶす.
// 再帰関数は,塗りつぶした面積を返す必要がある
// 最も大きな面積を記録しておき,それを返せばよい

func maxAreaOfIsland(grid [][]int) int {
maxArea := 0
for i := range grid {
for j := range grid[i] {
if grid[i][j] == 1 {
maxArea = max(maxArea, sink(grid, i, j))

@h-masder h-masder Jun 20, 2026

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

sinkのかわりに島の大きさを測る関数名がよいと思いました。
measureIslandSizeなどはいかがでしょうか?

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.

これはたしかに.自分では見逃してました.

自分の感覚だと,面積を測定するというように捉えていたので,measureIslandArea としたいと思いました.

}
}
}
return maxArea
}

func sink(grid [][]int, i, j int) int {
if i < 0 || i >= len(grid) || j < 0 || j >= len(grid[0]) || grid[i][j] != 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.

条件式を適宜分割すると見やすいと思うのですが、いかがでしょうか

    if i < 0 || i >= len(grid) {
        return 0
    } 
    if j < 0 || j >= len(grid[0]) {
        return 0
    }
    if grid[i][j] != 1 {
        return 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.

そう思います.3回目はそのように書いてみました.

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 < len(grid)) {
    return 0
}
if !(0 <= j && j < len(grid[0])) {
    return 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.

自分は否定条件をつかうとすこしだけ読解の負荷があがる感じがして,一律で避けがちでしたが,
この場合だと,配列のレンジチェックということがわかり,そこまで負荷ではないですね.アリだなと感じました.

コメントありがとうございます

return 0
}
grid[i][j] = 0
area := 1
area += sink(grid, i+1, j)
area += sink(grid, i-1, j)
area += sink(grid, i, j+1)
area += sink(grid, i, j-1)
return area
}
```
- 一発で通り.ちょっと嬉しい.
- まぁ,前回解いたやり方でもあり,わりと綺麗かな
- 他の実装選択肢としては,in-placeではない方法,再帰ではなくループを使う方法,BFSを使う方法などがあったはず.これらも書いてみる

# 2回目(out-of-place)
```go
# out-of-place ver
func maxAreaOfIsland(grid [][]int) int {
copyGrid := make([][]int, len(grid), len(grid))
for i := range grid {
copyGrid[i] = append([]int{}, grid[i]...)
}
maxArea := 0
for i := range copyGrid {
for j := range copyGrid[i] {
if copyGrid[i][j] == 1 {
maxArea = max(maxArea, sink(copyGrid, i, j))
}
}
}
return maxArea
}

func sink(grid [][]int, i, j int) int {
if i < 0 || i >= len(grid) || j < 0 || j >= len(grid[0]) || grid[i][j] != 1 {
return 0
}
grid[i][j] = 0
area := 1
area += sink(grid, i+1, j)
area += sink(grid, i-1, j)
area += sink(grid, i, j+1)
area += sink(grid, i, j-1)
return area
}
```


# 2回目(DFS loop)
```go
func maxAreaOfIsland(grid [][]int) int {
copyGrid := make([][]int, len(grid), len(grid))
for i := range grid {
copyGrid[i] = append([]int{}, grid[i]...)
}
maxArea := 0
for i := range copyGrid {
for j := range copyGrid[i] {
if copyGrid[i][j] == 1 {
maxArea = max(maxArea, sink(copyGrid, i, j))
}
}
}
return maxArea
}

func sink(grid [][]int, i, j int) int {
area := 0
grid[i][j] = 0 // DEBUG時に追加
stack := [][2]int{{i, j}}

for len(stack) > 0 {
area += 1
cell := stack[len(stack)-1]
stack = stack[:len(stack)-1]

for _, d := range [4][2]int{{1, 0}, {-1, 0}, {0, 1}, {0, -1}} {
ni, nj := cell[0]+d[0], cell[1]+d[1]
if ni < 0 || ni >= len(grid) || nj < 0 || nj >= len(grid[0]) || grid[ni][nj] != 1 {
continue
}
grid[ni][nj] = 0
stack = append(stack, [2]int{ni, nj})
}
}
return area
}
```
- 最初のマスを塗りつぶしするのが抜けていた.
- out-of-placeと,DFSをloopにするのはできた.
- BFSはまだ実装がスッとでてくる状態じゃなかったので,次回以降にまた取り組むことにした

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

DFS loopが書けているので、そこからBFSを理解するとよさそうです。

深さ優先探索では Last In First Out (LIFO) によって探索を進めます。
一方、幅優先探索では First In First Out (FIFO) にすることで、近い位置から順に探索していきます。

func sink(grid [][]int, i, j int) int {
    area := 0
    grid[i][j] = 0
    queue := [][2]int{{i, j}}

    for len(queue) > 0 {
        area += 1

        cell := queue[0]
        queue = queue[1:]

        for _, d := range [4][2]int{{1, 0}, {-1, 0}, {0, 1}, {0, -1}} {
            ni, nj := cell[0]+d[0], cell[1]+d[1]
            if ni < 0 || ni >= len(grid) || nj < 0 || nj >= len(grid[0]) || grid[ni][nj] != 1 {
                continue
            }

            grid[ni][nj] = 0
            queue = append(queue, [2]int{ni, nj})
        }
    }

    return area
}

@h-masder h-masder Jun 21, 2026

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

話はそれますが、stack, queueなどの変数名は、役割にしたほうが見やすいかと思います。

func sink(grid [][]int, row, column int) int {
    area := 0

    grid[row][column] = 0
    frontier := [][2]int{{row, column}}

    for len(frontier) > 0 {
        position := frontier[0] // cell := stack[len(stack)-1] を変更
        frontier = frontier[1:] // stack = stack[:len(stack)-1]

        area++

        for _, direction := range [4][2]int{{1, 0}, {-1, 0}, {0, 1}, {0, -1}} {
            neighborRow := position[0] + direction[0]
            neighborColumn := position[1] + direction[1]

            if neighborRow < 0 || neighborRow >= len(grid) {
                continue
            }
            if neighborColumn < 0 || neighborColumn >= len(grid[0]) {
                continue
            }
            if grid[neighborRow][neighborColumn] != 1 {
                continue
            }

            grid[neighborRow][neighborColumn] = 0
            frontier = append(frontier, [2]int{neighborRow, neighborColumn})
        }
    }

    return area
}

@h-masder h-masder Jun 21, 2026

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

話を元に戻して、
最初の幅優先探索は、深さごとに管理されておらず好みではないので、私は以下のように書いたりします。

func sink(grid [][]int, i, j int) int {
    area := 0

    grid[i][j] = 0
    frontier := [][2]int{{i, j}}

    for len(frontier) > 0 {
        nextFrontier := [][2]int{}
        for _, position := range frontier {
            area++
            for _, direction := range [4][2]int{{1, 0}, {-1, 0}, {0, 1}, {0, -1}} {
                neighborRow := position[0] + direction[0]
                neighborColumn := position[1] + direction[1]

                if neighborRow < 0 || neighborRow >= len(grid) {
                    continue
                }
                if neighborColumn < 0 || neighborColumn >= len(grid[0]) {
                    continue
                }
                if grid[neighborRow][neighborColumn] != 1 {
                    continue
                }

                grid[neighborRow][neighborColumn] = 0
                nextFrontier = append(nextFrontier, [2]int{neighborRow, neighborColumn})
            }
        }
        frontier = nextFrontier
    }
    return 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.

ありがとうございます!
frontier, directionという命名,とてもよいですね.
dはdeltaくらいのつもりでしたが,directionのほうが役割と一致していてよさそうです.

一点area++の位置だけ,もうひとつ外側のループに配置すべきかなとは思いました.

それはともかく,コメントありがとうございます


- 他の人のコードを読む
- https://github.com/hiro111208/leetcode/pull/37/changes
- 数直線のように並べるのはわかりやすそう
- https://github.com/hiroki-horiguchi-dev/leetcode/pull/18
- water, landを定数にというのはよさそう
- https://github.com/nicah4o/arai60/pull/18
- i, jではなく,r(ow), c(olumn) を使うというのはなるほど
- union-findについて調べておくとよさそう
- https://github.com/jjysogfy/arai60-202603/pull/8
- https://github.com/quinn-sasha/leetcode/pull/30
- https://github.com/h-masder/Arai60/pull/19

- Union-Find について
- ツリー構造
- Disjoint Set Union (DSU) とも呼ばれる
- 主に使う場面は,グループの結合と,同じグループかどうかの判定
- 基本操作
- find(x): xが属している代表元を返す
- union(x, y): xのグループとyのグループを結合する
- 動作
- 初期化で,全ノードをすべてバラバラ(ルートしかない)のツリーとする
- union処理で,ノードを一つにまとめる(片側をもう片側のparentにする)
- 小さいツリーを大きいツリーにつなげる(こうするとツリーが深くなりにくくなるらしい)
- find処理では,基本的に代表元を返すだけだが,経路圧縮と言って,大元の代表元に繋ぎ直す処理も行う. こうすると,ツリーが短く保たれる.

# 3回目

```go
const (
WATER = iota
LAND
)

func maxAreaOfIsland(grid [][]int) int {
maxArea := 0
for r := range grid {
for c := range grid[r] {
if grid[r][c] == LAND {
maxArea = max(maxArea, sink(grid, r, c))
}
}
}
return maxArea
}

func sink(grid [][]int, row, column int) int {
area := 0
grid[row][column] = WATER
stack := [][2]int{{row, column}}

for len(stack) > 0 {
cell := stack[len(stack)-1]
stack = stack[:len(stack)-1]
area++

for _, d := range [4][2]int{{1, 0}, {-1, 0}, {0, 1}, {0, -1}} {
nr, nc := cell[0]+d[0], cell[1]+d[1]
if nr < 0 || len(grid) <= nr {
continue
}
if nc < 0 || len(grid[0]) <= nc {
continue
}
if grid[nr][nc] != LAND {
continue
}

grid[nr][nc] = WATER
stack = append(stack, [2]int{nr, nc})
}
}
return area
}
```
- これを3回繰り返した