"""Dynamic Programming algorithm JS generators for generate.py""" def js_climbing_stairs(): return r''' const examples = [ {input: 5, label: '示例1: n=5'}, {input: 3, label: '示例2: n=3'}, {input: 10, label: '示例3: n=10'}, ]; let steps, stepCtrl, n; function buildSteps(total) { n = total; steps = []; const dp = new Array(n + 1).fill(0); dp[0] = 0; dp[1] = 1; dp[2] = 2; steps.push({stage:'init', msg:'初始化:dp[1]=1(1种),dp[2]=2(2种)', dp:[...dp], current:-1, from1:-1, from2:-1}); for (let i = 3; i <= n; i++) { steps.push({stage:'calc', msg:`计算 dp[${i}] = dp[${i-1}] + dp[${i-2}] = ${dp[i-1]} + ${dp[i-2]} = ${dp[i-1]+dp[i-2]}`, dp:[...dp], current:i, from1:i-1, from2:i-2}); dp[i] = dp[i-1] + dp[i-2]; steps.push({stage:'fill', msg:`dp[${i}] = ${dp[i]},已填充`, dp:[...dp], current:i, from1:i-1, from2:i-2}); } steps.push({stage:'done', msg:`结果:爬到第 ${n} 阶有 ${dp[n]} 种方法`, dp:[...dp], current:n, from1:-1, from2:-1}); } function render(step) { const s = steps[step]; let viz = '
'; for (let i = 1; i <= n; i++) { const filled = s.dp[i] > 0; const isCurrent = i === s.current; const isFrom1 = i === s.from1; const isFrom2 = i === s.from2; let bg = '#e2e8f0', color = '#64748b'; if (isCurrent && (s.stage==='fill'||s.stage==='done')) { bg = 'var(--green)'; color = 'white'; } else if (isCurrent) { bg = 'var(--orange)'; color = 'white'; } else if (isFrom1) { bg = '#dbeafe'; color = '#1e40af'; } else if (isFrom2) { bg = '#ede9fe'; color = '#5b21b6'; } else if (filled) { bg = '#f0fdf4'; color = '#166534'; } viz += `
${s.dp[i]||''}${i}
`; } viz += '
'; viz += '
dp 表:
'; const dpHl = {}; if (s.current > 0) dpHl[s.current-1] = s.stage==='fill'?'green':'orange'; if (s.from1 > 0) dpHl[s.from1-1] = 'blue'; if (s.from2 > 0) dpHl[s.from2-1] = 'purple'; viz += renderArray(s.dp.slice(1), {highlights: dpHl}); if (s.stage === 'calc') { viz += `
dp[${s.current}] = dp[${s.from1}] + dp[${s.from2}] = ${s.dp[s.from1]} + ${s.dp[s.from2]}
`; } $('vizArea').innerHTML = viz; $('detailContent').innerHTML = '
' + s.msg + '
'; if (s.stage !== 'done' && s.current > 0) { $('detailContent').innerHTML += `
dp[${s.current}] = ${s.dp[s.current]}
`; } if (s.stage === 'done') { $('resultContent').innerHTML = `
爬到第 ${n} 阶有 ${s.dp[n]} 种方法
`; } $('hintText').textContent = s.msg; const stages = ['init→初始化','calc→递推计算','fill→填充','done→完成']; $('pipeline').innerHTML = stages.map(st => { const [k,l] = st.split('→'); return `${l}`; }).join('→'); } function init() { const sel = $('exampleSelect'); examples.forEach((e,i) => { sel.innerHTML += ``; }); $('inputArea').value = 'n=5'; buildSteps(examples[0].input); stepCtrl = new StepController({onStep: render}); stepCtrl.setSteps(steps.map((_,i)=>i)); $('stepInfo').textContent = '步骤 1 / ' + steps.length; stepCtrl.onStep = (idx) => { render(idx); $('stepInfo').textContent = `步骤 ${idx+1} / ${steps.length}`; }; $('applyBtn').onclick = () => { const m = $('inputArea').value.match(/n=(\d+)/); if (!m) { alert('格式: n=5'); return; } buildSteps(parseInt(m[1])); stepCtrl.setSteps(steps.map((_,i)=>i)); render(0); $('stepInfo').textContent = '步骤 1 / ' + steps.length; }; $('exampleSelect').onchange = () => { const e = examples[parseInt($('exampleSelect').value)]; $('inputArea').value = `n=${e.input}`; buildSteps(e.input); stepCtrl.setSteps(steps.map((_,i)=>i)); render(0); }; $('prevBtn').onclick = () => stepCtrl.prev(); $('nextBtn').onclick = () => stepCtrl.next(); $('jumpBtn').onclick = () => stepCtrl.jumpToEnd(); $('autoBtn').onclick = () => { const on = stepCtrl.toggleAuto(); $('autoBtn').textContent = on ? '暂停' : '自动播放'; }; $('resetBtn').onclick = () => { stepCtrl.reset(); $('autoBtn').textContent = '自动播放'; }; } init(); $('codeArea').innerHTML = renderCode(`def climbStairs(n): if n <= 2: return n dp = [0] * (n + 1) dp[1], dp[2] = 1, 2 for i in range(3, n + 1): dp[i] = dp[i-1] + dp[i-2] return dp[n]`, {lang:'Python'}); ''' def js_pascals_triangle(): return r''' const examples = [ {input: 5, label: '示例1: numRows=5'}, {input: 1, label: '示例2: numRows=1'}, {input: 7, label: '示例3: numRows=7'}, ]; let steps, stepCtrl, numRows; function buildSteps(n) { numRows = n; steps = []; const triangle = []; steps.push({stage:'init', msg:'开始生成杨辉三角,每行首尾为1,中间元素 = 上方两数之和', triangle:[], row:-1, col:-1}); for (let i = 0; i < numRows; i++) { const row = new Array(i + 1).fill(1); steps.push({stage:'newRow', msg:`第 ${i} 行共 ${i+1} 个元素,首尾均为 1`, triangle:JSON.parse(JSON.stringify(triangle)), row:i, col:-1}); for (let j = 1; j < i; j++) { row[j] = triangle[i-1][j-1] + triangle[i-1][j]; steps.push({stage:'calc', msg:`triangle[${i}][${j}] = triangle[${i-1}][${j-1}] + triangle[${i-1}][${j}] = ${triangle[i-1][j-1]} + ${triangle[i-1][j]} = ${row[j]}`, triangle:JSON.parse(JSON.stringify(triangle)), row:i, col:j}); } triangle.push([...row]); steps.push({stage:'fillRow', msg:`第 ${i} 行填充完毕:[${row.join(', ')}]`, triangle:JSON.parse(JSON.stringify(triangle)), row:i, col:i}); } steps.push({stage:'done', msg:`杨辉三角生成完毕,共 ${numRows} 行`, triangle:JSON.parse(JSON.stringify(triangle)), row:-1, col:-1}); } function render(step) { const s = steps[step]; let viz = '
'; const maxRow = s.triangle.length; for (let i = 0; i < maxRow; i++) { const gap = Math.max(2, (maxRow - i) * 6); viz += `
`; for (let j = 0; j <= i; j++) { const val = s.triangle[i][j]; let cls = 'default'; if (i === s.row && s.stage !== 'done') { cls = j === s.col ? 'orange' : 'green'; } if (s.stage === 'calc' && i === s.row - 1 && (j === s.col - 1 || j === s.col)) { cls = 'blue'; } viz += `${val}`; } viz += '
'; } if (s.stage === 'newRow' || s.stage === 'calc') { viz += `
`; for (let j = 0; j <= s.row; j++) { viz += `?`; } viz += '
'; } viz += '
'; $('vizArea').innerHTML = viz; let detail = '
' + s.msg + '
'; if (s.triangle.length > 0) { detail += '
当前三角:
'; s.triangle.forEach((row, i) => { detail += `
${i}: [${row.join(', ')}]
`; }); } $('detailContent').innerHTML = detail; if (s.stage === 'done') { $('resultContent').innerHTML = `
杨辉三角 ${numRows} 行已生成
`; } $('hintText').textContent = s.msg; const stages = ['init→开始','newRow→新行','calc→计算','fillRow→填充','done→完成']; $('pipeline').innerHTML = stages.map(st => { const [k,l] = st.split('→'); return `${l}`; }).join('→'); } function init() { const sel = $('exampleSelect'); examples.forEach((e,i) => { sel.innerHTML += ``; }); $('inputArea').value = 'numRows=5'; buildSteps(examples[0].input); stepCtrl = new StepController({onStep: render}); stepCtrl.setSteps(steps.map((_,i)=>i)); $('stepInfo').textContent = '步骤 1 / ' + steps.length; stepCtrl.onStep = (idx) => { render(idx); $('stepInfo').textContent = `步骤 ${idx+1} / ${steps.length}`; }; $('applyBtn').onclick = () => { const m = $('inputArea').value.match(/numRows=(\d+)/); if (!m) { alert('格式: numRows=5'); return; } buildSteps(parseInt(m[1])); stepCtrl.setSteps(steps.map((_,i)=>i)); render(0); $('stepInfo').textContent = '步骤 1 / ' + steps.length; }; $('exampleSelect').onchange = () => { const e = examples[parseInt($('exampleSelect').value)]; $('inputArea').value = `numRows=${e.input}`; buildSteps(e.input); stepCtrl.setSteps(steps.map((_,i)=>i)); render(0); }; $('prevBtn').onclick = () => stepCtrl.prev(); $('nextBtn').onclick = () => stepCtrl.next(); $('jumpBtn').onclick = () => stepCtrl.jumpToEnd(); $('autoBtn').onclick = () => { const on = stepCtrl.toggleAuto(); $('autoBtn').textContent = on ? '暂停' : '自动播放'; }; $('resetBtn').onclick = () => { stepCtrl.reset(); $('autoBtn').textContent = '自动播放'; }; } init(); $('codeArea').innerHTML = renderCode(`def generate(numRows): triangle = [] for i in range(numRows): row = [1] * (i + 1) for j in range(1, i): row[j] = triangle[i-1][j-1] + triangle[i-1][j] triangle.append(row) return triangle`, {lang:'Python'}); ''' def js_house_robber(): return r''' const examples = [ {input: [1,2,3,1], label: '示例1: [1,2,3,1]'}, {input: [2,7,9,3,1], label: '示例2: [2,7,9,3,1]'}, {input: [2,1,1,2], label: '示例3: [2,1,1,2]'}, ]; let nums, steps, stepCtrl; function buildSteps(arr) { nums = [...arr]; steps = []; const n = nums.length; if (n === 0) { steps.push({stage:'done', msg:'数组为空', dp:[], rob:[]}); return; } const dp = new Array(n).fill(0); const rob = new Array(n).fill(false); dp[0] = nums[0]; rob[0] = true; steps.push({stage:'init', msg:`dp[0] = ${nums[0]}(只有一间房子,必须偷)`, dp:[...dp], rob:[...rob], current:0, choice:null}); if (n > 1) { if (nums[1] > nums[0]) { dp[1] = nums[1]; rob[1] = true; rob[0] = false; } else { dp[1] = nums[0]; rob[1] = false; } steps.push({stage:'init2', msg:`dp[1] = max(${nums[0]}, ${nums[1]}) = ${dp[1]}`, dp:[...dp], rob:[...rob], current:1, choice:'max'}); } for (let i = 2; i < n; i++) { const notRob = dp[i-1]; const doRob = dp[i-2] + nums[i]; steps.push({stage:'compare', msg:`dp[${i}]:不偷 = dp[${i-1}] = ${notRob},偷 = dp[${i-2}] + nums[${i}] = ${dp[i-2]} + ${nums[i]} = ${doRob}`, dp:[...dp], rob:[...rob], current:i, choice:'comparing', notRob, doRob}); if (doRob > notRob) { dp[i] = doRob; rob[i] = true; steps.push({stage:'rob', msg:`偷第 ${i} 间更优:dp[${i}] = ${doRob}`, dp:[...dp], rob:[...rob], current:i, choice:'rob'}); } else { dp[i] = notRob; rob[i] = false; steps.push({stage:'skip', msg:`不偷第 ${i} 间更优:dp[${i}] = ${notRob}`, dp:[...dp], rob:[...rob], current:i, choice:'skip'}); } } steps.push({stage:'done', msg:`最多可以偷取 ${dp[n-1]}`, dp:[...dp], rob:[...rob], current:n-1, choice:'done'}); } function render(step) { const s = steps[step]; let viz = '
'; nums.forEach((v, i) => { const isCurrent = i === s.current && s.stage !== 'done'; const isRobbed = s.rob[i]; let bg = '#e2e8f0', color = '#475569', border = '2px solid #cbd5e1'; if (isRobbed && s.stage === 'done') { bg = '#dcfce7'; color = '#166534'; border = '2px solid #16a34a'; } else if (isCurrent && s.choice === 'rob') { bg = '#fef3c7'; color = '#92400e'; border = '2px solid #f59e0b'; } else if (isCurrent) { bg = '#dbeafe'; color = '#1e40af'; border = '2px solid #3b82f6'; } else if (isRobbed) { bg = '#dcfce7'; color = '#166534'; border = '2px solid #86efac'; } viz += `
🏠
${v}
${isRobbed?'偷':'·'}
`; }); viz += '
'; viz += '
dp 值:
'; const dpHl = {}; if (s.current >= 0) dpHl[s.current] = s.choice==='rob'?'orange':'blue'; viz += renderArray(s.dp, {highlights: dpHl}); if (s.stage === 'compare') { viz += `
dp[${s.current}] = max(不偷=${s.notRob}, 偷=${s.doRob})
`; } $('vizArea').innerHTML = viz; $('detailContent').innerHTML = '
' + s.msg + '
'; if (s.dp.length > 0) { $('detailContent').innerHTML += `
当前最大金额:${s.dp[Math.max(0,s.current)]}
`; } if (s.stage === 'done') { $('resultContent').innerHTML = `
最多可以偷取 ${s.dp[nums.length-1]}
`; } $('hintText').textContent = s.msg; const stages = ['init→初始化','init2→初始化','compare→比较','rob→偷','skip→不偷','done→完成']; $('pipeline').innerHTML = stages.map(st => { const [k,l] = st.split('→'); return `${l}`; }).join('→'); } function init() { const sel = $('exampleSelect'); examples.forEach((e,i) => { sel.innerHTML += ``; }); $('inputArea').value = '[1,2,3,1]'; buildSteps(examples[0].input); stepCtrl = new StepController({onStep: render}); stepCtrl.setSteps(steps.map((_,i)=>i)); $('stepInfo').textContent = '步骤 1 / ' + steps.length; stepCtrl.onStep = (idx) => { render(idx); $('stepInfo').textContent = `步骤 ${idx+1} / ${steps.length}`; }; $('applyBtn').onclick = () => { try { const arr = JSON.parse($('inputArea').value); buildSteps(arr); stepCtrl.setSteps(steps.map((_,i)=>i)); render(0); $('stepInfo').textContent = '步骤 1 / ' + steps.length; } catch(e) { alert('请输入合法 JSON 数组'); } }; $('exampleSelect').onchange = () => { const e = examples[parseInt($('exampleSelect').value)]; $('inputArea').value = JSON.stringify(e.input); buildSteps(e.input); stepCtrl.setSteps(steps.map((_,i)=>i)); render(0); }; $('prevBtn').onclick = () => stepCtrl.prev(); $('nextBtn').onclick = () => stepCtrl.next(); $('jumpBtn').onclick = () => stepCtrl.jumpToEnd(); $('autoBtn').onclick = () => { const on = stepCtrl.toggleAuto(); $('autoBtn').textContent = on ? '暂停' : '自动播放'; }; $('resetBtn').onclick = () => { stepCtrl.reset(); $('autoBtn').textContent = '自动播放'; }; } init(); $('codeArea').innerHTML = renderCode(`def rob(nums): n = len(nums) if n == 0: return 0 if n == 1: return nums[0] dp = [0] * n dp[0], dp[1] = nums[0], max(nums[0], nums[1]) for i in range(2, n): dp[i] = max(dp[i-1], dp[i-2] + nums[i]) return dp[-1]`, {lang:'Python'}); ''' def js_perfect_squares(): return r''' const examples = [ {input: 12, label: '示例1: n=12'}, {input: 13, label: '示例2: n=13'}, {input: 4, label: '示例3: n=4'}, ]; let steps, stepCtrl, n; function buildSteps(target) { n = target; steps = []; const dp = new Array(n + 1).fill(Infinity); dp[0] = 0; steps.push({stage:'init', msg:`初始化 dp[0]=0,其余为 ∞`, dp:[...dp], current:-1, bestJ:-1, candidates:[]}); for (let i = 1; i <= n; i++) { let bestJ = 1, bestVal = Infinity; const candidates = []; for (let j = 1; j * j <= i; j++) { candidates.push({j, sq: j*j, val: dp[i - j*j] + 1}); if (dp[i - j*j] + 1 < bestVal) { bestVal = dp[i - j*j] + 1; bestJ = j; } } steps.push({stage:'try', msg:`计算 dp[${i}]:尝试所有平方数`, dp:[...dp], current:i, bestJ:-1, candidates}); dp[i] = bestVal; steps.push({stage:'fill', msg:`dp[${i}] = dp[${i-bestJ*bestJ}]+1 = ${dp[i-bestJ*bestJ]}+1 = ${bestVal}(选 ${bestJ}²=${bestJ*bestJ})`, dp:[...dp], current:i, bestJ, candidates}); } steps.push({stage:'done', msg:`${n} 最少需要 ${dp[n]} 个完全平方数`, dp:[...dp], current:n, bestJ:-1, candidates:[]}); } function render(step) { const s = steps[step]; let viz = '
dp 表:
'; viz += '
'; for (let i = 0; i <= n; i++) { let cls = 'default'; if (i === s.current) cls = s.stage==='try'?'orange':'green'; if (s.bestJ > 0 && i === s.current - s.bestJ*s.bestJ) cls = 'blue'; const val = s.dp[i] === Infinity ? '∞' : s.dp[i]; viz += `${val}${i}`; } viz += '
'; if (s.candidates && s.candidates.length > 0) { viz += '
候选平方数:
'; viz += '
'; s.candidates.forEach(c => { const isBest = c.j === s.bestJ; const bg = isBest ? '#dcfce7' : '#f0f9ff'; const bd = isBest ? '2px solid #16a34a' : '1px solid #cbd5e1'; viz += `${c.j}²=${c.sq} → dp[${s.current-c.sq}]+1=${c.val===Infinity?'∞':c.val}`; }); viz += '
'; } $('vizArea').innerHTML = viz; $('detailContent').innerHTML = '
' + s.msg + '
'; if (s.current > 0 && s.dp[s.current] !== Infinity) { $('detailContent').innerHTML += `
dp[${s.current}] = ${s.dp[s.current]}
`; } if (s.stage === 'done') { $('resultContent').innerHTML = `
${n} 最少需要 ${s.dp[n]} 个完全平方数
`; } $('hintText').textContent = s.msg; const stages = ['init→初始化','try→尝试','fill→填充','done→完成']; $('pipeline').innerHTML = stages.map(st => { const [k,l] = st.split('→'); return `${l}`; }).join('→'); } function init() { const sel = $('exampleSelect'); examples.forEach((e,i) => { sel.innerHTML += ``; }); $('inputArea').value = 'n=12'; buildSteps(examples[0].input); stepCtrl = new StepController({onStep: render}); stepCtrl.setSteps(steps.map((_,i)=>i)); $('stepInfo').textContent = '步骤 1 / ' + steps.length; stepCtrl.onStep = (idx) => { render(idx); $('stepInfo').textContent = `步骤 ${idx+1} / ${steps.length}`; }; $('applyBtn').onclick = () => { const m = $('inputArea').value.match(/n=(\d+)/); if (!m) { alert('格式: n=12'); return; } buildSteps(parseInt(m[1])); stepCtrl.setSteps(steps.map((_,i)=>i)); render(0); $('stepInfo').textContent = '步骤 1 / ' + steps.length; }; $('exampleSelect').onchange = () => { const e = examples[parseInt($('exampleSelect').value)]; $('inputArea').value = `n=${e.input}`; buildSteps(e.input); stepCtrl.setSteps(steps.map((_,i)=>i)); render(0); }; $('prevBtn').onclick = () => stepCtrl.prev(); $('nextBtn').onclick = () => stepCtrl.next(); $('jumpBtn').onclick = () => stepCtrl.jumpToEnd(); $('autoBtn').onclick = () => { const on = stepCtrl.toggleAuto(); $('autoBtn').textContent = on ? '暂停' : '自动播放'; }; $('resetBtn').onclick = () => { stepCtrl.reset(); $('autoBtn').textContent = '自动播放'; }; } init(); $('codeArea').innerHTML = renderCode(`def numSquares(n): dp = [float('inf')] * (n + 1) dp[0] = 0 for i in range(1, n + 1): j = 1 while j * j <= i: dp[i] = min(dp[i], dp[i - j*j] + 1) j += 1 return dp[n]`, {lang:'Python'}); ''' def js_coin_change(): return r''' const examples = [ {input: [1,2,5], amount: 11, label: '示例1: coins=[1,2,5], amount=11'}, {input: [2], amount: 3, label: '示例2: coins=[2], amount=3'}, {input: [1], amount: 0, label: '示例3: coins=[1], amount=0'}, ]; let coins, amount, steps, stepCtrl; function buildSteps(c, amt) { coins = [...c]; amount = amt; steps = []; const dp = new Array(amt + 1).fill(Infinity); dp[0] = 0; steps.push({stage:'init', msg:'初始化 dp[0]=0,其余为 ∞', dp:[...dp], current:-1, bestCoin:-1, candidates:[]}); for (let i = 1; i <= amt; i++) { let bestCoin = -1, bestVal = Infinity; const candidates = []; for (const coin of coins) { if (coin <= i) { const val = dp[i - coin] + 1; candidates.push({coin, prev: i - coin, val}); if (val < bestVal) { bestVal = val; bestCoin = coin; } } } steps.push({stage:'try', msg:`计算 dp[${i}]:尝试所有硬币`, dp:[...dp], current:i, bestCoin:-1, candidates}); dp[i] = bestVal; if (bestCoin > 0) { steps.push({stage:'fill', msg:`dp[${i}] = dp[${i-bestCoin}]+1 = ${dp[i-bestCoin]}+1 = ${bestVal}(选硬币 ${bestCoin})`, dp:[...dp], current:i, bestCoin, candidates}); } else { steps.push({stage:'fill', msg:`dp[${i}] = ∞(无法凑出)`, dp:[...dp], current:i, bestCoin:-1, candidates}); } } steps.push({stage:'done', msg:dp[amt]===Infinity?`无法凑出金额 ${amt}`:`凑出金额 ${amt} 最少需要 ${dp[amt]} 枚硬币`, dp:[...dp], current:amt, bestCoin:-1, candidates:[]}); } function render(step) { const s = steps[step]; let viz = '
金额轴 & dp 表:
'; viz += '
'; for (let i = 0; i <= amount; i++) { const cls = i === s.current ? (s.stage==='try'?'orange':'green') : 'default'; const val = s.dp[i] === Infinity ? '∞' : s.dp[i]; viz += `${val}${i}`; } viz += '
'; viz += '
硬币面额:
'; viz += '
'; coins.forEach(c => { const isBest = c === s.bestCoin; const cls = isBest ? 'green' : 'default'; viz += `${c}`; }); viz += '
'; if (s.candidates && s.candidates.length > 0) { viz += '
候选:
'; viz += '
'; s.candidates.forEach(c => { const isBest = c.coin === s.bestCoin; const bg = isBest ? '#dcfce7' : '#f0f9ff'; const bd = isBest ? '2px solid #16a34a' : '1px solid #cbd5e1'; viz += `用${c.coin} → dp[${c.prev}]+1=${c.val===Infinity?'∞':c.val}`; }); viz += '
'; } $('vizArea').innerHTML = viz; $('detailContent').innerHTML = '
' + s.msg + '
'; if (s.current >= 0 && s.dp[s.current] !== Infinity) { $('detailContent').innerHTML += `
dp[${s.current}] = ${s.dp[s.current]}
`; } if (s.stage === 'done') { if (s.dp[amount] === Infinity) { $('resultContent').innerHTML = '
无法凑出该金额,返回 -1
'; } else { $('resultContent').innerHTML = `
最少需要 ${s.dp[amount]} 枚硬币
`; } } $('hintText').textContent = s.msg; const stages = ['init→初始化','try→尝试','fill→填充','done→完成']; $('pipeline').innerHTML = stages.map(st => { const [k,l] = st.split('→'); return `${l}`; }).join('→'); } function init() { const sel = $('exampleSelect'); examples.forEach((e,i) => { sel.innerHTML += ``; }); $('inputArea').value = 'coins=[1,2,5], amount=11'; buildSteps(examples[0].input, examples[0].amount); stepCtrl = new StepController({onStep: render}); stepCtrl.setSteps(steps.map((_,i)=>i)); $('stepInfo').textContent = '步骤 1 / ' + steps.length; stepCtrl.onStep = (idx) => { render(idx); $('stepInfo').textContent = `步骤 ${idx+1} / ${steps.length}`; }; $('applyBtn').onclick = () => { const m = $('inputArea').value.match(/coins=\[([^\]]+)\].*amount=(\d+)/); if (!m) { alert('格式: coins=[1,2,5], amount=11'); return; } const c = m[1].split(',').map(Number); buildSteps(c, parseInt(m[2])); stepCtrl.setSteps(steps.map((_,i)=>i)); render(0); $('stepInfo').textContent = '步骤 1 / ' + steps.length; }; $('exampleSelect').onchange = () => { const e = examples[parseInt($('exampleSelect').value)]; $('inputArea').value = `coins=[${e.input}], amount=${e.amount}`; buildSteps(e.input, e.amount); stepCtrl.setSteps(steps.map((_,i)=>i)); render(0); }; $('prevBtn').onclick = () => stepCtrl.prev(); $('nextBtn').onclick = () => stepCtrl.next(); $('jumpBtn').onclick = () => stepCtrl.jumpToEnd(); $('autoBtn').onclick = () => { const on = stepCtrl.toggleAuto(); $('autoBtn').textContent = on ? '暂停' : '自动播放'; }; $('resetBtn').onclick = () => { stepCtrl.reset(); $('autoBtn').textContent = '自动播放'; }; } init(); $("codeArea").innerHTML = renderCode( `def coinChange(coins, amount):\n` + ` dp = [float('inf')] * (amount + 1)\n` + ` dp[0] = 0\n` + ` for i in range(1, amount + 1):\n` + ` for coin in coins:\n` + ` if coin <= i:\n` + ` dp[i] = min(dp[i], dp[i - coin] + 1)\n` + ` return dp[amount] if dp[amount] != float('inf') else -1`, {lang:'Python'} ); ''' def js_word_break(): return r''' const examples = [ {input: 'leetcode', dict: ['leet','code'], label: '示例1: "leetcode", ["leet","code"]'}, {input: 'applepenapple', dict: ['apple','pen'], label: '示例2: "applepenapple", ["apple","pen"]'}, {input: 'catsandog', dict: ['cats','dog','sand','and','cat'], label: '示例3: "catsandog"'}, ]; let s, wordDict, steps, stepCtrl; function buildSteps(str, dict) { s = str; wordDict = dict; steps = []; const n = str.length; const dp = new Array(n + 1).fill(false); dp[0] = true; steps.push({stage:'init', msg:'dp[0]=true(空串可分割)', dp:[...dp], i:-1, j:-1, matched:false, matchWord:''}); for (let i = 1; i <= n; i++) { steps.push({stage:'checkI', msg:`检查 dp[${i}]:能否将 s[0:${i}]="${s.slice(0,i)}" 分割`, dp:[...dp], i, j:-1, matched:false, matchWord:''}); for (let j = 0; j < i; j++) { const sub = s.slice(j, i); const inDict = dp[j] && dict.includes(sub); if (dp[j]) { steps.push({stage:'tryJ', msg:`dp[${j}]=true,检查 s[${j}:${i}]="${sub}" ${inDict?'在字典中 ✅':'不在字典中 ❌'}`, dp:[...dp], i, j, matched:inDict, matchWord:sub}); if (inDict) { dp[i] = true; steps.push({stage:'found', msg:`dp[${j}]=true 且 "${sub}" 在字典中 → dp[${i}]=true`, dp:[...dp], i, j, matched:true, matchWord:sub}); break; } } } if (!dp[i]) { steps.push({stage:'fail', msg:`dp[${i}]=false(无法分割 s[0:${i}])`, dp:[...dp], i, j:-1, matched:false, matchWord:''}); } } steps.push({stage:'done', msg:dp[n]?`"${s}" 可以被分割`:`"${s}" 无法被分割`, dp:[...dp], i:n, j:-1, matched:dp[n], matchWord:''}); } function render(step) { const st = steps[step]; let viz = '
字符串:
'; viz += '
'; for (let i = 0; i < s.length; i++) { const inRange = st.j >= 0 && i >= st.j && i < st.i; const cls = inRange ? (st.matched ? 'green' : 'orange') : 'default'; viz += `${s[i]}`; } viz += '
'; viz += '
'; for (let i = 0; i <= s.length; i++) { viz += `${i}`; } viz += '
'; viz += '
dp 状态:
'; viz += '
'; for (let i = 0; i <= s.length; i++) { const isCur = i === st.i; const cls = isCur ? 'orange' : (st.dp[i] ? 'green' : 'default'); viz += `${st.dp[i]?'T':'F'}`; } viz += '
'; viz += '
字典:
'; viz += '
'; wordDict.forEach(w => { const isMatch = w === st.matchWord; const cls = isMatch ? 'green' : 'default'; viz += `"${w}"`; }); viz += '
'; $('vizArea').innerHTML = viz; $('detailContent').innerHTML = '
' + st.msg + '
'; if (st.stage === 'done') { $('resultContent').innerHTML = `
${st.matched ? `"${s}" 可以被分割 ✅` : `"${s}" 无法被分割 ❌`}
`; } $('hintText').textContent = st.msg; const stages = ['init→初始化','checkI→检查','tryJ→尝试','found→找到','fail→失败','done→完成']; $('pipeline').innerHTML = stages.map(st2 => { const [k,l] = st2.split('→'); return `${l}`; }).join('→'); } function init() { const sel = $('exampleSelect'); examples.forEach((e,i) => { sel.innerHTML += ``; }); $('inputArea').value = 's="leetcode", dict=["leet","code"]'; buildSteps(examples[0].input, examples[0].dict); stepCtrl = new StepController({onStep: render}); stepCtrl.setSteps(steps.map((_,i)=>i)); $('stepInfo').textContent = '步骤 1 / ' + steps.length; stepCtrl.onStep = (idx) => { render(idx); $('stepInfo').textContent = `步骤 ${idx+1} / ${steps.length}`; }; $('applyBtn').onclick = () => { const m = $('inputArea').value.match(/s="([^"]+)"\s*,\s*dict=\[([^\]]+)\]/); if (!m) { alert('格式: s="leetcode", dict=["leet","code"]'); return; } const dict = m[2].match(/"([^"]*)"/g).map(x => x.replace(/"/g, '')); buildSteps(m[1], dict); stepCtrl.setSteps(steps.map((_,i)=>i)); render(0); $('stepInfo').textContent = '步骤 1 / ' + steps.length; }; $('exampleSelect').onchange = () => { const e = examples[parseInt($('exampleSelect').value)]; $('inputArea').value = `s="${e.input}", dict=["${e.dict.join('","')}"]`; buildSteps(e.input, e.dict); stepCtrl.setSteps(steps.map((_,i)=>i)); render(0); }; $('prevBtn').onclick = () => stepCtrl.prev(); $('nextBtn').onclick = () => stepCtrl.next(); $('jumpBtn').onclick = () => stepCtrl.jumpToEnd(); $('autoBtn').onclick = () => { const on = stepCtrl.toggleAuto(); $('autoBtn').textContent = on ? '暂停' : '自动播放'; }; $('resetBtn').onclick = () => { stepCtrl.reset(); $('autoBtn').textContent = '自动播放'; }; } init(); $('codeArea').innerHTML = renderCode(`def wordBreak(s, wordDict): n = len(s) dp = [False] * (n + 1) dp[0] = True word_set = set(wordDict) for i in range(1, n + 1): for j in range(i): if dp[j] and s[j:i] in word_set: dp[i] = True break return dp[n]`, {lang:'Python'}); ''' def js_longest_increasing_subsequence(): return r''' const examples = [ {input: [10,9,2,5,3,7,101,18], label: '示例1: [10,9,2,5,3,7,101,18]'}, {input: [0,1,0,3,2,3], label: '示例2: [0,1,0,3,2,3]'}, {input: [7,7,7,7,7,7,7], label: '示例3: [7,7,7,7,7,7,7]'}, ]; let nums, steps, stepCtrl; function buildSteps(arr) { nums = [...arr]; steps = []; const n = nums.length; if (n === 0) { steps.push({stage:'done', msg:'数组为空', dp:[], best:0, prev:[]}); return; } const dp = new Array(n).fill(1); const prev = new Array(n).fill(-1); steps.push({stage:'init', msg:'每个元素自身构成长度为1的递增子序列', dp:[...dp], current:-1, bestIdx:0, prev:[...prev]}); let bestIdx = 0; for (let i = 1; i < n; i++) { steps.push({stage:'check', msg:`检查 nums[${i}]=${nums[i]},寻找 j dp[i]) { dp[i] = dp[j] + 1; prev[i] = j; steps.push({stage:'update', msg:`nums[${j}]=${nums[j]} < ${nums[i]},dp[${i}] = dp[${j}]+1 = ${dp[i]}`, dp:[...dp], current:i, bestIdx, j, prev:[...prev]}); } } if (dp[i] > dp[bestIdx]) bestIdx = i; steps.push({stage:'doneI', msg:`dp[${i}]=${dp[i]},最长子序列长度 = ${dp[bestIdx]}`, dp:[...dp], current:i, bestIdx, prev:[...prev]}); } steps.push({stage:'done', msg:`最长递增子序列长度 = ${dp[bestIdx]}`, dp:[...dp], current:bestIdx, bestIdx, prev:[...prev]}); } function render(step) { const s = steps[step]; const hl = {}; if (s.current >= 0 && s.stage !== 'done') hl[s.current] = 'orange'; if (s.j >= 0) hl[s.j] = 'blue'; if (s.stage === 'done') { let idx = s.bestIdx; while (idx >= 0) { hl[idx] = 'green'; idx = s.prev[idx]; } } let viz = renderArray(nums, {highlights: hl}); viz += '
dp 值(以每个元素结尾的 LIS 长度):
'; const dpHl = {}; if (s.current >= 0 && s.stage !== 'done') dpHl[s.current] = 'orange'; if (s.bestIdx >= 0 && s.stage === 'done') dpHl[s.bestIdx] = 'green'; viz += renderArray(s.dp, {highlights: dpHl}); if (s.stage === 'done') { const lis = []; let idx = s.bestIdx; while (idx >= 0) { lis.unshift(nums[idx]); idx = s.prev[idx]; } viz += `
LIS: [${lis.join(', ')}]
`; } $('vizArea').innerHTML = viz; $('detailContent').innerHTML = '
' + s.msg + '
'; if (s.bestIdx >= 0) { $('detailContent').innerHTML += `
当前 LIS 长度:${s.dp[s.bestIdx]||1}
`; } if (s.stage === 'done') { $('resultContent').innerHTML = `
最长递增子序列长度 = ${s.dp[s.bestIdx]}
`; } $('hintText').textContent = s.msg; const stages = ['init→初始化','check→检查','update→更新','doneI→完成I','done→完成']; $('pipeline').innerHTML = stages.map(st => { const [k,l] = st.split('→'); return `${l}`; }).join('→'); } function init() { const sel = $('exampleSelect'); examples.forEach((e,i) => { sel.innerHTML += ``; }); $('inputArea').value = '[10,9,2,5,3,7,101,18]'; buildSteps(examples[0].input); stepCtrl = new StepController({onStep: render}); stepCtrl.setSteps(steps.map((_,i)=>i)); $('stepInfo').textContent = '步骤 1 / ' + steps.length; stepCtrl.onStep = (idx) => { render(idx); $('stepInfo').textContent = `步骤 ${idx+1} / ${steps.length}`; }; $('applyBtn').onclick = () => { try { const arr = JSON.parse($('inputArea').value); buildSteps(arr); stepCtrl.setSteps(steps.map((_,i)=>i)); render(0); $('stepInfo').textContent = '步骤 1 / ' + steps.length; } catch(e) { alert('请输入合法 JSON 数组'); } }; $('exampleSelect').onchange = () => { const e = examples[parseInt($('exampleSelect').value)]; $('inputArea').value = JSON.stringify(e.input); buildSteps(e.input); stepCtrl.setSteps(steps.map((_,i)=>i)); render(0); }; $('prevBtn').onclick = () => stepCtrl.prev(); $('nextBtn').onclick = () => stepCtrl.next(); $('jumpBtn').onclick = () => stepCtrl.jumpToEnd(); $('autoBtn').onclick = () => { const on = stepCtrl.toggleAuto(); $('autoBtn').textContent = on ? '暂停' : '自动播放'; }; $('resetBtn').onclick = () => { stepCtrl.reset(); $('autoBtn').textContent = '自动播放'; }; } init(); $('codeArea').innerHTML = renderCode(`def lengthOfLIS(nums): n = len(nums) dp = [1] * n for i in range(1, n): for j in range(i): if nums[j] < nums[i]: dp[i] = max(dp[i], dp[j] + 1) return max(dp)`, {lang:'Python'}); ''' def js_maximum_product_subarray(): return r''' const examples = [ {input: [2,3,-2,4], label: '示例1: [2,3,-2,4]'}, {input: [-2,0,-1], label: '示例2: [-2,0,-1]'}, {input: [-2,3,-4], label: '示例3: [-2,3,-4]'}, ]; let nums, steps, stepCtrl; function buildSteps(arr) { nums = [...arr]; steps = []; const n = nums.length; const maxDP = new Array(n).fill(0); const minDP = new Array(n).fill(0); maxDP[0] = nums[0]; minDP[0] = nums[0]; let globalMax = nums[0]; steps.push({stage:'init', msg:`maxDP[0]=${nums[0]},minDP[0]=${nums[0]}(同时维护最大和最小)`, maxDP:[...maxDP], minDP:[...minDP], current:-1, globalMax, candidates:null}); for (let i = 1; i < n; i++) { const candidates = [nums[i], maxDP[i-1]*nums[i], minDP[i-1]*nums[i]]; maxDP[i] = Math.max(...candidates); minDP[i] = Math.min(...candidates); globalMax = Math.max(globalMax, maxDP[i]); steps.push({stage:'calc', msg:`nums[${i}]=${nums[i]}:候选值 = [${nums[i]}, ${maxDP[i-1]}×${nums[i]}=${candidates[1]}, ${minDP[i-1]}×${nums[i]}=${candidates[2]}]`, maxDP:[...maxDP], minDP:[...minDP], current:i, globalMax, candidates}); steps.push({stage:'fill', msg:`maxDP[${i}]=${maxDP[i]},minDP[${i}]=${minDP[i]},全局最大=${globalMax}`, maxDP:[...maxDP], minDP:[...minDP], current:i, globalMax, candidates}); } steps.push({stage:'done', msg:`乘积最大子数组的积 = ${globalMax}`, maxDP:[...maxDP], minDP:[...minDP], current:n-1, globalMax, candidates:null}); } function render(step) { const s = steps[step]; const hl = {}; if (s.current >= 0) hl[s.current] = 'orange'; let viz = renderArray(nums, {highlights: hl}); viz += '
maxDP(最大乘积):
'; const maxHl = {}; if (s.current >= 0) maxHl[s.current] = 'green'; viz += renderArray(s.maxDP, {highlights: maxHl}); viz += '
minDP(最小乘积):
'; const minHl = {}; if (s.current >= 0) minHl[s.current] = 'purple'; viz += renderArray(s.minDP, {highlights: minHl}); if (s.candidates) { viz += '
三个候选:
'; viz += '
'; const labels = ['当前数', 'maxDP×当前', 'minDP×当前']; s.candidates.forEach((c, idx) => { const isMax = c === s.maxDP[s.current]; const isMin = c === s.minDP[s.current]; let bd = '1px solid #cbd5e1', bg = '#f8fafc'; if (isMax && isMin) { bd = '2px solid #8b5cf6'; bg = '#ede9fe'; } else if (isMax) { bd = '2px solid #16a34a'; bg = '#dcfce7'; } else if (isMin) { bd = '2px solid #7c3aed'; bg = '#ede9fe'; } viz += `${labels[idx]}=${c}`; }); viz += '
'; } $('vizArea').innerHTML = viz; $('detailContent').innerHTML = '
' + s.msg + '
'; $('detailContent').innerHTML += `
全局最大乘积:${s.globalMax}
`; if (s.stage === 'done') { $('resultContent').innerHTML = `
乘积最大子数组的积 = ${s.globalMax}
`; } $('hintText').textContent = s.msg; const stages = ['init→初始化','calc→计算','fill→填充','done→完成']; $('pipeline').innerHTML = stages.map(st => { const [k,l] = st.split('→'); return `${l}`; }).join('→'); } function init() { const sel = $('exampleSelect'); examples.forEach((e,i) => { sel.innerHTML += ``; }); $('inputArea').value = '[2,3,-2,4]'; buildSteps(examples[0].input); stepCtrl = new StepController({onStep: render}); stepCtrl.setSteps(steps.map((_,i)=>i)); $('stepInfo').textContent = '步骤 1 / ' + steps.length; stepCtrl.onStep = (idx) => { render(idx); $('stepInfo').textContent = `步骤 ${idx+1} / ${steps.length}`; }; $('applyBtn').onclick = () => { try { const arr = JSON.parse($('inputArea').value); buildSteps(arr); stepCtrl.setSteps(steps.map((_,i)=>i)); render(0); $('stepInfo').textContent = '步骤 1 / ' + steps.length; } catch(e) { alert('请输入合法 JSON 数组'); } }; $('exampleSelect').onchange = () => { const e = examples[parseInt($('exampleSelect').value)]; $('inputArea').value = JSON.stringify(e.input); buildSteps(e.input); stepCtrl.setSteps(steps.map((_,i)=>i)); render(0); }; $('prevBtn').onclick = () => stepCtrl.prev(); $('nextBtn').onclick = () => stepCtrl.next(); $('jumpBtn').onclick = () => stepCtrl.jumpToEnd(); $('autoBtn').onclick = () => { const on = stepCtrl.toggleAuto(); $('autoBtn').textContent = on ? '暂停' : '自动播放'; }; $('resetBtn').onclick = () => { stepCtrl.reset(); $('autoBtn').textContent = '自动播放'; }; } init(); $('codeArea').innerHTML = renderCode(`def maxProduct(nums): max_dp = min_dp = result = nums[0] for i in range(1, len(nums)): candidates = (nums[i], max_dp * nums[i], min_dp * nums[i]) max_dp = max(candidates) min_dp = min(candidates) result = max(result, max_dp) return result`, {lang:'Python'}); ''' def js_partition_equal_subset_sum(): return r''' const examples = [ {input: [1,5,11,5], label: '示例1: [1,5,11,5]'}, {input: [1,2,3,5], label: '示例2: [1,2,3,5]'}, {input: [1,1], label: '示例3: [1,1]'}, ]; let nums, steps, stepCtrl, target; function buildSteps(arr) { nums = [...arr]; steps = []; const sum = arr.reduce((a,b) => a + b, 0); if (sum % 2 !== 0) { target = 0; steps.push({stage:'done', msg:`总和=${sum}为奇数,无法等分`, dp:null, target:0, itemIdx:-1, curJ:-1}); return; } target = sum / 2; const n = arr.length; const dp = new Array(target + 1).fill(false); dp[0] = true; steps.push({stage:'init', msg:`总和=${sum},目标=${target},0-1背包:每件物品选或不选`, dp:[...dp], target, itemIdx:-1, curJ:-1}); for (let i = 0; i < n; i++) { steps.push({stage:'item', msg:`考虑物品 ${i}:nums[${i}]=${arr[i]}`, dp:[...dp], target, itemIdx:i, curJ:-1}); for (let j = target; j >= arr[i]; j--) { if (dp[j - arr[i]] && !dp[j]) { dp[j] = true; steps.push({stage:'fill', msg:`dp[${j}] = dp[${j}-${arr[i]}] = dp[${j-arr[i]}] = true → dp[${j}]=true`, dp:[...dp], target, itemIdx:i, curJ:j}); } } if (dp[target]) { steps.push({stage:'done', msg:`已找到和为 ${target} 的子集,可以等分!`, dp:[...dp], target, itemIdx:i, curJ:target}); return; } } steps.push({stage:'done', msg:`无法找到和为 ${target} 的子集`, dp:[...dp], target, itemIdx:-1, curJ:-1}); } function render(step) { const s = steps[step]; if (!s.dp) { $('vizArea').innerHTML = '
总和为奇数,直接返回 false
'; $('detailContent').innerHTML = '
' + s.msg + '
'; $('resultContent').innerHTML = '
无法等分
'; $('hintText').textContent = s.msg; $('pipeline').innerHTML = '完成'; return; } let viz = '
物品:
'; viz += '
'; nums.forEach((v, i) => { const cls = i === s.itemIdx ? 'orange' : (i < s.itemIdx || s.stage==='done' ? 'green' : 'default'); viz += `${v}`; }); viz += '
'; viz += `
dp 表(容量 0~${s.target}):
`; viz += '
'; for (let j = 0; j <= s.target; j++) { let cls = s.dp[j] ? 'green' : 'default'; if (j === s.curJ) cls = 'orange'; viz += `${s.dp[j]?'T':'F'}${j}`; } viz += '
'; $('vizArea').innerHTML = viz; $('detailContent').innerHTML = '
' + s.msg + '
'; if (s.dp[s.target]) { $('detailContent').innerHTML += `
dp[${s.target}] = true
`; } if (s.stage === 'done') { if (s.dp && s.dp[s.target]) { $('resultContent').innerHTML = `
可以等分为两个子集,和各为 ${s.target} ✅
`; } else { $('resultContent').innerHTML = '
无法等分 ❌
'; } } $('hintText').textContent = s.msg; const stages = ['init→初始化','item→物品','fill→填充','done→完成']; $('pipeline').innerHTML = stages.map(st => { const [k,l] = st.split('→'); return `${l}`; }).join('→'); } function init() { const sel = $('exampleSelect'); examples.forEach((e,i) => { sel.innerHTML += ``; }); $('inputArea').value = '[1,5,11,5]'; buildSteps(examples[0].input); stepCtrl = new StepController({onStep: render}); stepCtrl.setSteps(steps.map((_,i)=>i)); $('stepInfo').textContent = '步骤 1 / ' + steps.length; stepCtrl.onStep = (idx) => { render(idx); $('stepInfo').textContent = `步骤 ${idx+1} / ${steps.length}`; }; $('applyBtn').onclick = () => { try { const arr = JSON.parse($('inputArea').value); buildSteps(arr); stepCtrl.setSteps(steps.map((_,i)=>i)); render(0); $('stepInfo').textContent = '步骤 1 / ' + steps.length; } catch(e) { alert('请输入合法 JSON 数组'); } }; $('exampleSelect').onchange = () => { const e = examples[parseInt($('exampleSelect').value)]; $('inputArea').value = JSON.stringify(e.input); buildSteps(e.input); stepCtrl.setSteps(steps.map((_,i)=>i)); render(0); }; $('prevBtn').onclick = () => stepCtrl.prev(); $('nextBtn').onclick = () => stepCtrl.next(); $('jumpBtn').onclick = () => stepCtrl.jumpToEnd(); $('autoBtn').onclick = () => { const on = stepCtrl.toggleAuto(); $('autoBtn').textContent = on ? '暂停' : '自动播放'; }; $('resetBtn').onclick = () => { stepCtrl.reset(); $('autoBtn').textContent = '自动播放'; }; } init(); $('codeArea').innerHTML = renderCode(`def canPartition(nums): total = sum(nums) if total % 2: return False target = total // 2 dp = [False] * (target + 1) dp[0] = True for num in nums: for j in range(target, num - 1, -1): dp[j] = dp[j] or dp[j - num] if dp[target]: return True return dp[target]`, {lang:'Python'}); ''' def js_longest_valid_parentheses(): return r''' const examples = [ {input: '(()', label: '示例1: "(()"'}, {input: ')()())', label: '示例2: ")()())"'}, {input: '', label: '示例3: ""'}, ]; let s, steps, stepCtrl; function buildSteps(str) { s = str; steps = []; if (str.length === 0) { steps.push({stage:'done', msg:'空字符串,结果为 0', dp:[], stack:[-1], maxLen:0, current:-1, matched:-1}); return; } const n = str.length; const dp = new Array(n).fill(0); const stackArr = [-1]; let maxLen = 0; steps.push({stage:'init', msg:'用栈方法:栈中保存索引,初始压入 -1 作为基线', dp:[...dp], stack:[-1], maxLen:0, current:-1, matched:-1}); for (let i = 0; i < n; i++) { steps.push({stage:'check', msg:`处理 s[${i}]='${str[i]}'`, dp:[...dp], stack:[...stackArr], maxLen, current:i, matched:-1}); if (str[i] === '(') { stackArr.push(i); steps.push({stage:'push', msg:`遇到 '(',压入索引 ${i}`, dp:[...dp], stack:[...stackArr], maxLen, current:i, matched:-1}); } else { if (stackArr.length > 1) { stackArr.pop(); const start = stackArr[stackArr.length - 1]; const len = i - start; maxLen = Math.max(maxLen, len); dp[i] = len; steps.push({stage:'pop', msg:`遇到 ')',弹出栈顶,栈顶=${start},有效长度=${i}-${start}=${len}`, dp:[...dp], stack:[...stackArr], maxLen, current:i, matched:start}); } else { stackArr.length = 0; stackArr.push(i); steps.push({stage:'reset', msg:`遇到 ')' 但栈空(无匹配),重置基线为 ${i}`, dp:[...dp], stack:[...stackArr], maxLen, current:i, matched:-1}); } } } steps.push({stage:'done', msg:`最长有效括号子串长度 = ${maxLen}`, dp:[...dp], stack:[...stackArr], maxLen, current:-1, matched:-1}); } function render(step) { const st = steps[step]; if (!st.dp || st.dp.length === 0) { $('vizArea').innerHTML = '
空字符串,结果为 0
'; $('detailContent').innerHTML = '
' + st.msg + '
'; $('resultContent').innerHTML = '
最长有效括号长度 = 0
'; $('hintText').textContent = st.msg; $('pipeline').innerHTML = '完成'; return; } let viz = '
字符串:
'; viz += '
'; for (let i = 0; i < s.length; i++) { let cls = 'default'; if (i === st.current) cls = 'orange'; else if (st.dp[i] > 0) cls = 'green'; else if (i === st.matched) cls = 'blue'; viz += `${s[i]}`; } viz += '
'; viz += '
'; for (let i = 0; i < s.length; i++) { viz += `${i}`; } viz += '
'; viz += '
dp 值(以各位置结尾的有效长度):
'; viz += '
'; for (let i = 0; i < s.length; i++) { let cls = st.dp[i] > 0 ? 'green' : 'default'; if (i === st.current) cls = 'orange'; viz += `${st.dp[i]}`; } viz += '
'; if (st.stack) { viz += '
栈:
'; viz += '
'; st.stack.forEach((v, idx) => { const cls = idx === st.stack.length - 1 ? 'blue' : 'default'; viz += `${v}`; }); viz += '
'; } $('vizArea').innerHTML = viz; $('detailContent').innerHTML = '
' + st.msg + '
'; if (st.maxLen > 0) { $('detailContent').innerHTML += `
当前最长有效长度:${st.maxLen}
`; } if (st.stage === 'done') { $('resultContent').innerHTML = `
最长有效括号子串长度 = ${st.maxLen}
`; } $('hintText').textContent = st.msg; const stages = ['init→初始化','check→检查','push→入栈','pop→匹配','reset→重置','done→完成']; $('pipeline').innerHTML = stages.map(st2 => { const [k,l] = st2.split('→'); return `${l}`; }).join('→'); } function init() { const sel = $('exampleSelect'); examples.forEach((e,i) => { sel.innerHTML += ``; }); $('inputArea').value = '"(()"'; buildSteps(examples[0].input); stepCtrl = new StepController({onStep: render}); stepCtrl.setSteps(steps.map((_,i)=>i)); $('stepInfo').textContent = '步骤 1 / ' + steps.length; stepCtrl.onStep = (idx) => { render(idx); $('stepInfo').textContent = `步骤 ${idx+1} / ${steps.length}`; }; $('applyBtn').onclick = () => { const m = $('inputArea').value.match(/"([^"]+)"/); if (!m) { alert('格式: "(()"'); return; } buildSteps(m[1]); stepCtrl.setSteps(steps.map((_,i)=>i)); render(0); $('stepInfo').textContent = '步骤 1 / ' + steps.length; }; $('exampleSelect').onchange = () => { const e = examples[parseInt($('exampleSelect').value)]; $('inputArea').value = `"${e.input}"`; buildSteps(e.input); stepCtrl.setSteps(steps.map((_,i)=>i)); render(0); }; $('prevBtn').onclick = () => stepCtrl.prev(); $('nextBtn').onclick = () => stepCtrl.next(); $('jumpBtn').onclick = () => stepCtrl.jumpToEnd(); $('autoBtn').onclick = () => { const on = stepCtrl.toggleAuto(); $('autoBtn').textContent = on ? '暂停' : '自动播放'; }; $('resetBtn').onclick = () => { stepCtrl.reset(); $('autoBtn').textContent = '自动播放'; }; } init(); $('codeArea').innerHTML = renderCode(`def longestValidParentheses(s): stack = [-1] max_len = 0 for i, c in enumerate(s): if c == '(': stack.append(i) else: stack.pop() if not stack: stack.append(i) else: max_len = max(max_len, i - stack[-1]) return max_len`, {lang:'Python'}); '''