"""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 += `${e.label} `; });
$('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 += `${e.label} `; });
$('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 += `${e.label} `; });
$('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 += `${e.label} `; });
$('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 += `${e.label} `; });
$('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 += `${e.label} `; });
$('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 += `${e.label} `; });
$('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 += `${e.label} `; });
$('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 += `${e.label} `; });
$('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 += `${e.label} `; });
$('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'});
'''