questions F-L
This commit is contained in:
218
backend/data/questions/island-perimeter.yaml
Normal file
218
backend/data/questions/island-perimeter.yaml
Normal file
@@ -0,0 +1,218 @@
|
||||
title: Island Perimeter
|
||||
slug: island-perimeter
|
||||
difficulty: easy
|
||||
leetcode_id: 463
|
||||
leetcode_url: https://leetcode.com/problems/island-perimeter/
|
||||
categories:
|
||||
- arrays
|
||||
- math
|
||||
patterns:
|
||||
- matrix-traversal
|
||||
|
||||
description: |
|
||||
You are given a `row x col` grid representing a map where `grid[i][j] = 1` represents land and `grid[i][j] = 0` represents water.
|
||||
|
||||
Grid cells are connected **horizontally/vertically** (not diagonally). The `grid` is completely surrounded by water, and there is exactly one island (i.e., one or more connected land cells).
|
||||
|
||||
The island doesn't have "lakes", meaning the water inside isn't connected to the water around the island. One cell is a square with side length 1. The grid is rectangular, width and height don't exceed 100.
|
||||
|
||||
Determine the perimeter of the island.
|
||||
|
||||
constraints: |
|
||||
- `row == grid.length`
|
||||
- `col == grid[i].length`
|
||||
- `1 <= row, col <= 100`
|
||||
- `grid[i][j]` is `0` or `1`
|
||||
- There is exactly one island in `grid`
|
||||
|
||||
examples:
|
||||
- input: "grid = [[0,1,0,0],[1,1,1,0],[0,1,0,0],[1,1,0,0]]"
|
||||
output: "16"
|
||||
explanation: "The perimeter is formed by counting the edges of land cells that touch water or the grid boundary."
|
||||
- input: "grid = [[1]]"
|
||||
output: "4"
|
||||
explanation: "A single land cell has 4 sides, all contributing to the perimeter."
|
||||
- input: "grid = [[1,0]]"
|
||||
output: "4"
|
||||
explanation: "The single land cell is surrounded by water on one side and grid boundaries on the others."
|
||||
|
||||
explanation:
|
||||
intuition: |
|
||||
Imagine looking at the island from above, like a map. Each land cell is a square with 4 sides. The **perimeter** is the total length of the island's outer boundary — every edge where land meets water or the edge of the grid.
|
||||
|
||||
Think of it like this: if you placed a fence around every land cell, you'd have 4 fence segments per cell. But when two land cells are **adjacent** (share an edge), those touching sides are *internal* to the island — they shouldn't count toward the perimeter.
|
||||
|
||||
The key insight is: **each land cell contributes 4 to the perimeter, minus 2 for each neighbour it has**. Why minus 2? Because when two cells share an edge, that edge is counted by both cells, but it's actually an internal edge that shouldn't be part of the perimeter. We lose 1 from each cell's contribution.
|
||||
|
||||
Alternatively, you can think of it as: for each land cell, count how many of its 4 sides touch water or the boundary. That's its direct contribution to the perimeter.
|
||||
|
||||
approach: |
|
||||
We solve this using a **Simple Counting** approach:
|
||||
|
||||
**Step 1: Initialise counters**
|
||||
|
||||
- `perimeter`: Set to `0` to accumulate the total perimeter
|
||||
|
||||
|
||||
|
||||
**Step 2: Iterate through every cell in the grid**
|
||||
|
||||
- Use nested loops to visit each cell at position `(i, j)`
|
||||
- If `grid[i][j] == 1` (it's a land cell), count its perimeter contribution
|
||||
|
||||
|
||||
|
||||
**Step 3: For each land cell, check all 4 sides**
|
||||
|
||||
- **Top side**: If `i == 0` (top boundary) or `grid[i-1][j] == 0` (water above), add 1
|
||||
- **Bottom side**: If `i == rows-1` (bottom boundary) or `grid[i+1][j] == 0` (water below), add 1
|
||||
- **Left side**: If `j == 0` (left boundary) or `grid[i][j-1] == 0` (water left), add 1
|
||||
- **Right side**: If `j == cols-1` (right boundary) or `grid[i][j+1] == 0` (water right), add 1
|
||||
|
||||
|
||||
|
||||
**Step 4: Return the total perimeter**
|
||||
|
||||
- After checking all cells, return the accumulated `perimeter`
|
||||
|
||||
|
||||
|
||||
This approach works because we directly count the edges that form the island's boundary — any edge touching water or the grid boundary contributes to the perimeter.
|
||||
|
||||
common_pitfalls:
|
||||
- title: Counting Internal Edges
|
||||
description: |
|
||||
A common mistake is to count 4 for every land cell without subtracting shared edges between adjacent land cells.
|
||||
|
||||
For example, if two land cells are horizontally adjacent, the edge between them is internal — it's not part of the perimeter. You must either subtract these internal edges or only count edges that touch water/boundary.
|
||||
|
||||
With the grid `[[1,1]]`, simply counting `4 * 2 = 8` is wrong. The correct answer is `6` because the two cells share one edge.
|
||||
wrong_approach: "Count 4 for every land cell"
|
||||
correct_approach: "Count edges touching water or boundary only"
|
||||
|
||||
- title: Index Out of Bounds
|
||||
description: |
|
||||
When checking neighbours, it's easy to accidentally access `grid[i-1][j]` when `i == 0`, causing an index error.
|
||||
|
||||
Always check boundary conditions **before** accessing neighbouring cells. The order of conditions matters: `i == 0 or grid[i-1][j] == 0` short-circuits correctly, but `grid[i-1][j] == 0 or i == 0` will crash.
|
||||
wrong_approach: "Check neighbour first, then boundary"
|
||||
correct_approach: "Check boundary first (short-circuit evaluation)"
|
||||
|
||||
- title: Overcomplicating with DFS/BFS
|
||||
description: |
|
||||
While DFS or BFS can solve this problem, they're unnecessary complexity for this particular task. The problem states there's exactly one island with no lakes, so you don't need to track visited cells or flood-fill.
|
||||
|
||||
A simple double loop examining each cell independently is cleaner and equally efficient at O(rows * cols).
|
||||
wrong_approach: "Implement full DFS/BFS traversal"
|
||||
correct_approach: "Simple iteration checking each cell's edges"
|
||||
|
||||
key_takeaways:
|
||||
- "**Count contributions**: Each land cell contributes its edges that touch water or boundaries"
|
||||
- "**Boundary checks first**: Use short-circuit evaluation to avoid index errors when checking neighbours"
|
||||
- "**Matrix traversal pattern**: Iterating through a 2D grid with nested loops is fundamental for many problems"
|
||||
- "**Simplicity wins**: Don't overcomplicate — this problem doesn't need DFS/BFS despite being tagged as such"
|
||||
|
||||
time_complexity: "O(m * n). We visit each cell in the grid exactly once, where m is the number of rows and n is the number of columns."
|
||||
space_complexity: "O(1). We only use a single counter variable regardless of input size."
|
||||
|
||||
solutions:
|
||||
- approach_name: Simple Counting
|
||||
is_optimal: true
|
||||
code: |
|
||||
def island_perimeter(grid: list[list[int]]) -> int:
|
||||
rows, cols = len(grid), len(grid[0])
|
||||
perimeter = 0
|
||||
|
||||
for i in range(rows):
|
||||
for j in range(cols):
|
||||
# Only process land cells
|
||||
if grid[i][j] == 1:
|
||||
# Check all 4 sides - add 1 for each edge touching water/boundary
|
||||
|
||||
# Top: boundary or water above
|
||||
if i == 0 or grid[i - 1][j] == 0:
|
||||
perimeter += 1
|
||||
|
||||
# Bottom: boundary or water below
|
||||
if i == rows - 1 or grid[i + 1][j] == 0:
|
||||
perimeter += 1
|
||||
|
||||
# Left: boundary or water to the left
|
||||
if j == 0 or grid[i][j - 1] == 0:
|
||||
perimeter += 1
|
||||
|
||||
# Right: boundary or water to the right
|
||||
if j == cols - 1 or grid[i][j + 1] == 0:
|
||||
perimeter += 1
|
||||
|
||||
return perimeter
|
||||
explanation: |
|
||||
**Time Complexity:** O(m * n) — We iterate through every cell once.
|
||||
|
||||
**Space Complexity:** O(1) — Only a counter variable is used.
|
||||
|
||||
For each land cell, we check its 4 neighbours. If a neighbour is water or out of bounds, that edge contributes to the perimeter. This direct approach is clean and efficient.
|
||||
|
||||
- approach_name: Count Land and Subtract Neighbours
|
||||
is_optimal: true
|
||||
code: |
|
||||
def island_perimeter(grid: list[list[int]]) -> int:
|
||||
rows, cols = len(grid), len(grid[0])
|
||||
land_cells = 0
|
||||
neighbour_edges = 0
|
||||
|
||||
for i in range(rows):
|
||||
for j in range(cols):
|
||||
if grid[i][j] == 1:
|
||||
land_cells += 1
|
||||
|
||||
# Count neighbours (only check right and down to avoid double counting)
|
||||
if i < rows - 1 and grid[i + 1][j] == 1:
|
||||
neighbour_edges += 1
|
||||
if j < cols - 1 and grid[i][j + 1] == 1:
|
||||
neighbour_edges += 1
|
||||
|
||||
# Each land cell contributes 4, each shared edge removes 2 from perimeter
|
||||
return land_cells * 4 - neighbour_edges * 2
|
||||
explanation: |
|
||||
**Time Complexity:** O(m * n) — Single pass through the grid.
|
||||
|
||||
**Space Complexity:** O(1) — Two counter variables.
|
||||
|
||||
This alternative approach uses the formula: `perimeter = 4 * land_cells - 2 * shared_edges`. Each land cell starts with 4 sides. Each pair of adjacent land cells shares an edge, removing 2 from the total perimeter (1 from each cell). We only check right and down neighbours to avoid counting each shared edge twice.
|
||||
|
||||
- approach_name: DFS Traversal
|
||||
is_optimal: false
|
||||
code: |
|
||||
def island_perimeter(grid: list[list[int]]) -> int:
|
||||
rows, cols = len(grid), len(grid[0])
|
||||
visited = set()
|
||||
|
||||
def dfs(i: int, j: int) -> int:
|
||||
# Out of bounds or water - this edge contributes 1 to perimeter
|
||||
if i < 0 or i >= rows or j < 0 or j >= cols or grid[i][j] == 0:
|
||||
return 1
|
||||
|
||||
# Already visited - don't count again
|
||||
if (i, j) in visited:
|
||||
return 0
|
||||
|
||||
visited.add((i, j))
|
||||
|
||||
# Explore all 4 directions and sum up perimeter
|
||||
return (dfs(i - 1, j) + dfs(i + 1, j) +
|
||||
dfs(i, j - 1) + dfs(i, j + 1))
|
||||
|
||||
# Find the first land cell and start DFS
|
||||
for i in range(rows):
|
||||
for j in range(cols):
|
||||
if grid[i][j] == 1:
|
||||
return dfs(i, j)
|
||||
|
||||
return 0
|
||||
explanation: |
|
||||
**Time Complexity:** O(m * n) — Each cell is visited at most once.
|
||||
|
||||
**Space Complexity:** O(m * n) — Visited set and recursion stack in worst case.
|
||||
|
||||
DFS explores the island by recursively visiting connected land cells. When we hit water or the boundary, that's a perimeter edge (return 1). When we hit a visited cell, return 0 to avoid double counting. While correct, this uses extra space and is overkill for this problem.
|
||||
Reference in New Issue
Block a user