feat(viz): backtracking, greedy, intervals, matrix
This commit is contained in:
@@ -0,0 +1,567 @@
|
||||
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')],
|
||||
},
|
||||
},
|
||||
],
|
||||
};
|
||||
Reference in New Issue
Block a user