e-LC005-longest-palindrome.yml · YAML
YAMLYAMLYAMLYAMLYAMLYAMLYAMLYAMLYAMLYAMLYAMLYAMLYAMLYAMLYAMLYAMLYAMLYAMLYAMLYAMLYAMLYAMLYAMLYAMLYAMLYAMLYAMLYAMLYAMLYAMLYAMLYAMLYAMLYAMLYAMLYAMLYAMLYAMLYAMLYAMLYAMLYAMLYAMLYAMLYAMLYAML
# Editorial widget: LC 5 Longest Palindromic Substring
# Editorial widgets animate ONE specific signature problem at fixed input.
# Preset-only; no randomize, no edit.
id: e-LC005-longest-palindrome
title: "Longest Palindromic Substring (LC 5), expand around centers"
type: editorial-single-panel
level: 1
modes: [preset]
# Editorial widgets do not expose user input: the canonical input is the lesson.
caps:
string_length: { fixed: 5 } # exactly the canonical "babad"
commands:
- name: centerIter
payload: { i: 'integer', char: 'string' }
description: "Mark the new candidate center index for the next two expansions."
- name: expandInit
payload: { kind: "'odd' | 'even'", left: 'integer', right: 'integer' }
description: "Begin an expansion from (left, right). Odd centers start with left == right."
- name: expandMatch
payload: { left: 'integer', right: 'integer', leftChar: 'string', rightChar: 'string' }
description: "s[left] == s[right]; the window is a palindrome, push outward."
- name: expandMismatch
payload: { left: 'integer', right: 'integer', leftChar: 'string', rightChar: 'string' }
description: "s[left] != s[right]; loop terminates one step past the last match."
- name: expandBound
payload: { left: 'integer', right: 'integer' }
description: "Pointer ran off the string; loop terminates."
- name: expandResult
payload: { kind: "'odd' | 'even'", resultL: 'integer', resultR: 'integer', substr: 'string' }
description: "Report the maximal palindrome (left+1, right-1) for this center."
- name: bestUpdate
payload: { bestL: 'integer', bestR: 'integer', substr: 'string' }
description: "New strictly-longer palindrome; update the running best."
static_fallback:
generator: jsdom
output: site/static/widgets/e-LC005-longest-palindrome-fallback.svg
keyframes_used: [0, 12, 14, 15, 24, 44]
# init / first match inside the bab expansion / boundary stop / first best-update
# / aba expansion ties (no update) / final answer
panels:
default:
chapter: "9.10"
primary_chapter: part-9-dynamic-programming/10-palindrome-dp
label: "Longest Palindromic Substring, expand around centers"
canonical_input: |
s = "babad"
# Why "babad" not "cbbd": "babad" exercises both odd-length wins (bab at i=1)
# AND a same-length tie that strict-> rejects (aba at i=2). It surfaces the
# tie-break invariant that "cbbd" cannot show. "cbbd" is a secondary preset.
presets: [] # editorial widgets are preset-as-canonical-input
n_steps: 45
keyframes:
- { step: 0, kind: init, state: { bestL: 0, bestR: 0 }, narration: "Best palindrome initialized to s[0..0]." }
- { step: 1, kind: centerIter, payload: { i: 0, char: "b" }, state: { bestL: 0, bestR: 0 }, narration: "Consider centers at index 0." }
- { step: 2, kind: expandInit, payload: { kind: "odd", left: 0, right: 0 }, narration: "Start odd expansion at i=0." }
- { step: 3, kind: expandMatch, payload: { left: 0, right: 0, leftChar: "b", rightChar: "b" }, narration: "s[0]='b' == s[0]; extend." }
- { step: 4, kind: expandBound, payload: { left: -1, right: 1 }, narration: "Left pointer fell off the string; stop." }
- { step: 5, kind: expandResult, payload: { kind: "odd", resultL: 0, resultR: 0, substr: "b" }, narration: "Odd center at 0: \"b\", length 1." }
- { step: 6, kind: expandInit, payload: { kind: "even", left: 0, right: 1 }, narration: "Start even expansion between 0 and 1." }
- { step: 7, kind: expandMismatch, payload: { left: 0, right: 1, leftChar: "b", rightChar: "a" }, narration: "s[0]='b' != s[1]='a'; stop." }
- { step: 8, kind: expandResult, payload: { kind: "even", resultL: 1, resultR: 0, substr: "" }, narration: "Even center between 0,1: empty." }
- { step: 9, kind: centerIter, payload: { i: 1, char: "a" }, state: { bestL: 0, bestR: 0 }, narration: "Consider centers at index 1." }
- { step: 10, kind: expandInit, payload: { kind: "odd", left: 1, right: 1 }, narration: "Start odd expansion at i=1." }
- { step: 11, kind: expandMatch, payload: { left: 1, right: 1, leftChar: "a", rightChar: "a" }, narration: "s[1]='a' == s[1]; extend." }
- { step: 12, kind: expandMatch, payload: { left: 0, right: 2, leftChar: "b", rightChar: "b" }, narration: "s[0]='b' == s[2]='b'; extend." }
- { step: 13, kind: expandBound, payload: { left: -1, right: 3 }, narration: "Left pointer fell off; stop." }
- { step: 14, kind: expandResult, payload: { kind: "odd", resultL: 0, resultR: 2, substr: "bab" }, narration: "Odd center at 1: \"bab\", length 3." }
- { step: 15, kind: bestUpdate, payload: { bestL: 0, bestR: 2, substr: "bab" }, state: { bestL: 0, bestR: 2 }, narration: "New longest: \"bab\"." }
- { step: 16, kind: expandInit, payload: { kind: "even", left: 1, right: 2 }, narration: "Start even expansion between 1 and 2." }
- { step: 17, kind: expandMismatch, payload: { left: 1, right: 2, leftChar: "a", rightChar: "b" }, narration: "s[1]='a' != s[2]='b'; stop." }
- { step: 18, kind: expandResult, payload: { kind: "even", resultL: 2, resultR: 1, substr: "" }, narration: "Even center between 1,2: empty." }
- { step: 19, kind: centerIter, payload: { i: 2, char: "b" }, state: { bestL: 0, bestR: 2 }, narration: "Consider centers at index 2." }
- { step: 20, kind: expandInit, payload: { kind: "odd", left: 2, right: 2 }, narration: "Start odd expansion at i=2." }
- { step: 21, kind: expandMatch, payload: { left: 2, right: 2, leftChar: "b", rightChar: "b" }, narration: "s[2]='b' == s[2]; extend." }
- { step: 22, kind: expandMatch, payload: { left: 1, right: 3, leftChar: "a", rightChar: "a" }, narration: "s[1]='a' == s[3]='a'; extend." }
- { step: 23, kind: expandMismatch, payload: { left: 0, right: 4, leftChar: "b", rightChar: "d" }, narration: "s[0]='b' != s[4]='d'; stop." }
- { step: 24, kind: expandResult, payload: { kind: "odd", resultL: 1, resultR: 3, substr: "aba" }, narration: "Odd center at 2: \"aba\", length 3. Ties \"bab\"; strict > keeps \"bab\"." }
- { step: 25, kind: expandInit, payload: { kind: "even", left: 2, right: 3 }, narration: "Start even expansion between 2 and 3." }
- { step: 26, kind: expandMismatch, payload: { left: 2, right: 3, leftChar: "b", rightChar: "a" }, narration: "s[2]='b' != s[3]='a'; stop." }
- { step: 27, kind: expandResult, payload: { kind: "even", resultL: 3, resultR: 2, substr: "" }, narration: "Even center between 2,3: empty." }
- { step: 28, kind: centerIter, payload: { i: 3, char: "a" }, state: { bestL: 0, bestR: 2 }, narration: "Consider centers at index 3." }
- { step: 29, kind: expandInit, payload: { kind: "odd", left: 3, right: 3 }, narration: "Start odd expansion at i=3." }
- { step: 30, kind: expandMatch, payload: { left: 3, right: 3, leftChar: "a", rightChar: "a" }, narration: "s[3]='a' == s[3]; extend." }
- { step: 31, kind: expandMismatch, payload: { left: 2, right: 4, leftChar: "b", rightChar: "d" }, narration: "s[2]='b' != s[4]='d'; stop." }
- { step: 32, kind: expandResult, payload: { kind: "odd", resultL: 3, resultR: 3, substr: "a" }, narration: "Odd center at 3: \"a\", length 1." }
- { step: 33, kind: expandInit, payload: { kind: "even", left: 3, right: 4 }, narration: "Start even expansion between 3 and 4." }
- { step: 34, kind: expandMismatch, payload: { left: 3, right: 4, leftChar: "a", rightChar: "d" }, narration: "s[3]='a' != s[4]='d'; stop." }
- { step: 35, kind: expandResult, payload: { kind: "even", resultL: 4, resultR: 3, substr: "" }, narration: "Even center between 3,4: empty." }
- { step: 36, kind: centerIter, payload: { i: 4, char: "d" }, state: { bestL: 0, bestR: 2 }, narration: "Consider centers at index 4." }
- { step: 37, kind: expandInit, payload: { kind: "odd", left: 4, right: 4 }, narration: "Start odd expansion at i=4." }
- { step: 38, kind: expandMatch, payload: { left: 4, right: 4, leftChar: "d", rightChar: "d" }, narration: "s[4]='d' == s[4]; extend." }
- { step: 39, kind: expandBound, payload: { left: 3, right: 5 }, narration: "Right pointer fell off; stop." }
- { step: 40, kind: expandResult, payload: { kind: "odd", resultL: 4, resultR: 4, substr: "d" }, narration: "Odd center at 4: \"d\", length 1." }
- { step: 41, kind: expandInit, payload: { kind: "even", left: 4, right: 5 }, narration: "Start even expansion between 4 and 5." }
- { step: 42, kind: expandBound, payload: { left: 4, right: 5 }, narration: "Right pointer off; stop." }
- { step: 43, kind: expandResult, payload: { kind: "even", resultL: 5, resultR: 4, substr: "" }, narration: "Even center between 4,5: empty." }
- { step: 44, kind: terminate, state: { bestL: 0, bestR: 2, result: "bab" }, narration: "Return \"bab\". Centers visited: 9 (5 odd + 4 even)." }
lc_id: 5
difficulty: medium
algorithm: expand-around-centers
notes: |
Animates the optimal expand-around-centers algorithm at LC 5's n=1000 ceiling.
The 2D DP table is NOT animated; expand-around-centers is the recommended
interview answer.
Trace transcribed verbatim step buffer (45 steps, deterministic).
The pedagogically critical moment is step 24: \"aba\" ties \"bab\" at length 3,
and the strict-> guard rejects the update. A reader who switches to >=
observes \"aba\" as the answer instead; LC accepts both.From The Engineering Handbook (source), CC BY-SA 4.0.