"""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'}); '''