Files
illustrated-algorithm/dp_algos.py
T
2026-08-24 04:35:13 +00:00

1172 lines
55 KiB
Python
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.
"""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 = '<div style="display:flex;align-items:flex-end;gap:3px;margin:12px 0 8px;">';
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 += `<div style="width:42px;height:${36+i*6}px;background:${bg};border-radius:6px 6px 0 0;display:flex;flex-direction:column;align-items:center;justify-content:flex-end;padding:4px 0;font-size:11px;font-weight:600;color:${color};transition:all 0.3s;">
<span style="font-size:13px;">${s.dp[i]||''}</span><span style="font-size:9px;opacity:0.7;">${i}</span></div>`;
}
viz += '</div>';
viz += '<div style="margin-top:12px;"><b>dp 表:</b></div>';
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 += `<div class="formula-box" style="margin-top:8px;">dp[${s.current}] = dp[${s.from1}] + dp[${s.from2}] = ${s.dp[s.from1]} + ${s.dp[s.from2]}</div>`;
}
$('vizArea').innerHTML = viz;
$('detailContent').innerHTML = '<div class="calc-block">' + s.msg + '</div>';
if (s.stage !== 'done' && s.current > 0) {
$('detailContent').innerHTML += `<div class="current-answer">dp[${s.current}] = <b>${s.dp[s.current]}</b></div>`;
}
if (s.stage === 'done') {
$('resultContent').innerHTML = `<div class="final-answer">爬到第 ${n} 阶有 <b>${s.dp[n]}</b> 种方法</div>`;
}
$('hintText').textContent = s.msg;
const stages = ['init→初始化','calc→递推计算','fill→填充','done→完成'];
$('pipeline').innerHTML = stages.map(st => {
const [k,l] = st.split('→');
return `<span class="pipe-step ${s.stage===k?'active':''}">${l}</span>`;
}).join('<i>→</i>');
}
function init() {
const sel = $('exampleSelect');
examples.forEach((e,i) => { sel.innerHTML += `<option value="${i}">${e.label}</option>`; });
$('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 = '<div style="display:flex;flex-direction:column;align-items:center;gap:4px;padding:12px 0;">';
const maxRow = s.triangle.length;
for (let i = 0; i < maxRow; i++) {
const gap = Math.max(2, (maxRow - i) * 6);
viz += `<div style="display:flex;gap:${gap}px;justify-content:center;">`;
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 += `<span class="chip ${cls}" style="min-width:32px;height:28px;font-size:13px;">${val}</span>`;
}
viz += '</div>';
}
if (s.stage === 'newRow' || s.stage === 'calc') {
viz += `<div style="display:flex;gap:2px;justify-content:center;margin-top:2px;">`;
for (let j = 0; j <= s.row; j++) {
viz += `<span style="min-width:32px;height:28px;border:2px dashed var(--orange);border-radius:10px;display:flex;align-items:center;justify-content:center;font-size:13px;color:var(--text-muted);">?</span>`;
}
viz += '</div>';
}
viz += '</div>';
$('vizArea').innerHTML = viz;
let detail = '<div class="calc-block">' + s.msg + '</div>';
if (s.triangle.length > 0) {
detail += '<div style="margin-top:8px;"><b>当前三角:</b></div>';
s.triangle.forEach((row, i) => {
detail += `<div style="margin:2px 0;"><code>${i}: [${row.join(', ')}]</code></div>`;
});
}
$('detailContent').innerHTML = detail;
if (s.stage === 'done') {
$('resultContent').innerHTML = `<div class="final-answer">杨辉三角 <b>${numRows}</b> 行已生成</div>`;
}
$('hintText').textContent = s.msg;
const stages = ['init→开始','newRow→新行','calc→计算','fillRow→填充','done→完成'];
$('pipeline').innerHTML = stages.map(st => {
const [k,l] = st.split('→');
return `<span class="pipe-step ${s.stage===k?'active':''}">${l}</span>`;
}).join('<i>→</i>');
}
function init() {
const sel = $('exampleSelect');
examples.forEach((e,i) => { sel.innerHTML += `<option value="${i}">${e.label}</option>`; });
$('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 = '<div style="display:flex;gap:6px;margin:8px 0;">';
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 += `<div style="width:56px;padding:8px 4px;background:${bg};border:${border};border-radius:10px;text-align:center;font-size:13px;font-weight:600;color:${color};transition:all 0.3s;">
<div style="font-size:16px;margin-bottom:2px;">🏠</div>
<div>${v}</div>
<div style="font-size:10px;color:${isRobbed?'#16a34a':'#94a3b8'};">${isRobbed?'偷':'·'}</div>
</div>`;
});
viz += '</div>';
viz += '<div style="margin-top:12px;"><b>dp 值:</b></div>';
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 += `<div class="formula-box" style="margin-top:8px;">dp[${s.current}] = max(不偷=${s.notRob}, 偷=${s.doRob})</div>`;
}
$('vizArea').innerHTML = viz;
$('detailContent').innerHTML = '<div class="calc-block">' + s.msg + '</div>';
if (s.dp.length > 0) {
$('detailContent').innerHTML += `<div class="current-answer">当前最大金额:<b>${s.dp[Math.max(0,s.current)]}</b></div>`;
}
if (s.stage === 'done') {
$('resultContent').innerHTML = `<div class="final-answer">最多可以偷取 <b>${s.dp[nums.length-1]}</b></div>`;
}
$('hintText').textContent = s.msg;
const stages = ['init→初始化','init2→初始化','compare→比较','rob→偷','skip→不偷','done→完成'];
$('pipeline').innerHTML = stages.map(st => {
const [k,l] = st.split('→');
return `<span class="pipe-step ${s.stage===k?'active':''}">${l}</span>`;
}).join('<i>→</i>');
}
function init() {
const sel = $('exampleSelect');
examples.forEach((e,i) => { sel.innerHTML += `<option value="${i}">${e.label}</option>`; });
$('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 = '<div style="margin-top:8px;"><b>dp 表:</b></div>';
viz += '<div style="display:flex;flex-wrap:wrap;gap:4px;margin:8px 0;">';
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 += `<span class="chip-group"><span class="chip ${cls}" style="min-width:32px;font-size:13px;">${val}</span><span class="chip-index">${i}</span></span>`;
}
viz += '</div>';
if (s.candidates && s.candidates.length > 0) {
viz += '<div style="margin-top:8px;"><b>候选平方数:</b></div>';
viz += '<div style="display:flex;gap:6px;flex-wrap:wrap;margin:4px 0;">';
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 += `<span style="padding:4px 10px;background:${bg};border:${bd};border-radius:8px;font-size:13px;">${c.j}²=${c.sq} → dp[${s.current-c.sq}]+1=${c.val===Infinity?'∞':c.val}</span>`;
});
viz += '</div>';
}
$('vizArea').innerHTML = viz;
$('detailContent').innerHTML = '<div class="calc-block">' + s.msg + '</div>';
if (s.current > 0 && s.dp[s.current] !== Infinity) {
$('detailContent').innerHTML += `<div class="current-answer">dp[${s.current}] = <b>${s.dp[s.current]}</b></div>`;
}
if (s.stage === 'done') {
$('resultContent').innerHTML = `<div class="final-answer">${n} 最少需要 <b>${s.dp[n]}</b> 个完全平方数</div>`;
}
$('hintText').textContent = s.msg;
const stages = ['init→初始化','try→尝试','fill→填充','done→完成'];
$('pipeline').innerHTML = stages.map(st => {
const [k,l] = st.split('→');
return `<span class="pipe-step ${s.stage===k?'active':''}">${l}</span>`;
}).join('<i>→</i>');
}
function init() {
const sel = $('exampleSelect');
examples.forEach((e,i) => { sel.innerHTML += `<option value="${i}">${e.label}</option>`; });
$('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 = '<div style="margin-top:8px;"><b>金额轴 & dp 表:</b></div>';
viz += '<div style="display:flex;flex-wrap:wrap;gap:4px;margin:8px 0;">';
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 += `<span class="chip-group"><span class="chip ${cls}" style="min-width:36px;font-size:13px;">${val}</span><span class="chip-index">${i}</span></span>`;
}
viz += '</div>';
viz += '<div style="margin-top:8px;"><b>硬币面额:</b></div>';
viz += '<div style="display:flex;gap:6px;margin:4px 0;">';
coins.forEach(c => {
const isBest = c === s.bestCoin;
const cls = isBest ? 'green' : 'default';
viz += `<span class="chip ${cls}" style="border-radius:50%;min-width:36px;height:36px;">${c}</span>`;
});
viz += '</div>';
if (s.candidates && s.candidates.length > 0) {
viz += '<div style="margin-top:8px;"><b>候选:</b></div>';
viz += '<div style="display:flex;gap:6px;flex-wrap:wrap;margin:4px 0;">';
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 += `<span style="padding:4px 10px;background:${bg};border:${bd};border-radius:8px;font-size:13px;">用${c.coin} → dp[${c.prev}]+1=${c.val===Infinity?'∞':c.val}</span>`;
});
viz += '</div>';
}
$('vizArea').innerHTML = viz;
$('detailContent').innerHTML = '<div class="calc-block">' + s.msg + '</div>';
if (s.current >= 0 && s.dp[s.current] !== Infinity) {
$('detailContent').innerHTML += `<div class="current-answer">dp[${s.current}] = <b>${s.dp[s.current]}</b></div>`;
}
if (s.stage === 'done') {
if (s.dp[amount] === Infinity) {
$('resultContent').innerHTML = '<div class="final-answer" style="border-color:#f87171;background:#fef2f2;">无法凑出该金额,返回 <b>-1</b></div>';
} else {
$('resultContent').innerHTML = `<div class="final-answer">最少需要 <b>${s.dp[amount]}</b> 枚硬币</div>`;
}
}
$('hintText').textContent = s.msg;
const stages = ['init→初始化','try→尝试','fill→填充','done→完成'];
$('pipeline').innerHTML = stages.map(st => {
const [k,l] = st.split('→');
return `<span class="pipe-step ${s.stage===k?'active':''}">${l}</span>`;
}).join('<i>→</i>');
}
function init() {
const sel = $('exampleSelect');
examples.forEach((e,i) => { sel.innerHTML += `<option value="${i}">${e.label}</option>`; });
$('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 = '<div style="margin-top:8px;"><b>字符串:</b></div>';
viz += '<div style="display:flex;gap:2px;margin:8px 0;">';
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 += `<span class="chip ${cls}" style="min-width:28px;font-size:14px;">${s[i]}</span>`;
}
viz += '</div>';
viz += '<div style="display:flex;gap:2px;margin-top:-4px;">';
for (let i = 0; i <= s.length; i++) {
viz += `<span style="min-width:28px;text-align:center;font-size:10px;color:var(--text-muted);">${i}</span>`;
}
viz += '</div>';
viz += '<div style="margin-top:8px;"><b>dp 状态:</b></div>';
viz += '<div style="display:flex;gap:3px;flex-wrap:wrap;margin:4px 0;">';
for (let i = 0; i <= s.length; i++) {
const isCur = i === st.i;
const cls = isCur ? 'orange' : (st.dp[i] ? 'green' : 'default');
viz += `<span class="chip ${cls}" style="min-width:32px;font-size:12px;height:28px;">${st.dp[i]?'T':'F'}</span>`;
}
viz += '</div>';
viz += '<div style="margin-top:8px;"><b>字典:</b></div>';
viz += '<div style="display:flex;gap:4px;flex-wrap:wrap;margin:4px 0;">';
wordDict.forEach(w => {
const isMatch = w === st.matchWord;
const cls = isMatch ? 'green' : 'default';
viz += `<span class="chip ${cls}" style="min-width:auto;padding:4px 10px;font-size:13px;">"${w}"</span>`;
});
viz += '</div>';
$('vizArea').innerHTML = viz;
$('detailContent').innerHTML = '<div class="calc-block">' + st.msg + '</div>';
if (st.stage === 'done') {
$('resultContent').innerHTML = `<div class="final-answer">${st.matched ? `"${s}" 可以被分割 ✅` : `"${s}" 无法被分割 ❌`}</div>`;
}
$('hintText').textContent = st.msg;
const stages = ['init→初始化','checkI→检查','tryJ→尝试','found→找到','fail→失败','done→完成'];
$('pipeline').innerHTML = stages.map(st2 => {
const [k,l] = st2.split('→');
return `<span class="pipe-step ${st.stage===k?'active':''}">${l}</span>`;
}).join('<i>→</i>');
}
function init() {
const sel = $('exampleSelect');
examples.forEach((e,i) => { sel.innerHTML += `<option value="${i}">${e.label}</option>`; });
$('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<i 使得 nums[j]<nums[${i}]`, dp:[...dp], current:i, bestIdx, j:-1, prev:[...prev]});
for (let j = 0; j < i; j++) {
if (nums[j] < nums[i] && dp[j] + 1 > 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 += '<div style="margin-top:8px;"><b>dp 值(以每个元素结尾的 LIS 长度):</b></div>';
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 += `<div style="margin-top:8px;color:var(--green);"><b>LIS: [${lis.join(', ')}]</b></div>`;
}
$('vizArea').innerHTML = viz;
$('detailContent').innerHTML = '<div class="calc-block">' + s.msg + '</div>';
if (s.bestIdx >= 0) {
$('detailContent').innerHTML += `<div class="current-answer">当前 LIS 长度:<b>${s.dp[s.bestIdx]||1}</b></div>`;
}
if (s.stage === 'done') {
$('resultContent').innerHTML = `<div class="final-answer">最长递增子序列长度 = <b>${s.dp[s.bestIdx]}</b></div>`;
}
$('hintText').textContent = s.msg;
const stages = ['init→初始化','check→检查','update→更新','doneI→完成I','done→完成'];
$('pipeline').innerHTML = stages.map(st => {
const [k,l] = st.split('→');
return `<span class="pipe-step ${s.stage===k?'active':''}">${l}</span>`;
}).join('<i>→</i>');
}
function init() {
const sel = $('exampleSelect');
examples.forEach((e,i) => { sel.innerHTML += `<option value="${i}">${e.label}</option>`; });
$('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 += '<div style="margin-top:12px;"><b>maxDP(最大乘积):</b></div>';
const maxHl = {};
if (s.current >= 0) maxHl[s.current] = 'green';
viz += renderArray(s.maxDP, {highlights: maxHl});
viz += '<div style="margin-top:8px;"><b>minDP(最小乘积):</b></div>';
const minHl = {};
if (s.current >= 0) minHl[s.current] = 'purple';
viz += renderArray(s.minDP, {highlights: minHl});
if (s.candidates) {
viz += '<div style="margin-top:8px;"><b>三个候选:</b></div>';
viz += '<div style="display:flex;gap:8px;margin:4px 0;">';
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 += `<span style="padding:4px 10px;background:${bg};border:${bd};border-radius:8px;font-size:13px;">${labels[idx]}=<b>${c}</b></span>`;
});
viz += '</div>';
}
$('vizArea').innerHTML = viz;
$('detailContent').innerHTML = '<div class="calc-block">' + s.msg + '</div>';
$('detailContent').innerHTML += `<div class="current-answer">全局最大乘积:<b>${s.globalMax}</b></div>`;
if (s.stage === 'done') {
$('resultContent').innerHTML = `<div class="final-answer">乘积最大子数组的积 = <b>${s.globalMax}</b></div>`;
}
$('hintText').textContent = s.msg;
const stages = ['init→初始化','calc→计算','fill→填充','done→完成'];
$('pipeline').innerHTML = stages.map(st => {
const [k,l] = st.split('→');
return `<span class="pipe-step ${s.stage===k?'active':''}">${l}</span>`;
}).join('<i>→</i>');
}
function init() {
const sel = $('exampleSelect');
examples.forEach((e,i) => { sel.innerHTML += `<option value="${i}">${e.label}</option>`; });
$('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 = '<div class="formula-box">总和为奇数,直接返回 false</div>';
$('detailContent').innerHTML = '<div class="calc-block">' + s.msg + '</div>';
$('resultContent').innerHTML = '<div class="final-answer" style="border-color:#f87171;background:#fef2f2;">无法等分</div>';
$('hintText').textContent = s.msg;
$('pipeline').innerHTML = '<span class="pipe-step active">完成</span>';
return;
}
let viz = '<div style="margin-top:8px;"><b>物品:</b></div>';
viz += '<div style="display:flex;gap:4px;margin:4px 0;">';
nums.forEach((v, i) => {
const cls = i === s.itemIdx ? 'orange' : (i < s.itemIdx || s.stage==='done' ? 'green' : 'default');
viz += `<span class="chip ${cls}" style="min-width:36px;">${v}</span>`;
});
viz += '</div>';
viz += `<div style="margin-top:8px;"><b>dp 表(容量 0~${s.target}):</b></div>`;
viz += '<div style="display:flex;flex-wrap:wrap;gap:3px;margin:4px 0;">';
for (let j = 0; j <= s.target; j++) {
let cls = s.dp[j] ? 'green' : 'default';
if (j === s.curJ) cls = 'orange';
viz += `<span class="chip-group"><span class="chip ${cls}" style="min-width:28px;font-size:12px;height:26px;">${s.dp[j]?'T':'F'}</span><span class="chip-index">${j}</span></span>`;
}
viz += '</div>';
$('vizArea').innerHTML = viz;
$('detailContent').innerHTML = '<div class="calc-block">' + s.msg + '</div>';
if (s.dp[s.target]) {
$('detailContent').innerHTML += `<div class="current-answer">dp[${s.target}] = <b>true</b></div>`;
}
if (s.stage === 'done') {
if (s.dp && s.dp[s.target]) {
$('resultContent').innerHTML = `<div class="final-answer">可以等分为两个子集,和各为 <b>${s.target}</b> ✅</div>`;
} else {
$('resultContent').innerHTML = '<div class="final-answer" style="border-color:#f87171;background:#fef2f2;">无法等分 ❌</div>';
}
}
$('hintText').textContent = s.msg;
const stages = ['init→初始化','item→物品','fill→填充','done→完成'];
$('pipeline').innerHTML = stages.map(st => {
const [k,l] = st.split('→');
return `<span class="pipe-step ${s.stage===k?'active':''}">${l}</span>`;
}).join('<i>→</i>');
}
function init() {
const sel = $('exampleSelect');
examples.forEach((e,i) => { sel.innerHTML += `<option value="${i}">${e.label}</option>`; });
$('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 = '<div class="formula-box">空字符串,结果为 0</div>';
$('detailContent').innerHTML = '<div class="calc-block">' + st.msg + '</div>';
$('resultContent').innerHTML = '<div class="final-answer">最长有效括号长度 = <b>0</b></div>';
$('hintText').textContent = st.msg;
$('pipeline').innerHTML = '<span class="pipe-step active">完成</span>';
return;
}
let viz = '<div style="margin-top:8px;"><b>字符串:</b></div>';
viz += '<div style="display:flex;gap:3px;margin:8px 0;">';
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 += `<span class="chip ${cls}" style="min-width:28px;font-size:14px;">${s[i]}</span>`;
}
viz += '</div>';
viz += '<div style="display:flex;gap:3px;margin-top:-4px;">';
for (let i = 0; i < s.length; i++) {
viz += `<span style="min-width:28px;text-align:center;font-size:10px;color:var(--text-muted);">${i}</span>`;
}
viz += '</div>';
viz += '<div style="margin-top:8px;"><b>dp 值(以各位置结尾的有效长度):</b></div>';
viz += '<div style="display:flex;gap:3px;margin:4px 0;">';
for (let i = 0; i < s.length; i++) {
let cls = st.dp[i] > 0 ? 'green' : 'default';
if (i === st.current) cls = 'orange';
viz += `<span class="chip ${cls}" style="min-width:28px;font-size:12px;height:28px;">${st.dp[i]}</span>`;
}
viz += '</div>';
if (st.stack) {
viz += '<div style="margin-top:8px;"><b>栈:</b></div>';
viz += '<div style="display:flex;gap:3px;flex-wrap:wrap;margin:4px 0;">';
st.stack.forEach((v, idx) => {
const cls = idx === st.stack.length - 1 ? 'blue' : 'default';
viz += `<span class="chip ${cls}" style="min-width:28px;font-size:12px;height:28px;">${v}</span>`;
});
viz += '</div>';
}
$('vizArea').innerHTML = viz;
$('detailContent').innerHTML = '<div class="calc-block">' + st.msg + '</div>';
if (st.maxLen > 0) {
$('detailContent').innerHTML += `<div class="current-answer">当前最长有效长度:<b>${st.maxLen}</b></div>`;
}
if (st.stage === 'done') {
$('resultContent').innerHTML = `<div class="final-answer">最长有效括号子串长度 = <b>${st.maxLen}</b></div>`;
}
$('hintText').textContent = st.msg;
const stages = ['init→初始化','check→检查','push→入栈','pop→匹配','reset→重置','done→完成'];
$('pipeline').innerHTML = stages.map(st2 => {
const [k,l] = st2.split('→');
return `<span class="pipe-step ${st.stage===k?'active':''}">${l}</span>`;
}).join('<i>→</i>');
}
function init() {
const sel = $('exampleSelect');
examples.forEach((e,i) => { sel.innerHTML += `<option value="${i}">${e.label}</option>`; });
$('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'});
'''