Files
codetutor/frontend/src/content/algorithms/number-of-islands.ts
T

568 lines
17 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
import type { AlgorithmDefinition, GridCellState } from '@/lib/visualizations/types';
/**
* Number of Islands (LeetCode 200)
*
* Grid:
* ["1","1","0","0","0"]
* ["1","1","0","0","0"]
* ["0","0","1","0","0"]
* ["0","0","0","1","1"]
*
* Output: 3 islands
* Island 1: (0,0), (0,1), (1,0), (1,1)
* Island 2: (2,2)
* Island 3: (3,3), (3,4)
*/
// Grid data (4 rows x 5 cols)
const GRID = [
['1', '1', '0', '0', '0'],
['1', '1', '0', '0', '0'],
['0', '0', '1', '0', '0'],
['0', '0', '0', '1', '1'],
];
// Helper to create grid state with cell highlighting
function createIslandGrid(
visitedCells: Set<string>,
currentCell?: string,
exploringCells?: Set<string>,
label = 'Island Grid'
) {
const cells: GridCellState[][] = GRID.map((row, r) =>
row.map((val, c) => {
const key = `${r}-${c}`;
let state: GridCellState['state'] = val === '0' ? 'dimmed' : 'normal';
if (visitedCells.has(key)) state = 'success';
if (exploringCells?.has(key)) state = 'computing';
if (key === currentCell) state = 'highlighted';
return { id: `cell-${r}-${c}`, value: val, row: r, col: c, state };
})
);
return {
id: 'island-grid',
cells,
rowLabels: [0, 1, 2, 3],
colLabels: [0, 1, 2, 3, 4],
label,
};
}
// Helper to create initial grid showing all land/water
function createInitialGrid() {
const cells: GridCellState[][] = GRID.map((row, r) =>
row.map((val, c) => ({
id: `cell-${r}-${c}`,
value: val,
row: r,
col: c,
state: val === '0' ? 'dimmed' : 'normal',
}))
);
return {
id: 'island-grid',
cells,
rowLabels: [0, 1, 2, 3],
colLabels: [0, 1, 2, 3, 4],
label: 'Island Grid',
};
}
export const numberOfIslandsAlgorithm: AlgorithmDefinition = {
id: 'number-of-islands',
title: 'Number of Islands - Matrix Traversal',
slug: 'number-of-islands',
pattern: {
name: 'Matrix Traversal',
description:
'Explore a 2D grid using DFS or BFS to find connected components, paths, or regions.',
},
problemStatement:
'Given a 2D grid of \'1\'s (land) and \'0\'s (water), count the number of islands. An island is surrounded by water and formed by connecting adjacent lands horizontally or vertically.',
intuition:
'Think of the grid as a map. Each unvisited land cell could be the start of a new island. Use DFS to explore all connected land cells from a starting point, marking them visited. Each time we start a new DFS from an unvisited land cell, we\'ve found a new island.',
code: {
language: 'python',
code: `def numIslands(grid: list[list[str]]) -> int:
if not grid:
return 0
rows, cols = len(grid), len(grid[0])
count = 0
def dfs(r: int, c: int):
if r < 0 or r >= rows or c < 0 or c >= cols:
return
if grid[r][c] == '0':
return
grid[r][c] = '0' # Mark visited
dfs(r + 1, c) # Down
dfs(r - 1, c) # Up
dfs(r, c + 1) # Right
dfs(r, c - 1) # Left
for r in range(rows):
for c in range(cols):
if grid[r][c] == '1':
count += 1
dfs(r, c)
return count`,
},
initialExample: {
input: {
grid: [
['1', '1', '0', '0', '0'],
['1', '1', '0', '0', '0'],
['0', '0', '1', '0', '0'],
['0', '0', '0', '1', '1'],
],
},
expected: 3,
},
steps: [
// ==========================================
// Phase 1: Problem (2 steps)
// ==========================================
{
id: 'problem-1',
phase: 'problem',
explanation:
'We have a 4x5 grid where \'1\' represents land and \'0\' represents water. Our goal is to count distinct islands (connected land regions).',
dataState: {
arrays: [],
pointers: [],
variables: [],
calculations: [],
grids: [createInitialGrid()],
},
},
{
id: 'problem-2',
phase: 'problem',
explanation:
'Land cells are connected horizontally or vertically (not diagonally). Looking at this grid, we can visually spot 3 separate islands.',
dataState: {
arrays: [],
pointers: [],
variables: [
{ id: 'islands', name: 'Expected', value: '3 islands' },
],
calculations: [],
grids: [createInitialGrid()],
},
},
// ==========================================
// Phase 2: Intuition (3 steps)
// ==========================================
{
id: 'intuition-1',
phase: 'intuition',
explanation:
'Strategy: Scan the grid cell by cell. When we find an unvisited land cell (\'1\'), we\'ve discovered a new island. Increment count and explore all connected land using DFS.',
dataState: {
arrays: [],
pointers: [],
variables: [
{ id: 'count', name: 'islandCount', value: 0 },
],
calculations: [],
grids: [createIslandGrid(new Set(), '0-0')],
},
},
{
id: 'intuition-2',
phase: 'intuition',
explanation:
'DFS explores in 4 directions (up, down, left, right). As we visit each land cell, we mark it as visited by changing \'1\' to \'0\'. This prevents counting the same island twice.',
dataState: {
arrays: [],
pointers: [],
variables: [
{ id: 'count', name: 'islandCount', value: 1 },
],
calculations: [
{ id: 'calc-1', expression: 'DFS: (0,0) → neighbors', result: '4 directions', position: 'above' },
],
grids: [createIslandGrid(new Set(), '0-0', new Set(['0-1', '1-0']))],
},
},
{
id: 'intuition-3',
phase: 'intuition',
explanation:
'Key insight: Each time we start DFS from a new unvisited \'1\', we\'ve found a complete new island. The DFS call explores the entire island before returning.',
dataState: {
arrays: [],
pointers: [],
variables: [
{ id: 'count', name: 'islandCount', value: 1 },
],
calculations: [],
grids: [createIslandGrid(new Set(['0-0', '0-1', '1-0', '1-1']))],
},
},
// ==========================================
// Phase 3: Pattern (2 steps)
// ==========================================
{
id: 'pattern-1',
phase: 'pattern',
explanation:
'Matrix Traversal pattern: Treat the grid as an implicit graph where each cell is a node, and adjacent cells are edges. DFS/BFS finds connected components.',
dataState: {
arrays: [],
pointers: [],
variables: [
{ id: 'pattern', name: 'Pattern', value: 'Connected Components' },
],
calculations: [],
grids: [createInitialGrid()],
},
},
{
id: 'pattern-2',
phase: 'pattern',
explanation:
'Time: O(rows × cols) - we visit each cell at most once. Space: O(rows × cols) in worst case for DFS recursion stack (all land).',
dataState: {
arrays: [],
pointers: [],
variables: [
{ id: 'time', name: 'Time', value: 'O(m × n)' },
{ id: 'space', name: 'Space', value: 'O(m × n)' },
],
calculations: [],
grids: [createInitialGrid()],
},
},
// ==========================================
// Phase 4: Code (4 steps)
// ==========================================
{
id: 'code-1',
phase: 'code',
explanation:
'Initialize: Get grid dimensions, set island count to 0. We\'ll modify the grid in-place to track visited cells.',
codeLine: 5,
codeHighlightLines: [2, 3, 4, 5, 6],
dataState: {
arrays: [],
pointers: [],
variables: [
{ id: 'rows', name: 'rows', value: 4 },
{ id: 'cols', name: 'cols', value: 5 },
{ id: 'count', name: 'count', value: 0 },
],
calculations: [],
grids: [createInitialGrid()],
},
},
{
id: 'code-2',
phase: 'code',
explanation:
'DFS function: Base cases return early if out of bounds or cell is water. Otherwise, mark cell visited and recurse in all 4 directions.',
codeLine: 12,
codeHighlightLines: [8, 9, 10, 11, 12, 13, 14, 15, 16, 17],
dataState: {
arrays: [],
pointers: [],
variables: [],
calculations: [
{ id: 'calc-1', expression: 'dfs(r, c)', result: 'explore & mark', position: 'above' },
],
grids: [createInitialGrid()],
},
},
{
id: 'code-3',
phase: 'code',
explanation:
'Main loop: Scan every cell. When we find \'1\' (unvisited land), increment count and run DFS to mark entire island as visited.',
codeLine: 22,
codeHighlightLines: [19, 20, 21, 22, 23],
dataState: {
arrays: [],
pointers: [],
variables: [],
calculations: [],
grids: [createIslandGrid(new Set(), '0-0')],
},
},
{
id: 'code-4',
phase: 'code',
explanation:
'Return the final count. Each DFS call discovers one complete island, so count equals the number of islands.',
codeLine: 25,
dataState: {
arrays: [],
pointers: [],
variables: [
{ id: 'count', name: 'count', value: '?' },
],
calculations: [],
grids: [createInitialGrid()],
},
},
// ==========================================
// Phase 5: Execution (~12 steps)
// ==========================================
{
id: 'exec-1',
phase: 'execution',
explanation:
'Start scanning at (0,0). Found \'1\' - this is unvisited land! Increment count to 1 and start DFS to explore this island.',
codeLine: 21,
decision: {
question: 'Is cell (0,0) land?',
answer: 'Yes, grid[0][0] = \'1\'',
action: 'New island found! count++, start DFS',
},
dataState: {
arrays: [],
pointers: [],
variables: [
{ id: 'r', name: 'r', value: 0 },
{ id: 'c', name: 'c', value: 0 },
{ id: 'count', name: 'count', value: 1 },
],
calculations: [],
grids: [createIslandGrid(new Set(), '0-0')],
gridPointers: [
{ id: 'ptr-curr', name: 'current', row: 0, col: 0, color: 'current' },
],
},
},
{
id: 'exec-2',
phase: 'execution',
explanation:
'DFS from (0,0): Mark as visited, explore neighbors. Cell (0,1) is land - recurse there. Cell (1,0) is also land.',
codeLine: 13,
dataState: {
arrays: [],
pointers: [],
variables: [
{ id: 'count', name: 'count', value: 1 },
],
calculations: [
{ id: 'calc-1', expression: 'dfs(0,0) → dfs(0,1), dfs(1,0)', result: 'exploring', position: 'above' },
],
grids: [createIslandGrid(new Set(['0-0']), undefined, new Set(['0-1', '1-0']))],
gridPointers: [
{ id: 'ptr-curr', name: 'visited', row: 0, col: 0, color: 'result' },
],
},
},
{
id: 'exec-3',
phase: 'execution',
explanation:
'DFS explores (0,1), then (1,0), then (1,1). All 4 cells of island 1 are now marked as visited (green).',
codeLine: 13,
dataState: {
arrays: [],
pointers: [],
variables: [
{ id: 'count', name: 'count', value: 1 },
],
calculations: [],
grids: [createIslandGrid(new Set(['0-0', '0-1', '1-0', '1-1']))],
},
},
{
id: 'exec-4',
phase: 'execution',
explanation:
'DFS complete for island 1. Continue scanning: (0,2), (0,3), (0,4) are all water (\'0\'). Skip them.',
codeLine: 20,
dataState: {
arrays: [],
pointers: [],
variables: [
{ id: 'r', name: 'r', value: 0 },
{ id: 'c', name: 'c', value: 2 },
{ id: 'count', name: 'count', value: 1 },
],
calculations: [],
grids: [createIslandGrid(new Set(['0-0', '0-1', '1-0', '1-1']), '0-2')],
gridPointers: [
{ id: 'ptr-scan', name: 'scan', row: 0, col: 2, color: 'current' },
],
},
},
{
id: 'exec-5',
phase: 'execution',
explanation:
'Scanning row 1: (1,0) and (1,1) already visited. (1,2), (1,3), (1,4) are water. Continue to row 2.',
codeLine: 20,
dataState: {
arrays: [],
pointers: [],
variables: [
{ id: 'r', name: 'r', value: 1 },
{ id: 'count', name: 'count', value: 1 },
],
calculations: [],
grids: [createIslandGrid(new Set(['0-0', '0-1', '1-0', '1-1']))],
},
},
{
id: 'exec-6',
phase: 'execution',
explanation:
'Scan (2,0), (2,1) - water. At (2,2): Found \'1\'! This is a new unvisited land cell. Increment count to 2.',
codeLine: 21,
decision: {
question: 'Is cell (2,2) land?',
answer: 'Yes, grid[2][2] = \'1\'',
action: 'New island found! count++',
},
dataState: {
arrays: [],
pointers: [],
variables: [
{ id: 'r', name: 'r', value: 2 },
{ id: 'c', name: 'c', value: 2 },
{ id: 'count', name: 'count', value: 2 },
],
calculations: [],
grids: [createIslandGrid(new Set(['0-0', '0-1', '1-0', '1-1']), '2-2')],
gridPointers: [
{ id: 'ptr-curr', name: 'current', row: 2, col: 2, color: 'current' },
],
},
},
{
id: 'exec-7',
phase: 'execution',
explanation:
'DFS from (2,2): Check all 4 neighbors - all are water or out of bounds. This is a single-cell island. Mark visited.',
codeLine: 13,
dataState: {
arrays: [],
pointers: [],
variables: [
{ id: 'count', name: 'count', value: 2 },
],
calculations: [
{ id: 'calc-1', expression: 'dfs(2,2) neighbors', result: 'all water', position: 'above' },
],
grids: [createIslandGrid(new Set(['0-0', '0-1', '1-0', '1-1', '2-2']))],
},
},
{
id: 'exec-8',
phase: 'execution',
explanation:
'Continue scanning row 2 and row 3: (2,3), (2,4), (3,0), (3,1), (3,2) are all water. Skip them.',
codeLine: 20,
dataState: {
arrays: [],
pointers: [],
variables: [
{ id: 'r', name: 'r', value: 3 },
{ id: 'c', name: 'c', value: 2 },
{ id: 'count', name: 'count', value: 2 },
],
calculations: [],
grids: [createIslandGrid(new Set(['0-0', '0-1', '1-0', '1-1', '2-2']), '3-2')],
gridPointers: [
{ id: 'ptr-scan', name: 'scan', row: 3, col: 2, color: 'current' },
],
},
},
{
id: 'exec-9',
phase: 'execution',
explanation:
'At (3,3): Found \'1\'! This is another new island. Increment count to 3 and start DFS.',
codeLine: 21,
decision: {
question: 'Is cell (3,3) land?',
answer: 'Yes, grid[3][3] = \'1\'',
action: 'New island found! count++',
},
dataState: {
arrays: [],
pointers: [],
variables: [
{ id: 'r', name: 'r', value: 3 },
{ id: 'c', name: 'c', value: 3 },
{ id: 'count', name: 'count', value: 3 },
],
calculations: [],
grids: [createIslandGrid(new Set(['0-0', '0-1', '1-0', '1-1', '2-2']), '3-3')],
gridPointers: [
{ id: 'ptr-curr', name: 'current', row: 3, col: 3, color: 'current' },
],
},
},
{
id: 'exec-10',
phase: 'execution',
explanation:
'DFS from (3,3): Mark visited, explore neighbors. Found (3,4) is also land! Recurse to explore it.',
codeLine: 13,
dataState: {
arrays: [],
pointers: [],
variables: [
{ id: 'count', name: 'count', value: 3 },
],
calculations: [
{ id: 'calc-1', expression: 'dfs(3,3) → dfs(3,4)', result: 'found neighbor', position: 'above' },
],
grids: [createIslandGrid(new Set(['0-0', '0-1', '1-0', '1-1', '2-2', '3-3']), undefined, new Set(['3-4']))],
gridPointers: [
{ id: 'ptr-curr', name: 'exploring', row: 3, col: 4, color: 'current' },
],
},
},
{
id: 'exec-11',
phase: 'execution',
explanation:
'DFS visits (3,4): Mark visited. No more land neighbors. Island 3 complete with cells (3,3) and (3,4).',
codeLine: 13,
dataState: {
arrays: [],
pointers: [],
variables: [
{ id: 'count', name: 'count', value: 3 },
],
calculations: [],
grids: [createIslandGrid(new Set(['0-0', '0-1', '1-0', '1-1', '2-2', '3-3', '3-4']))],
},
},
{
id: 'exec-12',
phase: 'execution',
explanation:
'Scan complete! All cells visited. We found 3 islands: (1) 4 cells top-left, (2) 1 cell middle, (3) 2 cells bottom-right.',
codeLine: 25,
dataState: {
arrays: [],
pointers: [],
variables: [
{ id: 'count', name: 'count', value: 3, derivation: '3 connected components' },
],
calculations: [
{ id: 'calc-1', expression: 'return count', result: '3', position: 'above' },
],
grids: [createIslandGrid(new Set(['0-0', '0-1', '1-0', '1-1', '2-2', '3-3', '3-4']), undefined, undefined, 'Result: 3 Islands')],
},
},
],
};