"""Binary-tree algorithm JS generators for LeetCode Hot 100 图解."""
# Common JS helpers – inline in each function so pages are self-contained
_TREE_UTILS = r'''
function arrayToTree(arr) {
if (!arr || arr.length === 0) return null;
let idCtr = 0;
const nodes = arr.map(v => v === null ? null : {val: v, left: null, right: null, _id: 'n' + (idCtr++)});
for (let i = 0; i < nodes.length; i++) {
if (!nodes[i]) continue;
const li = 2*i+1, ri = 2*i+2;
if (li < nodes.length) nodes[i].left = nodes[li];
if (ri < nodes.length) nodes[i].right = nodes[ri];
}
return nodes[0];
}
function treeToArray(root) {
if (!root) return [];
const res = [], q = [root];
while (q.length) {
const n = q.shift();
if (n === null) { res.push(null); continue; }
res.push(n.val); q.push(n.left, n.right);
}
while (res.length && res[res.length-1] === null) res.pop();
return res;
}
function deepClone(node) {
if (!node) return null;
return {val: node.val, _id: node._id, left: deepClone(node.left), right: deepClone(node.right)};
}
'''
def js_binary_tree_inorder_traversal():
return _TREE_UTILS + r'''
const examples = [
{tree: [1,null,2,3], label: '示例1: [1,null,2,3] → [1,3,2]'},
{tree: [2,1,3], label: '示例2: [2,1,3] → [1,2,3]'},
{tree: [1,2,3,4,5,6,7], label: '示例3: [1,2,3,4,5,6,7]'},
];
let root, steps, stepCtrl;
function buildSteps(arr) {
root = arrayToTree(arr);
steps = []; const result = [];
const stack = []; let cur = root;
steps.push({stage:'init',hl:{},stack:[],result:[],msg:'初始化:空栈,当前指针指向根节点,中序遍历 左→根→右'});
while (cur || stack.length) {
while (cur) {
stack.push(cur);
steps.push({stage:'push',hl:{[cur._id]:'active'},stack:stack.map(n=>n.val),result:[...result],
msg:`走到节点 ${cur.val},入栈`});
cur = cur.left;
}
cur = stack.pop();
result.push(cur.val);
steps.push({stage:'visit',hl:{[cur._id]:'current'},stack:stack.map(n=>n.val),result:[...result],
msg:`弹出栈顶 ${cur.val},访问(加入结果):[${result.join(',')}]`});
cur = cur.right;
}
steps.push({stage:'done',hl:{},stack:[],result:[...result],msg:`中序遍历完成:[${result.join(',')}]`});
}
function render(step) {
const s = steps[step];
let viz = renderBinaryTree(root, {highlights: s.hl});
viz += '
栈:
';
viz += renderStack(s.stack, {topIndex: s.stack.length-1});
if (s.result.length) viz += '结果:[' + s.result.join(', ') + ']
';
$('vizArea').innerHTML = viz;
$('detailContent').innerHTML = '' + s.msg + '
';
if (s.stage === 'done') {
$('resultContent').innerHTML = `中序遍历结果 = [${s.result}]
`;
}
$('hintText').textContent = s.msg;
const stages = ['init→初始化','push→入栈','visit→访问','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,null,2,3]';
buildSteps(examples[0].tree);
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('请输入合法数组,如 [1,null,2,3]'); }
};
$('exampleSelect').onchange = () => {
const e = examples[parseInt($('exampleSelect').value)];
$('inputArea').value = JSON.stringify(e.tree);
buildSteps(e.tree); 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 inorderTraversal(root):
res, stack = [], []
cur = root
while cur or stack:
while cur:
stack.append(cur)
cur = cur.left
cur = stack.pop()
res.append(cur.val)
cur = cur.right
return res`, {lang:'Python'});
'''
def js_maximum_depth_of_binary_tree():
return _TREE_UTILS + r'''
const examples = [
{tree: [3,9,20,null,null,15,7], label: '示例1: [3,9,20,null,null,15,7] → 3'},
{tree: [1,null,2], label: '示例2: [1,null,2] → 2'},
{tree: [], label: '示例3: [] → 0'},
];
let root, steps, stepCtrl;
function buildSteps(arr) {
root = arrayToTree(arr);
steps = [];
if (!root) { steps.push({stage:'done',hl:{},msg:'空树,深度 = 0',depth:0}); return; }
function dfs(node) {
if (!node) return 0;
steps.push({stage:'enter', hl:{[node._id]:'current'}, msg:`进入节点 ${node.val}`});
if (!node.left && !node.right) {
steps.push({stage:'leaf', hl:{[node._id]:'visited'}, msg:`节点 ${node.val} 是叶子,返回深度 1`, depth:1});
return 1;
}
const ld = node.left ? dfs(node.left) : 0;
const rd = node.right ? dfs(node.right) : 0;
const depth = Math.max(ld, rd) + 1;
steps.push({stage:'return', hl:{[node._id]:'visited'}, msg:`节点 ${node.val}:左深度=${ld},右深度=${rd},max(${ld},${rd})+1 = ${depth}`, depth});
return depth;
}
dfs(root);
const finalDepth = steps[steps.length-1].depth;
steps.push({stage:'done', hl:{}, msg:`最大深度 = ${finalDepth}`, depth:finalDepth});
}
function render(step) {
const s = steps[step];
let viz = renderBinaryTree(root, {highlights: s.hl});
$('vizArea').innerHTML = viz;
$('detailContent').innerHTML = '' + s.msg + '
';
if (s.depth !== undefined) {
$('detailContent').innerHTML += `当前计算深度:${s.depth}
`;
}
if (s.stage === 'done') {
$('resultContent').innerHTML = `最大深度 = ${s.depth}
`;
}
$('hintText').textContent = s.msg;
const stages = ['enter→进入','leaf→叶子','return→返回','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 = '[3,9,20,null,null,15,7]';
buildSteps(examples[0].tree);
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('请输入合法数组'); }
};
$('exampleSelect').onchange = () => {
const e = examples[parseInt($('exampleSelect').value)];
$('inputArea').value = JSON.stringify(e.tree);
buildSteps(e.tree); 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 maxDepth(root):
if not root:
return 0
left = maxDepth(root.left)
right = maxDepth(root.right)
return max(left, right) + 1`, {lang:'Python'});
'''
def js_invert_binary_tree():
return _TREE_UTILS + r'''
const examples = [
{tree: [4,2,7,1,3,6,9], label: '示例1: [4,2,7,1,3,6,9]'},
{tree: [2,1,3], label: '示例2: [2,1,3]'},
{tree: [], label: '示例3: []'},
];
let root, steps, stepCtrl;
function buildSteps(arr) {
root = arrayToTree(arr);
steps = [];
if (!root) { steps.push({stage:'done',hl:{},snap:null,msg:'空树无需翻转'}); return; }
function saveTree(node) {
if (!node) return null;
return {val:node.val, _id:node._id, left:saveTree(node.left), right:saveTree(node.right)};
}
function dfs(node) {
if (!node) return;
steps.push({stage:'visit', hl:{[node._id]:'current'}, msg:`访问节点 ${node.val},准备交换左右子树`, snap:saveTree(root)});
const tmp = node.left;
node.left = node.right;
node.right = tmp;
steps.push({stage:'swap', hl:{[node._id]:'active'}, msg:`交换节点 ${node.val} 的左右子树完成`, snap:saveTree(root)});
dfs(node.left);
dfs(node.right);
}
dfs(root);
steps.push({stage:'done', hl:{}, msg:'翻转完成!', snap:saveTree(root)});
}
function render(step) {
const s = steps[step];
let viz = renderBinaryTree(s.snap || root, {highlights: s.hl});
$('vizArea').innerHTML = viz;
$('detailContent').innerHTML = '' + s.msg + '
';
if (s.stage === 'done') {
$('resultContent').innerHTML = `翻转后的树 = [${treeToArray(s.snap || root)}]
`;
}
$('hintText').textContent = s.msg;
const stages = ['visit→访问','swap→交换','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 = '[4,2,7,1,3,6,9]';
buildSteps(examples[0].tree);
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('请输入合法数组'); }
};
$('exampleSelect').onchange = () => {
const e = examples[parseInt($('exampleSelect').value)];
$('inputArea').value = JSON.stringify(e.tree);
buildSteps(e.tree); 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 invertTree(root):
if not root:
return None
root.left, root.right = root.right, root.left
invertTree(root.left)
invertTree(root.right)
return root`, {lang:'Python'});
'''
def js_symmetric_tree():
return _TREE_UTILS + r'''
const examples = [
{tree: [1,2,2,3,4,4,3], label: '示例1: 对称 [1,2,2,3,4,4,3]'},
{tree: [1,2,2,null,3,null,3], label: '示例2: 不对称 [1,2,2,null,3,null,3]'},
{tree: [1], label: '示例3: [1] 单节点'},
];
let root, steps, stepCtrl;
function buildSteps(arr) {
root = arrayToTree(arr);
steps = [];
if (!root) { steps.push({stage:'done',hl:{},msg:'空树是对称的',isSym:true}); return; }
steps.push({stage:'init',hl:{},msg:'递归检查左右子树是否镜像对称',isSym:true});
function check(a, b) {
if (!a && !b) {
steps.push({stage:'match',hl:{},msg:'两个节点都为空,匹配 ✓',isSym:true});
return true;
}
if (!a || !b) {
const nonNull = a || b;
steps.push({stage:'mismatch',hl:{[nonNull._id]:'current'},msg:'一侧为空另一侧不为空,不匹配 ✗',isSym:false});
return false;
}
if (a.val !== b.val) {
steps.push({stage:'mismatch',hl:{[a._id]:'current',[b._id]:'current'},msg:`节点 ${a.val} ≠ ${b.val},不匹配 ✗`,isSym:false});
return false;
}
steps.push({stage:'compare',hl:{[a._id]:'active',[b._id]:'active'},msg:`比较 ${a.val} 和 ${b.val},相等 ✓`,isSym:true});
const outer = check(a.left, b.right);
if (!outer) return false;
return check(a.right, b.left);
}
const isSym = check(root.left, root.right);
steps.push({stage:'done',hl:{},msg:`判断结果:${isSym?'对称 ✓':'不对称 ✗'}`,isSym});
}
function render(step) {
const s = steps[step];
let viz = renderBinaryTree(root, {highlights: s.hl});
$('vizArea').innerHTML = viz;
$('detailContent').innerHTML = '' + s.msg + '
';
if (s.stage === 'done') {
$('resultContent').innerHTML = `是否对称:${s.isSym?'是 ✓':'否 ✗'}
`;
}
$('hintText').textContent = s.msg;
const stages = ['init→初始化','compare→比较','match→匹配','mismatch→不匹配','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,2,3,4,4,3]';
buildSteps(examples[0].tree);
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('请输入合法数组'); }
};
$('exampleSelect').onchange = () => {
const e = examples[parseInt($('exampleSelect').value)];
$('inputArea').value = JSON.stringify(e.tree);
buildSteps(e.tree); 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 isSymmetric(root):
def check(a, b):
if not a and not b: return True
if not a or not b: return False
return a.val == b.val and check(a.left, b.right) and check(a.right, b.left)
return check(root.left, root.right) if root else True`, {lang:'Python'});
'''
def js_diameter_of_binary_tree():
return _TREE_UTILS + r'''
const examples = [
{tree: [1,2,3,4,5], label: '示例1: [1,2,3,4,5] 直径=3'},
{tree: [1,2], label: '示例2: [1,2] 直径=1'},
{tree: [1,2,3,4], label: '示例3: [1,2,3,4]'},
];
let root, steps, stepCtrl;
function buildSteps(arr) {
root = arrayToTree(arr);
steps = [];
let maxDiameter = 0;
steps.push({stage:'init',hl:{},msg:'DFS 返回每个节点的深度,维护最大直径(左深度+右深度)',maxDia:0});
function dfs(node) {
if (!node) return 0;
steps.push({stage:'enter',hl:{[node._id]:'current'},msg:`进入节点 ${node.val}`,maxDia:maxDiameter});
const ld = dfs(node.left);
const rd = dfs(node.right);
const diameter = ld + rd;
if (diameter > maxDiameter) maxDiameter = diameter;
const depth = Math.max(ld, rd) + 1;
steps.push({stage:'calc',hl:{[node._id]:'visited'},msg:`节点 ${node.val}:左深=${ld},右深=${rd},直径=${ld}+${rd}=${diameter},最大直径=${maxDiameter},返回深度=${depth}`,maxDia:maxDiameter,depth});
return depth;
}
if (root) dfs(root);
steps.push({stage:'done',hl:{},msg:`最大直径 = ${maxDiameter}`,maxDia:maxDiameter});
}
function render(step) {
const s = steps[step];
let viz = renderBinaryTree(root, {highlights: s.hl});
$('vizArea').innerHTML = viz;
$('detailContent').innerHTML = '' + s.msg + '
';
if (s.maxDia !== undefined) {
$('detailContent').innerHTML += `当前最大直径:${s.maxDia}
`;
}
if (s.stage === 'done') {
$('resultContent').innerHTML = `二叉树的直径 = ${s.maxDia}
`;
}
$('hintText').textContent = s.msg;
const stages = ['init→初始化','enter→进入','calc→计算','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,4,5]';
buildSteps(examples[0].tree);
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('请输入合法数组'); }
};
$('exampleSelect').onchange = () => {
const e = examples[parseInt($('exampleSelect').value)];
$('inputArea').value = JSON.stringify(e.tree);
buildSteps(e.tree); 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 diameterOfBinaryTree(root):
max_d = 0
def depth(node):
nonlocal max_d
if not node: return 0
l = depth(node.left)
r = depth(node.right)
max_d = max(max_d, l + r)
return max(l, r) + 1
depth(root)
return max_d`, {lang:'Python'});
'''
def js_binary_tree_level_order_traversal():
return _TREE_UTILS + r'''
const examples = [
{tree: [3,9,20,null,null,15,7], label: '示例1: [3,9,20,null,null,15,7]'},
{tree: [1], label: '示例2: [1]'},
{tree: [], label: '示例3: []'},
];
let root, steps, stepCtrl;
function buildSteps(arr) {
root = arrayToTree(arr);
steps = [];
if (!root) { steps.push({stage:'done',hl:{},queue:[],result:[],msg:'空树,返回 []'}); return; }
const queue = [root]; const result = []; let level = 0;
steps.push({stage:'init',hl:{},queue:[root.val],result:[],msg:'初始化队列,根节点入队'});
while (queue.length) {
const size = queue.length;
const levelVals = [];
const levelNodes = [];
steps.push({stage:'start_level',hl:{},queue:queue.map(n=>n.val),result:JSON.parse(JSON.stringify(result)),msg:`开始处理第 ${level} 层,本层 ${size} 个节点`});
for (let i = 0; i < size; i++) {
const node = queue.shift();
levelVals.push(node.val);
levelNodes.push(node);
const hl = {[node._id]:'current'};
levelNodes.forEach((n,j) => { if (j < levelVals.length-1) hl[n._id] = 'visited'; });
if (node.left) queue.push(node.left);
if (node.right) queue.push(node.right);
steps.push({stage:'visit',hl,queue:queue.map(n=>n.val),result:JSON.parse(JSON.stringify(result)),msg:`访问节点 ${node.val}`});
}
result.push(levelVals);
const hl2 = {}; levelNodes.forEach(n => hl2[n._id] = 'visited');
steps.push({stage:'end_level',hl:hl2,queue:queue.map(n=>n.val),result:JSON.parse(JSON.stringify(result)),msg:`第 ${level} 层完成:[${levelVals}]`});
level++;
}
steps.push({stage:'done',hl:{},queue:[],result:JSON.parse(JSON.stringify(result)),msg:'层序遍历完成'});
}
function render(step) {
const s = steps[step];
let viz = renderBinaryTree(root, {highlights: s.hl});
viz += '队列:
';
viz += renderQueue(s.queue);
viz += '结果:' + JSON.stringify(s.result) + '
';
$('vizArea').innerHTML = viz;
$('detailContent').innerHTML = '' + s.msg + '
';
if (s.stage === 'done') {
$('resultContent').innerHTML = `层序遍历 = ${JSON.stringify(s.result)}
`;
}
$('hintText').textContent = s.msg;
const stages = ['init→初始化','start_level→开始层','visit→访问','end_level→完成层','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 = '[3,9,20,null,null,15,7]';
buildSteps(examples[0].tree);
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('请输入合法数组'); }
};
$('exampleSelect').onchange = () => {
const e = examples[parseInt($('exampleSelect').value)];
$('inputArea').value = JSON.stringify(e.tree);
buildSteps(e.tree); 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 levelOrder(root):
if not root: return []
res, queue = [], [root]
while queue:
level = []
for _ in range(len(queue)):
node = queue.pop(0)
level.append(node.val)
if node.left: queue.append(node.left)
if node.right: queue.append(node.right)
res.append(level)
return res`, {lang:'Python'});
'''
def js_convert_sorted_array_to_bst():
return _TREE_UTILS + r'''
const examples = [
{input: [-10,-3,0,5,9], label: '示例1: [-10,-3,0,5,9]'},
{input: [1,3], label: '示例2: [1,3]'},
{input: [1,2,3,4], label: '示例3: [1,2,3,4]'},
];
let nums, steps, stepCtrl, builtRoot;
let idCtr;
function buildSteps(arr) {
nums = [...arr]; steps = []; idCtr = 0;
steps.push({stage:'init', hl:{}, lo:0, hi:arr.length-1, mid:-1, msg:'二分递归构造平衡 BST:取中间元素为根'});
function build(lo, hi) {
if (lo > hi) return null;
const mid = Math.floor((lo + hi) / 2);
const node = {val: arr[mid], _id: 'n' + (idCtr++), left: null, right: null};
const hl = {};
for (let i = lo; i <= hi; i++) hl[i] = i === mid ? 'orange' : 'blue';
steps.push({stage:'select', hl, mid, lo, hi, msg:`范围 [${lo},${hi}],mid=${mid},nums[${mid}]=${arr[mid]} 作为根`});
node.left = build(lo, mid - 1);
node.right = build(mid + 1, hi);
return node;
}
builtRoot = build(0, arr.length - 1);
steps.push({stage:'done', hl:{}, lo:0, hi:arr.length-1, mid:-1, msg:'构造完成!'});
}
function render(step) {
const s = steps[step];
let viz = '有序数组:
';
viz += renderArray(nums, {highlights: s.hl || {}});
if (s.lo !== undefined) {
viz += `范围: [${s.lo}, ${s.hi}]${s.mid>=0 ? ' mid='+s.mid+' 值='+nums[s.mid] : ''}
`;
}
if (builtRoot) {
viz += '构造中的 BST:
';
viz += renderBinaryTree(builtRoot, {highlights: {}});
}
$('vizArea').innerHTML = viz;
$('detailContent').innerHTML = '' + s.msg + '
';
if (s.stage === 'done') {
$('resultContent').innerHTML = `BST 构建完成 = [${treeToArray(builtRoot)}]
`;
}
$('hintText').textContent = s.msg;
const stages = ['init→初始化','select→选中间','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,-3,0,5,9]';
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('请输入合法升序数组'); }
};
$('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 sortedArrayToBST(nums):
def build(lo, hi):
if lo > hi: return None
mid = (lo + hi) // 2
root = TreeNode(nums[mid])
root.left = build(lo, mid - 1)
root.right = build(mid + 1, hi)
return root
return build(0, len(nums) - 1)`, {lang:'Python'});
'''
def js_validate_binary_search_tree():
return _TREE_UTILS + r'''
const examples = [
{tree: [2,1,3], label: '示例1: [2,1,3] 有效 BST'},
{tree: [5,1,4,null,null,3,6], label: '示例2: [5,1,4,null,null,3,6] 无效'},
{tree: [5,4,6,null,null,3,7], label: '示例3: [5,4,6,null,null,3,7] 无效'},
];
let root, steps, stepCtrl, inorderArr;
function buildSteps(arr) {
root = arrayToTree(arr);
steps = []; inorderArr = [];
if (!root) { steps.push({stage:'done',hl:{},msg:'空树是有效 BST',valid:true}); return; }
const stack = []; let cur = root; let prev = null; let valid = true;
steps.push({stage:'init',hl:{},msg:'中序遍历 BST,检查结果是否严格递增',valid:true});
while (cur || stack.length) {
while (cur) {
stack.push(cur);
steps.push({stage:'push',hl:{[cur._id]:'active'},msg:`走到节点 ${cur.val},入栈`,valid});
cur = cur.left;
}
cur = stack.pop();
inorderArr.push(cur.val);
if (prev !== null && cur.val <= prev) {
valid = false;
steps.push({stage:'invalid',hl:{[cur._id]:'current'},msg:`${cur.val} ≤ 上一个值 ${prev},不是严格递增!无效 BST ✗`,valid:false,inorder:[...inorderArr]});
} else {
steps.push({stage:'visit',hl:{[cur._id]:'visited'},msg:`访问 ${cur.val},大于上一个值 ${prev !== null ? prev : '-∞'} ✓`,valid,inorder:[...inorderArr]});
}
prev = cur.val;
cur = cur.right;
}
steps.push({stage:'done',hl:{},msg:valid ? '中序遍历严格递增,有效 BST ✓' : '中序遍历不严格递增,无效 BST ✗',valid,inorder:[...inorderArr]});
}
function render(step) {
const s = steps[step];
let viz = renderBinaryTree(root, {highlights: s.hl});
if (s.inorder && s.inorder.length) {
viz += '中序序列:[' + s.inorder.join(', ') + ']
';
}
$('vizArea').innerHTML = viz;
$('detailContent').innerHTML = '' + s.msg + '
';
if (s.stage === 'done') {
$('resultContent').innerHTML = `是否有效 BST:${s.valid?'是 ✓':'否 ✗'}
`;
}
$('hintText').textContent = s.msg;
const stages = ['init→初始化','push→入栈','visit→访问','invalid→无效','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,1,3]';
buildSteps(examples[0].tree);
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('请输入合法数组'); }
};
$('exampleSelect').onchange = () => {
const e = examples[parseInt($('exampleSelect').value)];
$('inputArea').value = JSON.stringify(e.tree);
buildSteps(e.tree); 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 isValidBST(root):
stack, prev = [], None
cur = root
while cur or stack:
while cur:
stack.append(cur)
cur = cur.left
cur = stack.pop()
if prev is not None and cur.val <= prev:
return False
prev = cur.val
cur = cur.right
return True`, {lang:'Python'});
'''
def js_kth_smallest_element_in_a_bst():
return _TREE_UTILS + r'''
const examples = [
{tree: [3,1,4,null,2], k: 1, label: '示例1: [3,1,4,null,2], k=1'},
{tree: [5,3,6,2,4,null,null,1], k: 3, label: '示例2: [5,3,6,2,4,null,null,1], k=3'},
];
let root, steps, stepCtrl;
function buildSteps(arr, k) {
root = arrayToTree(arr);
steps = [];
const stack = []; let cur = root; let count = 0;
steps.push({stage:'init',hl:{},msg:`寻找第 ${k} 小的元素,中序遍历 BST`,count:0,k,result:null});
while (cur || stack.length) {
while (cur) {
stack.push(cur);
steps.push({stage:'push',hl:{[cur._id]:'active'},msg:`走到节点 ${cur.val},入栈`,count,k,result:null});
cur = cur.left;
}
cur = stack.pop();
count++;
const found = count === k;
steps.push({stage:'visit',hl:{[cur._id]:found?'current':'visited'},msg:`访问 ${cur.val},第 ${count} 小${found?' — 找到!':''}`,count,k,result:found?cur.val:null});
if (found) {
steps.push({stage:'done',hl:{[cur._id]:'current'},msg:`第 ${k} 小的元素是 ${cur.val}`,count,k,result:cur.val});
return;
}
cur = cur.right;
}
}
function render(step) {
const s = steps[step];
let viz = renderBinaryTree(root, {highlights: s.hl});
viz += `已访问 ${s.count} / ${s.k} 个节点
`;
$('vizArea').innerHTML = viz;
$('detailContent').innerHTML = '' + s.msg + '
';
if (s.stage === 'done') {
$('resultContent').innerHTML = `第 ${s.k} 小的元素 = ${s.result}
`;
}
$('hintText').textContent = s.msg;
const stages = ['init→初始化','push→入栈','visit→访问','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 = '[3,1,4,null,2], k=1';
buildSteps(examples[0].tree, examples[0].k);
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(/\[([^\]]+)\].*k\s*=\s*(\d+)/);
if (!m) { alert('格式: [3,1,4,null,2], k=1'); return; }
const arr = JSON.parse('['+m[1]+']'); const k = parseInt(m[2]);
buildSteps(arr, k); stepCtrl.setSteps(steps.map((_,i)=>i)); render(0); $('stepInfo').textContent = '步骤 1 / ' + steps.length;
};
$('exampleSelect').onchange = () => {
const e = examples[parseInt($('exampleSelect').value)];
$('inputArea').value = JSON.stringify(e.tree) + ', k=' + e.k;
buildSteps(e.tree, e.k); 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 kthSmallest(root, k):
stack, count = [], 0
cur = root
while cur or stack:
while cur:
stack.append(cur)
cur = cur.left
cur = stack.pop()
count += 1
if count == k:
return cur.val
cur = cur.right`, {lang:'Python'});
'''
def js_binary_tree_right_side_view():
return _TREE_UTILS + r'''
const examples = [
{tree: [1,2,3,null,5,null,4], label: '示例1: [1,2,3,null,5,null,4]'},
{tree: [1,null,3], label: '示例2: [1,null,3]'},
{tree: [1,2,3,4], label: '示例3: [1,2,3,4]'},
];
let root, steps, stepCtrl;
function buildSteps(arr) {
root = arrayToTree(arr);
steps = [];
if (!root) { steps.push({stage:'done',hl:{},result:[],msg:'空树,右视图为 []'}); return; }
const queue = [root]; const result = []; let level = 0;
steps.push({stage:'init',hl:{},queue:[root.val],result:[],msg:'BFS 逐层遍历,取每层最后一个节点'});
while (queue.length) {
const size = queue.length;
let rightNode = null;
const levelNodes = [];
for (let i = 0; i < size; i++) {
const node = queue.shift();
levelNodes.push(node);
if (node.left) queue.push(node.left);
if (node.right) queue.push(node.right);
if (i === size - 1) rightNode = node;
}
const hl = {};
levelNodes.forEach((n,i) => hl[n._id] = i === size-1 ? 'current' : 'visited');
result.push(rightNode.val);
steps.push({stage:'level',hl,queue:queue.map(n=>n.val),result:[...result],msg:`第 ${level} 层:[${levelNodes.map(n=>n.val)}],最右 = ${rightNode.val}`});
level++;
}
steps.push({stage:'done',hl:{},queue:[],result:[...result],msg:'右视图 = [' + result.join(', ') + ']'});
}
function render(step) {
const s = steps[step];
let viz = renderBinaryTree(root, {highlights: s.hl});
viz += '队列:
';
viz += renderQueue(s.queue);
viz += '右视图:[' + s.result.join(', ') + ']
';
$('vizArea').innerHTML = viz;
$('detailContent').innerHTML = '' + s.msg + '
';
if (s.stage === 'done') {
$('resultContent').innerHTML = `右视图 = [${s.result}]
`;
}
$('hintText').textContent = s.msg;
const stages = ['init→初始化','level→逐层','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,null,5,null,4]';
buildSteps(examples[0].tree);
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('请输入合法数组'); }
};
$('exampleSelect').onchange = () => {
const e = examples[parseInt($('exampleSelect').value)];
$('inputArea').value = JSON.stringify(e.tree);
buildSteps(e.tree); 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 rightSideView(root):
if not root: return []
res, queue = [], [root]
while queue:
size = len(queue)
for i in range(size):
node = queue.pop(0)
if i == size - 1:
res.append(node.val)
if node.left: queue.append(node.left)
if node.right: queue.append(node.right)
return res`, {lang:'Python'});
'''
def js_flatten_binary_tree_to_linked_list():
return _TREE_UTILS + r'''
const examples = [
{tree: [1,2,5,3,4,null,6], label: '示例1: [1,2,5,3,4,null,6]'},
{tree: [], label: '示例2: []'},
{tree: [1,2], label: '示例3: [1,2]'},
];
let root, steps, stepCtrl;
function buildSteps(arr) {
root = arrayToTree(arr);
steps = [];
if (!root) { steps.push({stage:'done',hl:{},snap:null,msg:'空树无需展开'}); return; }
function saveTree(node) {
if (!node) return null;
return {val:node.val, _id:node._id, left:saveTree(node.left), right:saveTree(node.right)};
}
steps.push({stage:'init',hl:{},snap:saveTree(root),msg:'前序遍历,原地修改为右偏链表'});
let cur = root;
while (cur) {
if (cur.left) {
let pred = cur.left;
const path = [pred.val];
while (pred.right) { pred = pred.right; path.push(pred.val); }
steps.push({stage:'find',hl:{[cur._id]:'current',[pred._id]:'active'},snap:saveTree(root),msg:`节点 ${cur.val} 有左子树,找前驱(左子树最右)= ${pred.val},路径:${path.join('→')}`});
pred.right = cur.right;
cur.right = cur.left;
cur.left = null;
steps.push({stage:'reconnect',hl:{[cur._id]:'current'},snap:saveTree(root),msg:`前驱右指针→${cur.val}原右子树,${cur.val}.right→左子树,left=null`});
} else {
steps.push({stage:'skip',hl:{[cur._id]:'visited'},snap:saveTree(root),msg:`节点 ${cur.val} 无左子树,向右移动`});
}
cur = cur.right;
}
steps.push({stage:'done',hl:{},snap:saveTree(root),msg:'展开完成!'});
}
function render(step) {
const s = steps[step];
let viz = '当前树结构:
';
viz += renderBinaryTree(s.snap, {highlights: s.hl});
$('vizArea').innerHTML = viz;
$('detailContent').innerHTML = '' + s.msg + '
';
if (s.stage === 'done') {
const arr = treeToArray(s.snap);
$('resultContent').innerHTML = `展开后 = [${arr}]
`;
}
$('hintText').textContent = s.msg;
const stages = ['init→初始化','find→找前驱','reconnect→重连','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,5,3,4,null,6]';
buildSteps(examples[0].tree);
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('请输入合法数组'); }
};
$('exampleSelect').onchange = () => {
const e = examples[parseInt($('exampleSelect').value)];
$('inputArea').value = JSON.stringify(e.tree);
buildSteps(e.tree); 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 flatten(root):
cur = root
while cur:
if cur.left:
pred = cur.left
while pred.right:
pred = pred.right
pred.right = cur.right
cur.right = cur.left
cur.left = None
cur = cur.right`, {lang:'Python'});
'''
def js_construct_binary_tree_from_preorder_and_inorder():
return _TREE_UTILS + r'''
const examples = [
{preorder: [3,9,20,15,7], inorder: [9,3,15,20,7], label: '示例1: pre=[3,9,20,15,7] in=[9,3,15,20,7]'},
{preorder: [-1], inorder: [-1], label: '示例2: 单节点'},
];
let preo, ino, steps, stepCtrl, builtRoot;
let idCtr;
function buildSteps(pre, ine) {
preo = [...pre]; ino = [...ine];
steps = []; idCtr = 0;
const inMap = {};
ine.forEach((v,i) => inMap[v] = i);
steps.push({stage:'init', hlPre:{}, hlIn:{}, msg:'前序找根,中序分左右,递归构造'});
function build(pl, pr, il, ir) {
if (pl > pr) return null;
const rootVal = pre[pl];
const node = {val: rootVal, _id: 'n' + (idCtr++), left: null, right: null};
const inIdx = inMap[rootVal];
const leftSize = inIdx - il;
const hlPre = {}, hlIn = {};
hlPre[pl] = 'orange';
for (let i = pl+1; i <= pr; i++) hlPre[i] = 'default';
for (let i = il; i <= ir; i++) hlIn[i] = i === inIdx ? 'orange' : (i < inIdx ? 'blue' : 'purple');
steps.push({stage:'select', hlPre, hlIn, msg:`根 = pre[${pl}]=${rootVal},中序位置=${inIdx},左子树 ${leftSize} 个,右子树 ${pr-pl-leftSize} 个`, rootVal, inIdx});
node.left = build(pl + 1, pl + leftSize, il, inIdx - 1);
node.right = build(pl + leftSize + 1, pr, inIdx + 1, ir);
return node;
}
builtRoot = build(0, pre.length - 1, 0, ine.length - 1);
steps.push({stage:'done', hlPre:{}, hlIn:{}, msg:'构造完成!'});
}
function render(step) {
const s = steps[step];
let viz = '前序:
';
viz += renderArray(preo, {highlights: s.hlPre || {}});
viz += '中序:
';
viz += renderArray(ino, {highlights: s.hlIn || {}});
if (s.inIdx !== undefined) {
viz += `
左子树
根
右子树
`;
}
if (builtRoot) {
viz += '构造中的树:
';
viz += renderBinaryTree(builtRoot, {highlights: {}});
}
$('vizArea').innerHTML = viz;
$('detailContent').innerHTML = '' + s.msg + '
';
if (s.stage === 'done') {
$('resultContent').innerHTML = `构造的树 = [${treeToArray(builtRoot)}]
`;
}
$('hintText').textContent = s.msg;
const stages = ['init→初始化','select→选根','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 = 'pre=[3,9,20,15,7] in=[9,3,15,20,7]';
buildSteps(examples[0].preorder, examples[0].inorder);
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(/pre=\[([^\]]+)\].*in=\[([^\]]+)\]/);
if (!m) { alert('格式: pre=[3,9,20,15,7] in=[9,3,15,20,7]'); return; }
const pre = JSON.parse('['+m[1]+']'), ine = JSON.parse('['+m[2]+']');
buildSteps(pre, ine); stepCtrl.setSteps(steps.map((_,i)=>i)); render(0); $('stepInfo').textContent = '步骤 1 / ' + steps.length;
};
$('exampleSelect').onchange = () => {
const e = examples[parseInt($('exampleSelect').value)];
$('inputArea').value = `pre=[${e.preorder}] in=[${e.inorder}]`;
buildSteps(e.preorder, e.inorder); 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 buildTree(preorder, inorder):
in_map = {v: i for i, v in enumerate(inorder)}
def build(pl, pr, il, ir):
if pl > pr: return None
root_val = preorder[pl]
idx = in_map[root_val]
left_size = idx - il
root = TreeNode(root_val)
root.left = build(pl+1, pl+left_size, il, idx-1)
root.right = build(pl+left_size+1, pr, idx+1, ir)
return root
return build(0, len(preorder)-1, 0, len(inorder)-1)`, {lang:'Python'});
'''
def js_path_sum_iii():
return _TREE_UTILS + r'''
const examples = [
{tree: [10,5,-3,3,2,null,11,3,-2,null,1], target: 8, label: '示例1: target=8 → 3'},
{tree: [5,4,8,11,null,13,4,7,2,null,null,5,1], target: 22, label: '示例2: target=22 → 3'},
];
let root, steps, stepCtrl;
function buildSteps(arr, target) {
root = arrayToTree(arr);
steps = [];
let count = 0;
const prefixSum = {0: 1};
steps.push({stage:'init', hl:{}, msg:`前缀和 DFS,targetSum=${target},初始 {0:1}`, prefixSum:{0:1}, currentSum:0, count:0, path:[]});
function dfs(node, currSum, path) {
if (!node) return;
currSum += node.val;
path.push(node.val);
const need = currSum - target;
const found = prefixSum[need] || 0;
if (found > 0) {
count += found;
steps.push({stage:'found', hl:{[node._id]:'current'}, msg:`节点 ${node.val}:前缀和=${currSum},需 ${currSum}-${target}=${need},出现 ${found} 次,+${found}=${count}`, prefixSum:JSON.parse(JSON.stringify(prefixSum)), currentSum:currSum, count, path:[...path]});
} else {
steps.push({stage:'visit', hl:{[node._id]:'active'}, msg:`节点 ${node.val}:前缀和=${currSum},需 ${need},未出现`, prefixSum:JSON.parse(JSON.stringify(prefixSum)), currentSum:currSum, count, path:[...path]});
}
prefixSum[currSum] = (prefixSum[currSum] || 0) + 1;
steps.push({stage:'add_prefix', hl:{[node._id]:'visited'}, msg:`前缀和 ${currSum} 计数+1`, prefixSum:JSON.parse(JSON.stringify(prefixSum)), currentSum:currSum, count, path:[...path]});
dfs(node.left, currSum, path);
dfs(node.right, currSum, path);
prefixSum[currSum]--;
if (prefixSum[currSum] === 0) delete prefixSum[currSum];
path.pop();
}
dfs(root, 0, []);
steps.push({stage:'done', hl:{}, msg:`满足条件的路径数 = ${count}`, prefixSum:{}, currentSum:0, count, path:[]});
}
function render(step) {
const s = steps[step];
let viz = renderBinaryTree(root, {highlights: s.hl});
if (s.path.length > 0) {
viz += '路径:' + s.path.join(' → ') + ` 前缀和=${s.currentSum}
`;
}
let psHtml = '前缀和:';
Object.entries(s.prefixSum).forEach(([k,v]) => { psHtml += `${k}:${v} `; });
psHtml += '
';
viz += psHtml;
$('vizArea').innerHTML = viz;
$('detailContent').innerHTML = '' + s.msg + '
';
if (s.count > 0) {
$('detailContent').innerHTML += `路径总数:${s.count}
`;
}
if (s.stage === 'done') {
$('resultContent').innerHTML = `路径总和 III = ${s.count} 条
`;
}
$('hintText').textContent = s.msg;
const stages = ['init→初始化','visit→访问','found→找到','add_prefix→加前缀和','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,5,-3,3,2,null,11,3,-2,null,1], target=8';
buildSteps(examples[0].tree, examples[0].target);
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(/\[([^\]]+)\].*target\s*=\s*(-?\d+)/);
if (!m) { alert('格式: [10,5,-3,...], target=8'); return; }
const arr = JSON.parse('['+m[1]+']'); const t = parseInt(m[2]);
buildSteps(arr, t); stepCtrl.setSteps(steps.map((_,i)=>i)); render(0); $('stepInfo').textContent = '步骤 1 / ' + steps.length;
};
$('exampleSelect').onchange = () => {
const e = examples[parseInt($('exampleSelect').value)];
$('inputArea').value = JSON.stringify(e.tree) + ', target=' + e.target;
buildSteps(e.tree, e.target); 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 pathSum(root, targetSum):
from collections import defaultdict
prefix = defaultdict(int)
prefix[0] = 1
count = 0
def dfs(node, curr):
nonlocal count
if not node: return
curr += node.val
count += prefix[curr - targetSum]
prefix[curr] += 1
dfs(node.left, curr)
dfs(node.right, curr)
prefix[curr] -= 1
dfs(root, 0)
return count`, {lang:'Python'});
'''
def js_lowest_common_ancestor_of_a_binary_tree():
return _TREE_UTILS + r'''
const examples = [
{tree: [3,5,1,6,2,0,8,null,null,7,4], p: 5, q: 1, label: '示例1: p=5, q=1 → LCA=3'},
{tree: [3,5,1,6,2,0,8,null,null,7,4], p: 5, q: 4, label: '示例2: p=5, q=4 → LCA=5'},
];
let root, steps, stepCtrl, pVal, qVal, lcaVal;
function findNodeByVal(node, val) {
if (!node) return null;
if (node.val === val) return node;
return findNodeByVal(node.left, val) || findNodeByVal(node.right, val);
}
function buildSteps(arr, p, q) {
root = arrayToTree(arr); pVal = p; qVal = q;
steps = []; lcaVal = null;
const pNode = findNodeByVal(root, p);
const qNode = findNodeByVal(root, q);
const targetHl = {};
if (pNode) targetHl[pNode._id] = 'active';
if (qNode) targetHl[qNode._id] = 'active';
steps.push({stage:'init', hl:Object.assign({}, targetHl), msg:`寻找 ${p} 和 ${q} 的最近公共祖先`});
function dfs(node) {
if (!node) return null;
steps.push({stage:'enter', hl:Object.assign({[node._id]:'current'}, targetHl), msg:`进入节点 ${node.val}`});
if (node.val === p || node.val === q) {
steps.push({stage:'found', hl:{[node._id]:'current'}, msg:`节点 ${node.val} 是目标节点 ${node.val===pVal?'p':'q'}!`});
return node;
}
const left = dfs(node.left);
const right = dfs(node.right);
if (left && right) {
lcaVal = node.val;
steps.push({stage:'lca', hl:{[node._id]:'current'}, msg:`左右子树各找到一个目标,LCA = ${node.val}`, lca:node.val});
return node;
}
const ret = left || right;
if (ret) {
steps.push({stage:'return_one', hl:{[node._id]:'visited'}, msg:`节点 ${node.val}:一侧找到目标,向上返回`});
} else {
steps.push({stage:'return_none', hl:{[node._id]:'visited'}, msg:`节点 ${node.val}:两侧都没找到,返回 null`});
}
return ret;
}
dfs(root);
steps.push({stage:'done', hl:{}, msg:`最近公共祖先 = ${lcaVal !== null ? lcaVal : 'null'}`, lca:lcaVal});
}
function render(step) {
const s = steps[step];
let viz = renderBinaryTree(root, {highlights: s.hl});
viz += `p = ${pVal} q = ${qVal}
`;
$('vizArea').innerHTML = viz;
$('detailContent').innerHTML = '' + s.msg + '
';
if (s.stage === 'done') {
$('resultContent').innerHTML = `LCA(${pVal}, ${qVal}) = ${s.lca}
`;
}
$('hintText').textContent = s.msg;
const stages = ['init→初始化','enter→进入','found→找到目标','lca→确定LCA','return_one→返回一侧','return_none→返回空','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 = '[3,5,1,6,2,0,8,null,null,7,4], p=5, q=1';
buildSteps(examples[0].tree, examples[0].p, examples[0].q);
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(/\[([^\]]+)\].*p\s*=\s*(-?\d+).*q\s*=\s*(-?\d+)/);
if (!m) { alert('格式: [3,5,1,...], p=5, q=1'); return; }
const arr = JSON.parse('['+m[1]+']');
buildSteps(arr, parseInt(m[2]), parseInt(m[3]));
stepCtrl.setSteps(steps.map((_,i)=>i)); render(0); $('stepInfo').textContent = '步骤 1 / ' + steps.length;
};
$('exampleSelect').onchange = () => {
const e = examples[parseInt($('exampleSelect').value)];
$('inputArea').value = JSON.stringify(e.tree) + `, p=${e.p}, q=${e.q}`;
buildSteps(e.tree, e.p, e.q); 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 lowestCommonAncestor(root, p, q):
if not root or root == p or root == q:
return root
left = lowestCommonAncestor(root.left, p, q)
right = lowestCommonAncestor(root.right, p, q)
if left and right:
return root
return left if left else right`, {lang:'Python'});
'''
def js_binary_tree_maximum_path_sum():
return _TREE_UTILS + r'''
const examples = [
{tree: [-10,9,20,null,null,15,7], label: '示例1: [-10,9,20,null,null,15,7] → 42'},
{tree: [2,-1], label: '示例2: [2,-1] → 2'},
{tree: [-3], label: '示例3: [-3] → -3'},
];
let root, steps, stepCtrl;
function buildSteps(arr) {
root = arrayToTree(arr);
steps = [];
let maxSum = -Infinity;
steps.push({stage:'init', hl:{}, msg:'DFS 返回节点最大贡献,维护全局最大路径和', maxSum: null});
function dfs(node) {
if (!node) return 0;
steps.push({stage:'enter', hl:{[node._id]:'current'}, msg:`进入节点 ${node.val}`, maxSum});
const leftGain = Math.max(0, dfs(node.left));
const rightGain = Math.max(0, dfs(node.right));
const pathSum = node.val + leftGain + rightGain;
const oldMax = maxSum;
if (pathSum > maxSum) maxSum = pathSum;
const contribution = node.val + Math.max(leftGain, rightGain);
steps.push({stage:'calc', hl:{[node._id]:'visited'}, msg:`节点 ${node.val}:左贡献=${leftGain},右贡献=${rightGain},路径和=${node.val}+${leftGain}+${rightGain}=${pathSum},最大${oldMax}→${maxSum},向上贡献=${contribution}`, maxSum, contribution, leftGain, rightGain, pathSum});
return contribution;
}
if (root) dfs(root);
else maxSum = 0;
steps.push({stage:'done', hl:{}, msg:`最大路径和 = ${maxSum}`, maxSum});
}
function render(step) {
const s = steps[step];
let viz = renderBinaryTree(root, {highlights: s.hl});
$('vizArea').innerHTML = viz;
let detail = '' + s.msg + '
';
if (s.contribution !== undefined) {
detail += `最大路径和:${s.maxSum}
向上贡献=${s.contribution} 左=${s.leftGain} 右=${s.rightGain} 路径和=${s.pathSum}
`;
}
$('detailContent').innerHTML = detail;
if (s.stage === 'done') {
$('resultContent').innerHTML = `最大路径和 = ${s.maxSum}
`;
}
$('hintText').textContent = s.msg;
const stages = ['init→初始化','enter→进入','calc→计算','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,20,null,null,15,7]';
buildSteps(examples[0].tree);
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('请输入合法数组'); }
};
$('exampleSelect').onchange = () => {
const e = examples[parseInt($('exampleSelect').value)];
$('inputArea').value = JSON.stringify(e.tree);
buildSteps(e.tree); 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 maxPathSum(root):
max_sum = float('-inf')
def dfs(node):
nonlocal max_sum
if not node: return 0
left = max(0, dfs(node.left))
right = max(0, dfs(node.right))
max_sum = max(max_sum, node.val + left + right)
return node.val + max(left, right)
dfs(root)
return max_sum`, {lang:'Python'});
'''