feat(viz): backtracking, greedy, intervals, matrix
This commit is contained in:
490
frontend/src/content/algorithms/merge-intervals.ts
Normal file
490
frontend/src/content/algorithms/merge-intervals.ts
Normal file
@@ -0,0 +1,490 @@
|
||||
import type { AlgorithmDefinition, IntervalState, IntervalListState } from '@/lib/visualizations/types';
|
||||
|
||||
/**
|
||||
* Merge Intervals (LeetCode 56)
|
||||
*
|
||||
* Input: intervals = [[1,3], [2,6], [8,10], [15,18]]
|
||||
* Output: [[1,6], [8,10], [15,18]]
|
||||
*
|
||||
* Sort by start time, then merge overlapping intervals in one pass.
|
||||
*/
|
||||
|
||||
// Helper to create input intervals state
|
||||
function createInputIntervals(
|
||||
intervals: [number, number][],
|
||||
currentIndex?: number,
|
||||
processedIndices: number[] = []
|
||||
): IntervalListState {
|
||||
return {
|
||||
id: 'input',
|
||||
label: 'Input Intervals (sorted)',
|
||||
minValue: 0,
|
||||
maxValue: 20,
|
||||
intervals: intervals.map(([start, end], index) => ({
|
||||
id: `input-${index}`,
|
||||
start,
|
||||
end,
|
||||
label: `[${start},${end}]`,
|
||||
state: (index === currentIndex
|
||||
? 'highlighted'
|
||||
: processedIndices.includes(index)
|
||||
? 'dimmed'
|
||||
: 'normal') as IntervalState['state'],
|
||||
})),
|
||||
};
|
||||
}
|
||||
|
||||
// Helper to create result intervals state
|
||||
function createResultIntervals(
|
||||
intervals: [number, number][],
|
||||
highlightLast = false,
|
||||
mergingLast = false
|
||||
): IntervalListState {
|
||||
return {
|
||||
id: 'result',
|
||||
label: 'Merged Result',
|
||||
minValue: 0,
|
||||
maxValue: 20,
|
||||
intervals: intervals.map(([start, end], index) => ({
|
||||
id: `result-${index}`,
|
||||
start,
|
||||
end,
|
||||
label: `[${start},${end}]`,
|
||||
state: (index === intervals.length - 1
|
||||
? mergingLast
|
||||
? 'merging'
|
||||
: highlightLast
|
||||
? 'highlighted'
|
||||
: 'merged'
|
||||
: 'merged') as IntervalState['state'],
|
||||
})),
|
||||
};
|
||||
}
|
||||
|
||||
export const mergeIntervalsAlgorithm: AlgorithmDefinition = {
|
||||
id: 'merge-intervals',
|
||||
title: 'Merge Intervals',
|
||||
slug: 'merge-intervals',
|
||||
pattern: {
|
||||
name: 'Intervals',
|
||||
description:
|
||||
'Sort intervals by start time, then process them linearly to merge overlapping ranges.',
|
||||
},
|
||||
problemStatement:
|
||||
'Given an array of intervals where intervals[i] = [start_i, end_i], merge all overlapping intervals and return an array of non-overlapping intervals.',
|
||||
intuition:
|
||||
'After sorting by start time, overlapping intervals become adjacent. Two sorted intervals [a,b] and [c,d] overlap if c <= b. When they overlap, merge them by extending the end to max(b, d).',
|
||||
code: {
|
||||
language: 'python',
|
||||
code: `def merge(intervals: list[list[int]]) -> list[list[int]]:
|
||||
if len(intervals) <= 1:
|
||||
return intervals
|
||||
|
||||
# Sort by start time
|
||||
intervals.sort(key=lambda x: x[0])
|
||||
|
||||
merged = [intervals[0]]
|
||||
|
||||
for current in intervals[1:]:
|
||||
last = merged[-1]
|
||||
|
||||
if current[0] <= last[1]: # Overlap
|
||||
last[1] = max(last[1], current[1])
|
||||
else:
|
||||
merged.append(current)
|
||||
|
||||
return merged`,
|
||||
},
|
||||
initialExample: {
|
||||
input: { intervals: [[1, 3], [2, 6], [8, 10], [15, 18]] },
|
||||
expected: [[1, 6], [8, 10], [15, 18]],
|
||||
},
|
||||
steps: [
|
||||
// ==========================================
|
||||
// Phase 1: Problem (2 steps)
|
||||
// ==========================================
|
||||
{
|
||||
id: 'problem-1',
|
||||
phase: 'problem',
|
||||
explanation:
|
||||
'Given intervals [[1,3], [2,6], [8,10], [15,18]], merge all overlapping intervals. Notice [1,3] and [2,6] share the range [2,3].',
|
||||
dataState: {
|
||||
arrays: [],
|
||||
pointers: [],
|
||||
variables: [],
|
||||
calculations: [],
|
||||
intervals: [
|
||||
createInputIntervals([[1, 3], [2, 6], [8, 10], [15, 18]]),
|
||||
],
|
||||
},
|
||||
},
|
||||
{
|
||||
id: 'problem-2',
|
||||
phase: 'problem',
|
||||
explanation:
|
||||
'Brute force: Compare every pair of intervals, O(n^2). But if intervals are sorted, we can detect overlaps in a single pass!',
|
||||
dataState: {
|
||||
arrays: [],
|
||||
pointers: [],
|
||||
variables: [
|
||||
{ id: 'brute', name: 'brute force', value: 'O(n^2)' },
|
||||
{ id: 'optimal', name: 'optimal', value: 'O(n log n)' },
|
||||
],
|
||||
calculations: [],
|
||||
intervals: [
|
||||
createInputIntervals([[1, 3], [2, 6], [8, 10], [15, 18]]),
|
||||
],
|
||||
},
|
||||
},
|
||||
|
||||
// ==========================================
|
||||
// Phase 2: Intuition (3 steps)
|
||||
// ==========================================
|
||||
{
|
||||
id: 'intuition-1',
|
||||
phase: 'intuition',
|
||||
explanation:
|
||||
'Key insight: When sorted by start time, overlapping intervals are always adjacent. No need to compare distant intervals!',
|
||||
dataState: {
|
||||
arrays: [],
|
||||
pointers: [],
|
||||
variables: [],
|
||||
calculations: [],
|
||||
intervals: [
|
||||
createInputIntervals([[1, 3], [2, 6], [8, 10], [15, 18]]),
|
||||
],
|
||||
},
|
||||
},
|
||||
{
|
||||
id: 'intuition-2',
|
||||
phase: 'intuition',
|
||||
explanation:
|
||||
'Two intervals [a,b] and [c,d] (with a <= c) overlap when c <= b. In our example, [1,3] and [2,6]: 2 <= 3, so they overlap!',
|
||||
dataState: {
|
||||
arrays: [],
|
||||
pointers: [],
|
||||
variables: [
|
||||
{ id: 'overlap', name: 'overlap condition', value: 'c <= b' },
|
||||
],
|
||||
calculations: [],
|
||||
intervals: [
|
||||
createInputIntervals([[1, 3], [2, 6], [8, 10], [15, 18]], 0),
|
||||
],
|
||||
},
|
||||
},
|
||||
{
|
||||
id: 'intuition-3',
|
||||
phase: 'intuition',
|
||||
explanation:
|
||||
'To merge, extend the end to max(b, d). [1,3] + [2,6] = [1, max(3,6)] = [1,6]. This covers both original ranges.',
|
||||
dataState: {
|
||||
arrays: [],
|
||||
pointers: [],
|
||||
variables: [
|
||||
{ id: 'merge', name: 'merge formula', value: '[a, max(b,d)]' },
|
||||
],
|
||||
calculations: [],
|
||||
intervals: [
|
||||
createInputIntervals([[1, 3], [2, 6], [8, 10], [15, 18]], 1),
|
||||
],
|
||||
},
|
||||
},
|
||||
|
||||
// ==========================================
|
||||
// Phase 3: Pattern (2 steps)
|
||||
// ==========================================
|
||||
{
|
||||
id: 'pattern-1',
|
||||
phase: 'pattern',
|
||||
explanation:
|
||||
'Intervals pattern: Sort by start time first. This transforms O(n^2) pair comparisons into O(n) sequential checks.',
|
||||
dataState: {
|
||||
arrays: [],
|
||||
pointers: [],
|
||||
variables: [
|
||||
{ id: 'pattern', name: 'pattern', value: 'sort + linear scan' },
|
||||
],
|
||||
calculations: [],
|
||||
intervals: [
|
||||
createInputIntervals([[1, 3], [2, 6], [8, 10], [15, 18]]),
|
||||
],
|
||||
},
|
||||
},
|
||||
{
|
||||
id: 'pattern-2',
|
||||
phase: 'pattern',
|
||||
explanation:
|
||||
'Build result incrementally: For each interval, either merge with the last result interval (if overlapping) or add as new.',
|
||||
dataState: {
|
||||
arrays: [],
|
||||
pointers: [],
|
||||
variables: [
|
||||
{ id: 'time', name: 'Time', value: 'O(n log n)' },
|
||||
{ id: 'space', name: 'Space', value: 'O(n)' },
|
||||
],
|
||||
calculations: [],
|
||||
intervals: [
|
||||
createInputIntervals([[1, 3], [2, 6], [8, 10], [15, 18]]),
|
||||
],
|
||||
},
|
||||
},
|
||||
|
||||
// ==========================================
|
||||
// Phase 4: Code (3 steps)
|
||||
// ==========================================
|
||||
{
|
||||
id: 'code-1',
|
||||
phase: 'code',
|
||||
explanation:
|
||||
'Sort intervals by start time. Our input is already sorted: [[1,3], [2,6], [8,10], [15,18]].',
|
||||
codeLine: 6,
|
||||
codeHighlightLines: [5, 6],
|
||||
dataState: {
|
||||
arrays: [],
|
||||
pointers: [],
|
||||
variables: [],
|
||||
calculations: [],
|
||||
intervals: [
|
||||
createInputIntervals([[1, 3], [2, 6], [8, 10], [15, 18]]),
|
||||
],
|
||||
},
|
||||
},
|
||||
{
|
||||
id: 'code-2',
|
||||
phase: 'code',
|
||||
explanation:
|
||||
'Initialize merged list with first interval. This becomes our "current" merged interval to potentially extend.',
|
||||
codeLine: 8,
|
||||
codeHighlightLines: [8],
|
||||
dataState: {
|
||||
arrays: [],
|
||||
pointers: [],
|
||||
variables: [],
|
||||
calculations: [],
|
||||
intervals: [
|
||||
createInputIntervals([[1, 3], [2, 6], [8, 10], [15, 18]], 0),
|
||||
createResultIntervals([[1, 3]], true),
|
||||
],
|
||||
},
|
||||
},
|
||||
{
|
||||
id: 'code-3',
|
||||
phase: 'code',
|
||||
explanation:
|
||||
'For each remaining interval: if it overlaps with the last merged (current[0] <= last[1]), extend. Otherwise, add as new interval.',
|
||||
codeLine: 13,
|
||||
codeHighlightLines: [10, 11, 13, 14, 15, 16],
|
||||
dataState: {
|
||||
arrays: [],
|
||||
pointers: [],
|
||||
variables: [],
|
||||
calculations: [],
|
||||
intervals: [
|
||||
createInputIntervals([[1, 3], [2, 6], [8, 10], [15, 18]]),
|
||||
createResultIntervals([[1, 3]]),
|
||||
],
|
||||
},
|
||||
},
|
||||
|
||||
// ==========================================
|
||||
// Phase 5: Execution (~10 steps)
|
||||
// ==========================================
|
||||
{
|
||||
id: 'exec-1',
|
||||
phase: 'execution',
|
||||
explanation:
|
||||
'Initialize: Add first interval [1,3] to merged result.',
|
||||
codeLine: 8,
|
||||
dataState: {
|
||||
arrays: [],
|
||||
pointers: [],
|
||||
variables: [
|
||||
{ id: 'merged', name: 'merged', value: '[[1,3]]' },
|
||||
],
|
||||
calculations: [],
|
||||
intervals: [
|
||||
createInputIntervals([[1, 3], [2, 6], [8, 10], [15, 18]], 0, []),
|
||||
createResultIntervals([[1, 3]], true),
|
||||
],
|
||||
},
|
||||
},
|
||||
{
|
||||
id: 'exec-2',
|
||||
phase: 'execution',
|
||||
explanation:
|
||||
'Process [2,6]: Check overlap with last merged [1,3]. Is 2 <= 3? Yes! They overlap.',
|
||||
codeLine: 13,
|
||||
decision: {
|
||||
question: 'Does current[0] <= last[1]?',
|
||||
answer: '2 <= 3 = true',
|
||||
action: 'Intervals overlap, merge them',
|
||||
},
|
||||
dataState: {
|
||||
arrays: [],
|
||||
pointers: [],
|
||||
variables: [
|
||||
{ id: 'current', name: 'current', value: '[2,6]' },
|
||||
{ id: 'last', name: 'last', value: '[1,3]' },
|
||||
],
|
||||
calculations: [],
|
||||
intervals: [
|
||||
createInputIntervals([[1, 3], [2, 6], [8, 10], [15, 18]], 1, [0]),
|
||||
createResultIntervals([[1, 3]], false, true),
|
||||
],
|
||||
},
|
||||
},
|
||||
{
|
||||
id: 'exec-3',
|
||||
phase: 'execution',
|
||||
explanation:
|
||||
'Merge: Extend last interval\'s end to max(3, 6) = 6. Result: [1,6].',
|
||||
codeLine: 14,
|
||||
dataState: {
|
||||
arrays: [],
|
||||
pointers: [],
|
||||
variables: [
|
||||
{ id: 'merged', name: 'merged', value: '[[1,6]]' },
|
||||
{ id: 'calc', name: 'new end', value: 'max(3,6) = 6' },
|
||||
],
|
||||
calculations: [],
|
||||
intervals: [
|
||||
createInputIntervals([[1, 3], [2, 6], [8, 10], [15, 18]], 1, [0]),
|
||||
createResultIntervals([[1, 6]], true),
|
||||
],
|
||||
},
|
||||
},
|
||||
{
|
||||
id: 'exec-4',
|
||||
phase: 'execution',
|
||||
explanation:
|
||||
'Process [8,10]: Check overlap with last merged [1,6]. Is 8 <= 6? No! No overlap.',
|
||||
codeLine: 13,
|
||||
decision: {
|
||||
question: 'Does current[0] <= last[1]?',
|
||||
answer: '8 <= 6 = false',
|
||||
action: 'No overlap, add as new interval',
|
||||
},
|
||||
dataState: {
|
||||
arrays: [],
|
||||
pointers: [],
|
||||
variables: [
|
||||
{ id: 'current', name: 'current', value: '[8,10]' },
|
||||
{ id: 'last', name: 'last', value: '[1,6]' },
|
||||
],
|
||||
calculations: [],
|
||||
intervals: [
|
||||
createInputIntervals([[1, 3], [2, 6], [8, 10], [15, 18]], 2, [0, 1]),
|
||||
createResultIntervals([[1, 6]]),
|
||||
],
|
||||
},
|
||||
},
|
||||
{
|
||||
id: 'exec-5',
|
||||
phase: 'execution',
|
||||
explanation:
|
||||
'Add [8,10] as a new interval in the result. Gap between [1,6] and [8,10] is preserved.',
|
||||
codeLine: 16,
|
||||
dataState: {
|
||||
arrays: [],
|
||||
pointers: [],
|
||||
variables: [
|
||||
{ id: 'merged', name: 'merged', value: '[[1,6], [8,10]]' },
|
||||
],
|
||||
calculations: [],
|
||||
intervals: [
|
||||
createInputIntervals([[1, 3], [2, 6], [8, 10], [15, 18]], 2, [0, 1]),
|
||||
createResultIntervals([[1, 6], [8, 10]], true),
|
||||
],
|
||||
},
|
||||
},
|
||||
{
|
||||
id: 'exec-6',
|
||||
phase: 'execution',
|
||||
explanation:
|
||||
'Process [15,18]: Check overlap with last merged [8,10]. Is 15 <= 10? No! No overlap.',
|
||||
codeLine: 13,
|
||||
decision: {
|
||||
question: 'Does current[0] <= last[1]?',
|
||||
answer: '15 <= 10 = false',
|
||||
action: 'No overlap, add as new interval',
|
||||
},
|
||||
dataState: {
|
||||
arrays: [],
|
||||
pointers: [],
|
||||
variables: [
|
||||
{ id: 'current', name: 'current', value: '[15,18]' },
|
||||
{ id: 'last', name: 'last', value: '[8,10]' },
|
||||
],
|
||||
calculations: [],
|
||||
intervals: [
|
||||
createInputIntervals([[1, 3], [2, 6], [8, 10], [15, 18]], 3, [0, 1, 2]),
|
||||
createResultIntervals([[1, 6], [8, 10]]),
|
||||
],
|
||||
},
|
||||
},
|
||||
{
|
||||
id: 'exec-7',
|
||||
phase: 'execution',
|
||||
explanation:
|
||||
'Add [15,18] as a new interval. Result now has 3 non-overlapping intervals.',
|
||||
codeLine: 16,
|
||||
dataState: {
|
||||
arrays: [],
|
||||
pointers: [],
|
||||
variables: [
|
||||
{ id: 'merged', name: 'merged', value: '[[1,6], [8,10], [15,18]]' },
|
||||
],
|
||||
calculations: [],
|
||||
intervals: [
|
||||
createInputIntervals([[1, 3], [2, 6], [8, 10], [15, 18]], 3, [0, 1, 2]),
|
||||
createResultIntervals([[1, 6], [8, 10], [15, 18]], true),
|
||||
],
|
||||
},
|
||||
},
|
||||
{
|
||||
id: 'exec-8',
|
||||
phase: 'execution',
|
||||
explanation:
|
||||
'Done! Return merged = [[1,6], [8,10], [15,18]]. We merged 4 intervals into 3.',
|
||||
codeLine: 18,
|
||||
dataState: {
|
||||
arrays: [],
|
||||
pointers: [],
|
||||
variables: [
|
||||
{ id: 'result', name: 'result', value: '[[1,6], [8,10], [15,18]]' },
|
||||
],
|
||||
calculations: [],
|
||||
intervals: [
|
||||
createInputIntervals([[1, 3], [2, 6], [8, 10], [15, 18]], undefined, [0, 1, 2, 3]),
|
||||
createResultIntervals([[1, 6], [8, 10], [15, 18]]),
|
||||
],
|
||||
},
|
||||
},
|
||||
{
|
||||
id: 'exec-9',
|
||||
phase: 'execution',
|
||||
explanation:
|
||||
'Time: O(n log n) for sorting + O(n) for merging. Space: O(n) for result. Sorting is the bottleneck.',
|
||||
codeLine: 18,
|
||||
decision: {
|
||||
question: 'Why sort first?',
|
||||
answer: 'Sorted intervals make overlap detection O(1) per pair',
|
||||
action: 'Only need to compare adjacent intervals after sorting',
|
||||
},
|
||||
dataState: {
|
||||
arrays: [],
|
||||
pointers: [],
|
||||
variables: [
|
||||
{ id: 'result', name: 'result', value: '[[1,6], [8,10], [15,18]]' },
|
||||
{ id: 'time', name: 'Time', value: 'O(n log n)' },
|
||||
{ id: 'space', name: 'Space', value: 'O(n)' },
|
||||
],
|
||||
calculations: [],
|
||||
intervals: [
|
||||
createInputIntervals([[1, 3], [2, 6], [8, 10], [15, 18]], undefined, [0, 1, 2, 3]),
|
||||
createResultIntervals([[1, 6], [8, 10], [15, 18]]),
|
||||
],
|
||||
},
|
||||
},
|
||||
],
|
||||
};
|
||||
Reference in New Issue
Block a user