-
Notifications
You must be signed in to change notification settings - Fork 0
695. Max Area of Island #18
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| 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)) | ||
| } | ||
| } | ||
| } | ||
| 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 { | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe 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
}
Owner
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. そう思います.3回目はそのように書いてみました. There was a problem hiding this comment. Choose a reason for hiding this commentThe 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
}
Owner
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe 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はまだ実装がスッとでてくる状態じゃなかったので,次回以降にまた取り組むことにした | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. DFS loopが書けているので、そこからBFSを理解するとよさそうです。 深さ優先探索では Last In First Out (LIFO) によって探索を進めます。 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
}There was a problem hiding this comment. Choose a reason for hiding this commentThe 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
}There was a problem hiding this comment. Choose a reason for hiding this commentThe 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
}
Owner
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. ありがとうございます! 一点 それはともかく,コメントありがとうございます |
||
|
|
||
| - 他の人のコードを読む | ||
| - 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回繰り返した | ||
Uh oh!
There was an error while loading. Please reload this page.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
sinkのかわりに島の大きさを測る関数名がよいと思いました。
measureIslandSizeなどはいかがでしょうか?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
これはたしかに.自分では見逃してました.
自分の感覚だと,面積を測定するというように捉えていたので,
measureIslandAreaとしたいと思いました.