"""Linked-list algorithm JS generators for LeetCode Hot 100 图解.""" # Common JS helper – inline in each function so pages are self-contained _LLVIZ = r''' function llViz(nodes, opts) { opts = opts || {}; var highs = opts.highs || {}; var ptrs = opts.ptrs || {}; var cycle = opts.cycle != null ? opts.cycle : -1; var label = opts.label || ''; var showNull = opts.showNull !== false; var html = ''; if (label) html += '
' + label + '
'; html += '
'; for (var i = 0; i < nodes.length; i++) { var cls = highs[i] || ''; html += '
'; html += '
'; html += '' + nodes[i] + ''; if (i === cycle) { html += '↩'; } else if (i < nodes.length - 1) { html += '→'; } else if (showNull && i === nodes.length - 1) { html += '∅'; } html += '
'; var plabels = []; var pkeys = Object.keys(ptrs); for (var pi = 0; pi < pkeys.length; pi++) { if (ptrs[pkeys[pi]] === i) plabels.push(pkeys[pi]); } if (plabels.length) { html += '
' + plabels.join(',') + '
'; } html += '
'; } html += '
'; return html; } ''' _PIPELINE_HELPER = r''' function setPipeline(stages, current) { $('pipeline').innerHTML = stages.map(function(st) { var parts = st.split('\u2192'); return '' + parts[1] + ''; }).join('\u2192'); } ''' def js_intersection_of_two_linked_lists(): return _LLVIZ + _PIPELINE_HELPER + r''' var examples = [ {listA:[4,1,8,4,5], listB:[5,6,1,8,4,5], skipA:2, skipB:3, label:'示例1: 交点在8'}, {listA:[1,9,1,2,4], listB:[3,2,4], skipA:3, skipB:1, label:'示例2: 交点在2'}, {listA:[2,6,4], listB:[1,5], skipA:-1, skipB:-1, label:'示例3: 不相交'}, ]; var steps, stepCtrl; function buildSteps(ex) { steps = []; var a = ex.listA, b = ex.listB; var skipA = ex.skipA, skipB = ex.skipB; var intersect = skipA >= 0; steps.push({stage:'init', msg:'双指针拼接法:pA走完A走B,pB走完B走A,若相交必在交点相遇', pA:0, pB:0, onA:true, pBonA:true}); var iA = 0, iB = 0, onA = true, pBonA = true; var maxIter = a.length + b.length + 2; for (var iter = 0; iter < maxIter; iter++) { if (intersect && iA === skipA && iB === skipB) { steps.push({stage:'found', msg:'pA 和 pB 在交点 ' + a[skipA] + ' 相遇!', pA:skipA, pB:skipB, onA:true, pBonA:true, found:true}); return; } if (iA >= a.length && iB >= b.length) { steps.push({stage:'done', msg:'pA 和 pB 同时到达末尾,链表不相交', pA:a.length, pB:b.length, onA:false, pBonA:false, noIntersect:true}); return; } steps.push({stage:'step', msg:'pA 在 ' + (onA?'A':'B') + '[' + iA + ']=' + (onA?(iA= (onA ? a.length : b.length)) { onA = !onA; iA = 0; } iB++; if (iB >= (pBonA ? a.length : b.length)) { pBonA = !pBonA; iB = 0; } } steps.push({stage:'done', msg:'遍历完毕,链表不相交', pA:a.length, pB:b.length, onA:false, pBonA:false, noIntersect:true}); } function render(step) { var s = steps[step]; var ex = examples[0]; var exIdx = parseInt($('exampleSelect').value) || 0; ex = examples[exIdx]; var hlA = {}, hlB = {}; if (s.pA < ex.listA.length && s.onA) hlA[s.pA] = 'current'; if (s.pB < ex.listA.length && s.pBonA) hlB[s.pB] = 'active'; if (!s.pBonA && s.pB < ex.listB.length) hlB[s.pB] = 'active'; if (!s.onA && s.pA < ex.listB.length) hlA[s.pA] = 'current'; if (s.found) { hlA[ex.skipA] = 'active'; hlB[ex.skipB] = 'active'; } var viz = llViz(ex.listA, {highs:hlA, ptrs:{pA: s.onA ? s.pA : -1}, label:'链表 A'}); viz += llViz(ex.listB, {highs:hlB, ptrs:{pB: s.pBonA ? s.pB : -1}, label:'链表 B'}); if (s.found) viz += '
交点值为 ' + ex.listA[ex.skipA] + '
'; $('vizArea').innerHTML = viz; $('detailContent').innerHTML = '
' + s.msg + '
'; if (s.found) $('resultContent').innerHTML = '
交点引用 = 节点 ' + ex.listA[ex.skipA] + '
'; if (s.noIntersect) $('resultContent').innerHTML = '
null — 链表不相交
'; $('hintText').textContent = s.msg; setPipeline(['init\u2192初始化','step\u2192移动','found\u2192找到','done\u2192完成'], s.stage); } function init() { var sel = $('exampleSelect'); examples.forEach(function(e,i){ sel.innerHTML += ''; }); $('inputArea').value = 'A=[4,1,8,4,5] B=[5,6,1,8,4,5] skipA=2 skipB=3'; buildSteps(examples[0]); stepCtrl = new StepController({onStep:render}); stepCtrl.setSteps(steps.map(function(_,i){return i;})); $('stepInfo').textContent = '步骤 1 / ' + steps.length; stepCtrl.onStep = function(idx){ render(idx); $('stepInfo').textContent = '步骤 '+(idx+1)+' / '+steps.length; }; $('applyBtn').onclick = function(){ buildSteps(examples[parseInt($('exampleSelect').value)]); stepCtrl.setSteps(steps.map(function(_,i){return i;})); render(0); }; $('exampleSelect').onchange = function(){ var e=examples[parseInt($('exampleSelect').value)]; buildSteps(e); stepCtrl.setSteps(steps.map(function(_,i){return i;})); render(0); }; $('prevBtn').onclick = function(){ stepCtrl.prev(); }; $('nextBtn').onclick = function(){ stepCtrl.next(); }; $('jumpBtn').onclick = function(){ stepCtrl.jumpToEnd(); }; $('autoBtn').onclick = function(){ var on=stepCtrl.toggleAuto(); $('autoBtn').textContent=on?'暂停':'自动播放'; }; $('resetBtn').onclick = function(){ stepCtrl.reset(); $('autoBtn').textContent='自动播放'; }; } init(); $('codeArea').innerHTML = renderCode('def getIntersectionNode(headA, headB):\n pA, pB = headA, headB\n while pA != pB:\n pA = pA.next if pA else headB\n pB = pB.next if pB else headA\n return pA', {lang:'Python'}); ''' def js_reverse_linked_list(): return _LLVIZ + _PIPELINE_HELPER + r''' var examples = [ {input:[1,2,3,4,5], label:'示例1: [1,2,3,4,5]'}, {input:[1,2], label:'示例2: [1,2]'}, {input:[], label:'示例3: []'}, ]; var nodes, steps, stepCtrl; function buildSteps(arr) { nodes = arr.slice(); steps = []; steps.push({stage:'init', msg:'迭代法:prev=null, curr=头节点, 每步将 curr.next 指向 prev', prev:-1, curr:0, next:1}); var prev = -1, curr = 0; while (curr < arr.length) { var nxt = curr + 1; steps.push({stage:'visit', msg:'访问 curr=' + arr[curr] + ':保存 next=' + (nxt=0?arr[prev]:'null'), prev:prev, curr:curr, next:nxt}); prev = curr; curr = nxt; steps.push({stage:'shift', msg:'prev 移到 ' + arr[prev] + ',curr 移到 ' + (curr=0?arr[prev]:'null'), prev:prev, curr:-1, next:-1}); } function render(step) { var s = steps[step]; var hl = {}; if (s.curr >= 0 && s.curr < nodes.length) hl[s.curr] = 'current'; if (s.prev >= 0 && s.prev < nodes.length) hl[s.prev] = 'active'; if (s.next < nodes.length && s.next >= 0) hl[s.next] = 'null-node'; // Show reversed portion in green, unreversed in blue for (var i = 0; i < nodes.length; i++) { if (s.prev >= 0 && i <= s.prev && i > s.curr) { hl[i] = 'active'; } } var viz = llViz(nodes, {highs:hl, ptrs:{prev:s.prev>=0&&s.prev=0&&s.curr=0?s.next:-1}, label:'链表状态'}); if (s.prev >= 0) { var reversed = []; for (var i = s.prev; i >= 0; i--) reversed.push(nodes[i]); viz += llViz(reversed, {highs:{}, label:'已反转部分', showNull:false}); } $('vizArea').innerHTML = viz; $('detailContent').innerHTML = '
' + s.msg + '
'; if (s.stage === 'done') { var result = []; for (var i = nodes.length-1; i >= 0; i--) result.push(nodes[i]); $('resultContent').innerHTML = '
反转结果:[' + result.join(' → ') + ']
'; } $('hintText').textContent = s.msg; setPipeline(['init\u2192初始化','visit\u2192访问','shift\u2192移动','done\u2192完成'], s.stage); } function init() { var sel = $('exampleSelect'); examples.forEach(function(e,i){ sel.innerHTML += ''; }); $('inputArea').value = '[1,2,3,4,5]'; buildSteps(examples[0].input); stepCtrl = new StepController({onStep:render}); stepCtrl.setSteps(steps.map(function(_,i){return i;})); $('stepInfo').textContent = '步骤 1 / ' + steps.length; stepCtrl.onStep = function(idx){ render(idx); $('stepInfo').textContent = '步骤 '+(idx+1)+' / '+steps.length; }; $('applyBtn').onclick = function(){ try { var arr=JSON.parse($('inputArea').value); buildSteps(arr); stepCtrl.setSteps(steps.map(function(_,i){return i;})); render(0); } catch(e){ alert('请输入合法 JSON 数组'); } }; $('exampleSelect').onchange = function(){ buildSteps(examples[parseInt($('exampleSelect').value)].input); stepCtrl.setSteps(steps.map(function(_,i){return i;})); render(0); }; $('prevBtn').onclick = function(){ stepCtrl.prev(); }; $('nextBtn').onclick = function(){ stepCtrl.next(); }; $('jumpBtn').onclick = function(){ stepCtrl.jumpToEnd(); }; $('autoBtn').onclick = function(){ var on=stepCtrl.toggleAuto(); $('autoBtn').textContent=on?'暂停':'自动播放'; }; $('resetBtn').onclick = function(){ stepCtrl.reset(); $('autoBtn').textContent='自动播放'; }; } init(); $('codeArea').innerHTML = renderCode('def reverseList(head):\n prev, curr = None, head\n while curr:\n nxt = curr.next\n curr.next = prev\n prev = curr\n curr = nxt\n return prev', {lang:'Python'}); ''' def js_palindrome_linked_list(): return _LLVIZ + _PIPELINE_HELPER + r''' var examples = [ {input:[1,2,2,1], label:'示例1: [1,2,2,1] 回文'}, {input:[1,2], label:'示例2: [1,2] 非回文'}, {input:[1,2,3,2,1], label:'示例3: [1,2,3,2,1] 回文'}, ]; var nodes, steps, stepCtrl; function buildSteps(arr) { nodes = arr.slice(); steps = []; if (arr.length === 0) { steps.push({stage:'done',msg:'空链表是回文',phase:'done'}); return; } steps.push({stage:'init', msg:'快慢指针找中点 + 反转后半部分 + 比较', phase:'find_mid', slow:0, fast:0}); // Phase 1: find middle var slow = 0, fast = 0; while (fast < arr.length - 1 && fast + 1 < arr.length - 1) { slow++; fast += 2; steps.push({stage:'move', msg:'slow→'+arr[slow]+', fast→'+arr[Math.min(fast,arr.length-1)], phase:'find_mid', slow:slow, fast:Math.min(fast,arr.length-1)}); } var mid = slow + 1; steps.push({stage:'mid', msg:'中点后半起始位置:index ' + mid + ' (值=' + arr[mid] + ')', phase:'find_mid', slow:slow, fast:Math.min(fast,arr.length-1), mid:mid}); // Phase 2: reverse second half var second = arr.slice(mid); var reversed = second.slice().reverse(); steps.push({stage:'reverse', msg:'反转后半 [' + second.join(',') + '] → [' + reversed.join(',') + ']', phase:'reverse', second:second.slice(), reversed:reversed.slice()}); // Phase 3: compare var first = arr.slice(0, arr.length - second.length); var isPalin = true; for (var i = 0; i < reversed.length; i++) { if (first[i] !== reversed[i]) { isPalin = false; break; } } steps.push({stage:'compare', msg:'前半 ['+first.join(',')+'] vs 反转后半 ['+reversed.join(',')+'] → ' + (isPalin?'匹配 ✓':'不匹配 ✗'), phase:'compare', first:first, reversed:reversed, isPalin:isPalin}); steps.push({stage:'done', msg:'结果:' + (isPalin?'是回文链表':'不是回文链表'), phase:'done', isPalin:isPalin}); } function render(step) { var s = steps[step]; var viz = ''; if (s.phase === 'find_mid' || s.stage === 'init') { var hl = {}; if (s.slow >= 0 && s.slow < nodes.length) hl[s.slow] = 'active'; if (s.fast >= 0 && s.fast < nodes.length) hl[s.fast] = 'current'; viz = llViz(nodes, {highs:hl, ptrs:{slow:s.slow, fast:s.fast}, label:'寻找中点'}); } else if (s.phase === 'reverse') { viz = llViz(nodes, {highs:{}, label:'原始链表'}); viz += llViz(s.reversed, {highs:{}, label:'反转后半', showNull:false}); } else if (s.phase === 'compare' || s.phase === 'done') { viz = llViz(s.first, {label:'前半部分', showNull:false}); viz += llViz(s.reversed, {label:'反转后半', showNull:false}); viz += '
' + (s.isPalin ? '✓ 匹配' : '✗ 不匹配') + '
'; } $('vizArea').innerHTML = viz; $('detailContent').innerHTML = '
' + s.msg + '
'; if (s.stage === 'done') { $('resultContent').innerHTML = '
' + (s.isPalin?'是回文链表 ✓':'不是回文链表 ✗') + '
'; } $('hintText').textContent = s.msg; setPipeline(['init\u2192开始','move\u2192快慢','mid\u2192中点','reverse\u2192反转','compare\u2192比较','done\u2192结果'], s.stage); } function init() { var sel = $('exampleSelect'); examples.forEach(function(e,i){ sel.innerHTML += ''; }); $('inputArea').value = '[1,2,2,1]'; buildSteps(examples[0].input); stepCtrl = new StepController({onStep:render}); stepCtrl.setSteps(steps.map(function(_,i){return i;})); $('stepInfo').textContent = '步骤 1 / ' + steps.length; stepCtrl.onStep = function(idx){ render(idx); $('stepInfo').textContent = '步骤 '+(idx+1)+' / '+steps.length; }; $('applyBtn').onclick = function(){ try { var arr=JSON.parse($('inputArea').value); buildSteps(arr); stepCtrl.setSteps(steps.map(function(_,i){return i;})); render(0); } catch(e){ alert('请输入合法 JSON 数组'); } }; $('exampleSelect').onchange = function(){ buildSteps(examples[parseInt($('exampleSelect').value)].input); stepCtrl.setSteps(steps.map(function(_,i){return i;})); render(0); }; $('prevBtn').onclick = function(){ stepCtrl.prev(); }; $('nextBtn').onclick = function(){ stepCtrl.next(); }; $('jumpBtn').onclick = function(){ stepCtrl.jumpToEnd(); }; $('autoBtn').onclick = function(){ var on=stepCtrl.toggleAuto(); $('autoBtn').textContent=on?'暂停':'自动播放'; }; $('resetBtn').onclick = function(){ stepCtrl.reset(); $('autoBtn').textContent='自动播放'; }; } init(); $('codeArea').innerHTML = renderCode('def isPalindrome(head):\n slow = fast = head\n while fast and fast.next:\n slow = slow.next\n fast = fast.next.next\n prev = None\n while slow:\n nxt = slow.next\n slow.next = prev\n prev = slow\n slow = nxt\n left, right = head, prev\n while right:\n if left.val != right.val:\n return False\n left = left.next\n right = right.next\n return True', {lang:'Python'}); ''' def js_linked_list_cycle(): return _LLVIZ + _PIPELINE_HELPER + r''' var examples = [ {input:[3,2,0,-4], pos:1, label:'示例1: [3,2,0,-4] pos=1 有环'}, {input:[1,2], pos:0, label:'示例2: [1,2] pos=0 有环'}, {input:[1], pos:-1, label:'示例3: [1] 无环'}, ]; var nodes, steps, stepCtrl; function buildSteps(ex) { nodes = ex.input.slice(); steps = []; var hasCycle = ex.pos >= 0; steps.push({stage:'init', msg:'快慢指针:slow每次走1步,fast每次走2步,若相遇则有环', slow:0, fast:0, cyclePos:ex.pos}); var slow = 0, fast = 0, n = nodes.length; var maxIter = n + 2; for (var iter = 0; iter < maxIter; iter++) { slow = (slow + 1) % n; fast = (fast + 2) % n; if (!hasCycle) { if (fast >= n) { steps.push({stage:'noreturn', msg:'fast 到达链表末尾,无环', slow:slow, fast:n, cyclePos:-1}); steps.push({stage:'done', msg:'链表无环', slow:slow, fast:n, cyclePos:-1, result:false}); return; } } steps.push({stage:'step', msg:'slow→'+nodes[slow]+', fast→'+nodes[fast] + (slow===fast&&hasCycle?' 相遇!有环!':''), slow:slow, fast:fast, cyclePos:ex.pos, met:slow===fast&&hasCycle}); if (slow === fast && hasCycle) { steps.push({stage:'done', msg:'快慢指针在节点 '+nodes[slow]+' 相遇,链表有环', slow:slow, fast:fast, cyclePos:ex.pos, result:true}); return; } } steps.push({stage:'done', msg:'遍历完毕,链表无环', slow:0, fast:0, cyclePos:-1, result:false}); } function render(step) { var s = steps[step]; var hl = {}; if (s.slow >= 0 && s.slow < nodes.length) hl[s.slow] = 'active'; if (s.fast >= 0 && s.fast < nodes.length) { hl[s.fast] = s.slow === s.fast ? 'active' : 'current'; } var viz = llViz(nodes, {highs:hl, ptrs:{slow:s.slow>=0&&s.slow=0&&s.fast'; if (s.stage === 'done') { $('resultContent').innerHTML = s.result ? '
链表有环 ✓ (环入口在 index ' + s.cyclePos + ')
' : '
链表无环 ✗
'; } $('hintText').textContent = s.msg; setPipeline(['init\u2192初始化','step\u2192移动','noreturn\u2192无返回','done\u2192结果'], s.stage); } function init() { var sel = $('exampleSelect'); examples.forEach(function(e,i){ sel.innerHTML += ''; }); $('inputArea').value = '[3,2,0,-4] pos=1'; buildSteps(examples[0]); stepCtrl = new StepController({onStep:render}); stepCtrl.setSteps(steps.map(function(_,i){return i;})); $('stepInfo').textContent = '步骤 1 / ' + steps.length; stepCtrl.onStep = function(idx){ render(idx); $('stepInfo').textContent = '步骤 '+(idx+1)+' / '+steps.length; }; $('applyBtn').onclick = function(){ buildSteps(examples[parseInt($('exampleSelect').value)]); stepCtrl.setSteps(steps.map(function(_,i){return i;})); render(0); }; $('exampleSelect').onchange = function(){ buildSteps(examples[parseInt($('exampleSelect').value)]); stepCtrl.setSteps(steps.map(function(_,i){return i;})); render(0); }; $('prevBtn').onclick = function(){ stepCtrl.prev(); }; $('nextBtn').onclick = function(){ stepCtrl.next(); }; $('jumpBtn').onclick = function(){ stepCtrl.jumpToEnd(); }; $('autoBtn').onclick = function(){ var on=stepCtrl.toggleAuto(); $('autoBtn').textContent=on?'暂停':'自动播放'; }; $('resetBtn').onclick = function(){ stepCtrl.reset(); $('autoBtn').textContent='自动播放'; }; } init(); $('codeArea').innerHTML = renderCode('def hasCycle(head):\n slow = fast = head\n while fast and fast.next:\n slow = slow.next\n fast = fast.next.next\n if slow is fast:\n return True\n return False', {lang:'Python'}); ''' def js_linked_list_cycle_ii(): return _LLVIZ + _PIPELINE_HELPER + r''' var examples = [ {input:[3,2,0,-4], pos:1, label:'示例1: [3,2,0,-4] pos=1'}, {input:[1,2], pos:0, label:'示例2: [1,2] pos=0'}, {input:[1], pos:-1, label:'示例3: 无环'}, ]; var nodes, steps, stepCtrl; function buildSteps(ex) { nodes = ex.input.slice(); steps = []; var hasCycle = ex.pos >= 0; var n = nodes.length; steps.push({stage:'init', msg:'Floyd 算法:Phase1 快慢指针检测环,Phase2 找环入口', phase:'detect', slow:0, fast:0, cyclePos:ex.pos}); if (!hasCycle) { steps.push({stage:'done', msg:'链表无环,返回 null', phase:'done', entry:-1, result:null}); return; } // Phase 1: detect var slow = 0, fast = 0; var maxD = n + 2; for (var d = 0; d < maxD; d++) { slow = (slow + 1) % n; fast = (fast + 2) % n; steps.push({stage:'detect', msg:'Phase1: slow→'+nodes[slow]+', fast→'+nodes[fast], phase:'detect', slow:slow, fast:fast, cyclePos:ex.pos}); if (slow === fast) { steps.push({stage:'met', msg:'Phase1: 快慢在 '+nodes[slow]+' 相遇,确认有环', phase:'detect', slow:slow, fast:fast, cyclePos:ex.pos}); break; } } // Phase 2: find entry steps.push({stage:'phase2', msg:'Phase2: ptr1 从头出发,ptr2 从相遇点出发,各走1步', phase:'entry', ptr1:0, ptr2:slow, cyclePos:ex.pos}); var ptr1 = 0, ptr2 = slow; for (var e = 0; e < n + 2; e++) { if (ptr1 === ptr2) { steps.push({stage:'found', msg:'Phase2: ptr1 和 ptr2 在 '+nodes[ptr1]+' 相遇,这就是环入口!', phase:'entry', ptr1:ptr1, ptr2:ptr2, cyclePos:ex.pos, entry:ptr1}); break; } ptr1 = (ptr1 + 1) % n; ptr2 = (ptr2 + 1) % n; steps.push({stage:'seek', msg:'Phase2: ptr1→'+nodes[ptr1]+', ptr2→'+nodes[ptr2], phase:'entry', ptr1:ptr1, ptr2:ptr2, cyclePos:ex.pos}); } steps.push({stage:'done', msg:'环入口为节点 '+nodes[ex.pos]+' (index '+ex.pos+')', phase:'done', entry:ex.pos, cyclePos:ex.pos}); } function render(step) { var s = steps[step]; var hl = {}; var ptrs = {}; if (s.phase === 'detect') { if (s.slow >= 0 && s.slow < nodes.length) hl[s.slow] = 'active'; if (s.fast >= 0 && s.fast < nodes.length) hl[s.fast] = s.slow===s.fast ? 'active' : 'current'; ptrs = {slow:s.slow, fast:s.fast}; } else if (s.phase === 'entry') { if (s.ptr1 >= 0 && s.ptr1 < nodes.length) hl[s.ptr1] = 'active'; if (s.ptr2 >= 0 && s.ptr2 < nodes.length) hl[s.ptr2] = s.ptr1===s.ptr2 ? 'active' : 'current'; ptrs = {ptr1:s.ptr1, ptr2:s.ptr2}; } var viz = llViz(nodes, {highs:hl, ptrs:ptrs, cycle:s.cyclePos||-1, label:'链表'}); if (s.stage === 'found' || s.stage === 'met') { viz += '
🎯 ' + s.msg + '
'; } $('vizArea').innerHTML = viz; $('detailContent').innerHTML = '
' + s.msg + '
'; if (s.stage === 'done') { if (s.entry >= 0) { $('resultContent').innerHTML = '
环入口 = 节点 '+nodes[s.entry]+' (index '+s.entry+')
'; } else { $('resultContent').innerHTML = '
无环,返回 null
'; } } $('hintText').textContent = s.msg; setPipeline(['init\u2192初始化','detect\u2192检测','met\u2192相遇','phase2\u2192找入口','seek\u2192搜索','found\u2192找到','done\u2192完成'], s.stage); } function init() { var sel = $('exampleSelect'); examples.forEach(function(e,i){ sel.innerHTML += ''; }); $('inputArea').value = '[3,2,0,-4] pos=1'; buildSteps(examples[0]); stepCtrl = new StepController({onStep:render}); stepCtrl.setSteps(steps.map(function(_,i){return i;})); $('stepInfo').textContent = '步骤 1 / ' + steps.length; stepCtrl.onStep = function(idx){ render(idx); $('stepInfo').textContent = '步骤 '+(idx+1)+' / '+steps.length; }; $('applyBtn').onclick = function(){ buildSteps(examples[parseInt($('exampleSelect').value)]); stepCtrl.setSteps(steps.map(function(_,i){return i;})); render(0); }; $('exampleSelect').onchange = function(){ buildSteps(examples[parseInt($('exampleSelect').value)]); stepCtrl.setSteps(steps.map(function(_,i){return i;})); render(0); }; $('prevBtn').onclick = function(){ stepCtrl.prev(); }; $('nextBtn').onclick = function(){ stepCtrl.next(); }; $('jumpBtn').onclick = function(){ stepCtrl.jumpToEnd(); }; $('autoBtn').onclick = function(){ var on=stepCtrl.toggleAuto(); $('autoBtn').textContent=on?'暂停':'自动播放'; }; $('resetBtn').onclick = function(){ stepCtrl.reset(); $('autoBtn').textContent='自动播放'; }; } init(); $('codeArea').innerHTML = renderCode('def detectCycle(head):\n slow = fast = head\n while fast and fast.next:\n slow = slow.next\n fast = fast.next.next\n if slow is fast:\n ptr1, ptr2 = head, slow\n while ptr1 is not ptr2:\n ptr1 = ptr1.next\n ptr2 = ptr2.next\n return ptr1\n return None', {lang:'Python'}); ''' def js_merge_two_sorted_lists(): return _LLVIZ + _PIPELINE_HELPER + r''' var examples = [ {list1:[1,2,4], list2:[1,3,4], label:'示例1: [1,2,4]+[1,3,4]'}, {list1:[], list2:[], label:'示例2: 两空链表'}, {list1:[], list2:[0], label:'示例3: []+[0]'}, ]; var steps, stepCtrl; function buildSteps(ex) { steps = []; var l1 = ex.list1.slice(), l2 = ex.list2.slice(); steps.push({stage:'init', msg:'双指针合并:比较两个链表头,取较小者加入结果', i1:0, i2:0, merged:[], phase:'merge'}); var i1 = 0, i2 = 0, merged = []; while (i1 < l1.length && i2 < l2.length) { steps.push({stage:'compare', msg:'比较 l1['+i1+']=' + l1[i1] + ' vs l2['+i2+']=' + l2[i2], i1:i1, i2:i2, merged:merged.slice(), phase:'merge'}); if (l1[i1] <= l2[i2]) { merged.push(l1[i1]); steps.push({stage:'take1', msg:'取 l1['+i1+']=' + l1[i1] + ' 加入结果', i1:i1+1, i2:i2, merged:merged.slice(), phase:'merge'}); i1++; } else { merged.push(l2[i2]); steps.push({stage:'take2', msg:'取 l2['+i2+']=' + l2[i2] + ' 加入结果', i1:i1, i2:i2+1, merged:merged.slice(), phase:'merge'}); i2++; } } while (i1 < l1.length) { merged.push(l1[i1]); i1++; } while (i2 < l2.length) { merged.push(l2[i2]); i2++; } steps.push({stage:'done', msg:'合并完成:[' + merged.join(',') + ']', i1:i1, i2:i2, merged:merged.slice(), phase:'done'}); } function render(step) { var s = steps[step]; var exIdx = parseInt($('exampleSelect').value) || 0; var ex = examples[exIdx]; var hl1 = {}, hl2 = {}; if (s.i1 < ex.list1.length) { hl1[s.i1] = s.stage === 'take1' ? 'active' : 'current'; } if (s.i2 < ex.list2.length) { hl2[s.i2] = s.stage === 'take2' ? 'active' : 'current'; } var viz = llViz(ex.list1, {highs:hl1, ptrs:{i1:s.i1 0) { viz += llViz(s.merged, {highs:{[s.merged.length-1]:'active'}, label:'合并结果', showNull:s.phase==='done'?true:false}); } $('vizArea').innerHTML = viz; $('detailContent').innerHTML = '
' + s.msg + '
'; if (s.stage === 'done') { $('resultContent').innerHTML = '
合并结果:[' + s.merged.join(' → ') + ']
'; } $('hintText').textContent = s.msg; setPipeline(['init\u2192初始化','compare\u2192比较','take1\u2192取L1','take2\u2192取L2','done\u2192完成'], s.stage); } function init() { var sel = $('exampleSelect'); examples.forEach(function(e,i){ sel.innerHTML += ''; }); $('inputArea').value = 'L1=[1,2,4] L2=[1,3,4]'; buildSteps(examples[0]); stepCtrl = new StepController({onStep:render}); stepCtrl.setSteps(steps.map(function(_,i){return i;})); $('stepInfo').textContent = '步骤 1 / ' + steps.length; stepCtrl.onStep = function(idx){ render(idx); $('stepInfo').textContent = '步骤 '+(idx+1)+' / '+steps.length; }; $('applyBtn').onclick = function(){ buildSteps(examples[parseInt($('exampleSelect').value)]); stepCtrl.setSteps(steps.map(function(_,i){return i;})); render(0); }; $('exampleSelect').onchange = function(){ buildSteps(examples[parseInt($('exampleSelect').value)]); stepCtrl.setSteps(steps.map(function(_,i){return i;})); render(0); }; $('prevBtn').onclick = function(){ stepCtrl.prev(); }; $('nextBtn').onclick = function(){ stepCtrl.next(); }; $('jumpBtn').onclick = function(){ stepCtrl.jumpToEnd(); }; $('autoBtn').onclick = function(){ var on=stepCtrl.toggleAuto(); $('autoBtn').textContent=on?'暂停':'自动播放'; }; $('resetBtn').onclick = function(){ stepCtrl.reset(); $('autoBtn').textContent='自动播放'; }; } init(); $('codeArea').innerHTML = renderCode('def mergeTwoLists(l1, l2):\n dummy = ListNode(0)\n curr = dummy\n while l1 and l2:\n if l1.val <= l2.val:\n curr.next = l1\n l1 = l1.next\n else:\n curr.next = l2\n l2 = l2.next\n curr = curr.next\n curr.next = l1 or l2\n return dummy.next', {lang:'Python'}); ''' def js_add_two_numbers(): return _LLVIZ + _PIPELINE_HELPER + r''' var examples = [ {l1:[2,4,3], l2:[5,6,4], label:'示例1: 342+465=807'}, {l1:[0], l2:[0], label:'示例2: 0+0=0'}, {l1:[9,9,9,9,9,9,9], l2:[9,9,9,9], label:'示例3: 大数相加'}, ]; var steps, stepCtrl; function buildSteps(ex) { steps = []; var l1 = ex.l1.slice(), l2 = ex.l2.slice(); steps.push({stage:'init', msg:'逐位相加 + 进位,链表已按逆序存储各位数字', i1:0, i2:0, carry:0, result:[], phase:'add'}); var i1 = 0, i2 = 0, carry = 0, result = []; while (i1 < l1.length || i2 < l2.length || carry) { var v1 = i1 < l1.length ? l1[i1] : 0; var v2 = i2 < l2.length ? l2[i2] : 0; var sum = v1 + v2 + carry; var digit = sum % 10; var newCarry = Math.floor(sum / 10); steps.push({stage:'calc', msg: v1+' + '+v2+' + '+carry+'(carry) = '+sum+' → 写 '+digit+',进位 '+newCarry, i1:i1, i2:i2, carry:carry, sum:sum, digit:digit, newCarry:newCarry, result:result.slice(), phase:'add'}); result.push(digit); carry = newCarry; i1++; i2++; } steps.push({stage:'done', msg:'结果:[' + result.join(',') + ']', result:result.slice(), phase:'done'}); } function render(step) { var s = steps[step]; var exIdx = parseInt($('exampleSelect').value) || 0; var ex = examples[exIdx]; var hl1 = {}, hl2 = {}, hlR = {}; if (s.i1 < ex.l1.length) hl1[s.i1] = 'current'; if (s.i2 < ex.l2.length) hl2[s.i2] = 'current'; if (s.digit !== undefined && s.result) { for (var ri = 0; ri < s.result.length; ri++) hlR[ri] = 'active'; } var viz = llViz(ex.l1, {highs:hl1, ptrs:{p1:s.i1'; } if (s.result && s.result.length > 0) { viz += llViz(s.result, {highs:hlR, label:'结果 (逆序)'}); } $('vizArea').innerHTML = viz; $('detailContent').innerHTML = '
' + s.msg + '
'; if (s.stage === 'done') { var num1 = ex.l1.slice().reverse().join(''); var num2 = ex.l2.slice().reverse().join(''); var numR = s.result.slice().reverse().join(''); $('resultContent').innerHTML = '
' + num1 + ' + ' + num2 + ' = ' + numR + '
结果链表:[' + s.result.join(' → ') + ']
'; } $('hintText').textContent = s.msg; setPipeline(['init\u2192开始','calc\u2192计算','done\u2192完成'], s.stage); } function init() { var sel = $('exampleSelect'); examples.forEach(function(e,i){ sel.innerHTML += ''; }); $('inputArea').value = 'l1=[2,4,3] l2=[5,6,4]'; buildSteps(examples[0]); stepCtrl = new StepController({onStep:render}); stepCtrl.setSteps(steps.map(function(_,i){return i;})); $('stepInfo').textContent = '步骤 1 / ' + steps.length; stepCtrl.onStep = function(idx){ render(idx); $('stepInfo').textContent = '步骤 '+(idx+1)+' / '+steps.length; }; $('applyBtn').onclick = function(){ buildSteps(examples[parseInt($('exampleSelect').value)]); stepCtrl.setSteps(steps.map(function(_,i){return i;})); render(0); }; $('exampleSelect').onchange = function(){ buildSteps(examples[parseInt($('exampleSelect').value)]); stepCtrl.setSteps(steps.map(function(_,i){return i;})); render(0); }; $('prevBtn').onclick = function(){ stepCtrl.prev(); }; $('nextBtn').onclick = function(){ stepCtrl.next(); }; $('jumpBtn').onclick = function(){ stepCtrl.jumpToEnd(); }; $('autoBtn').onclick = function(){ var on=stepCtrl.toggleAuto(); $('autoBtn').textContent=on?'暂停':'自动播放'; }; $('resetBtn').onclick = function(){ stepCtrl.reset(); $('autoBtn').textContent='自动播放'; }; } init(); $('codeArea').innerHTML = renderCode('def addTwoNumbers(l1, l2):\n dummy = ListNode(0)\n curr = dummy\n carry = 0\n while l1 or l2 or carry:\n v1 = l1.val if l1 else 0\n v2 = l2.val if l2 else 0\n s = v1 + v2 + carry\n carry = s // 10\n curr.next = ListNode(s % 10)\n curr = curr.next\n l1 = l1.next if l1 else None\n l2 = l2.next if l2 else None\n return dummy.next', {lang:'Python'}); ''' def js_remove_nth_node_from_end_of_list(): return _LLVIZ + _PIPELINE_HELPER + r''' var examples = [ {input:[1,2,3,4,5], n:2, label:'示例1: [1,2,3,4,5] n=2 删除4'}, {input:[1], n:1, label:'示例2: [1] n=1 删除1'}, {input:[1,2], n:1, label:'示例3: [1,2] n=1 删除2'}, ]; var nodes, steps, stepCtrl; function buildSteps(ex) { nodes = ex.input.slice(); steps = []; var n = ex.n; var len = nodes.length; steps.push({stage:'init', msg:'快慢指针:fast 先走 n 步,然后 slow 和 fast 同步走', slow:-1, fast:0, phase:'advance'}); // Use dummy node represented as index -1 var slow = -1, fast = 0; // fast advances n steps for (var i = 0; i < n; i++) { steps.push({stage:'advance', msg:'fast 前进:' + (i+1) + '/' + n + ' 步,fast=' + (fast < len ? nodes[fast] : 'null'), slow:-1, fast:fast, phase:'advance'}); fast++; } steps.push({stage:'ready', msg:'fast 已领先 ' + n + ' 步,现在 slow 和 fast 同步前进', slow:-1, fast:fast, phase:'move'}); // both advance until fast reaches end while (fast < len) { slow++; if (fast < len) fast++; steps.push({stage:'move', msg:'slow→' + (slow>=0?nodes[slow]:'dummy') + ', fast→' + (fast= 0 && s.slow < nodes.length) { hl[s.slow] = 'active'; ptrs.slow = s.slow; } if (s.fast >= 0 && s.fast < nodes.length) { hl[s.fast] = 'current'; ptrs.fast = s.fast; } if (s.delIdx !== undefined && s.delIdx < nodes.length) hl[s.delIdx] = 'null-node'; var vizLabel = '链表 (n=' + examples[parseInt($('exampleSelect').value)||0].n + ')'; var viz = llViz(nodes, {highs:hl, ptrs:ptrs, label:vizLabel}); if (s.slow === -1 && (s.phase === 'advance' || s.phase === 'ready')) { viz += '
slow 在 dummy 节点
'; } $('vizArea').innerHTML = viz; $('detailContent').innerHTML = '
' + s.msg + '
'; if (s.stage === 'done') { $('resultContent').innerHTML = '
删除后:[' + s.result.join(' → ') + ']
'; } $('hintText').textContent = s.msg; setPipeline(['init\u2192初始化','advance\u2192快进','ready\u2192就绪','move\u2192同步','delete\u2192删除','done\u2192完成'], s.stage); } function init() { var sel = $('exampleSelect'); examples.forEach(function(e,i){ sel.innerHTML += ''; }); $('inputArea').value = '[1,2,3,4,5] n=2'; buildSteps(examples[0]); stepCtrl = new StepController({onStep:render}); stepCtrl.setSteps(steps.map(function(_,i){return i;})); $('stepInfo').textContent = '步骤 1 / ' + steps.length; stepCtrl.onStep = function(idx){ render(idx); $('stepInfo').textContent = '步骤 '+(idx+1)+' / '+steps.length; }; $('applyBtn').onclick = function(){ buildSteps(examples[parseInt($('exampleSelect').value)]); stepCtrl.setSteps(steps.map(function(_,i){return i;})); render(0); }; $('exampleSelect').onchange = function(){ buildSteps(examples[parseInt($('exampleSelect').value)]); stepCtrl.setSteps(steps.map(function(_,i){return i;})); render(0); }; $('prevBtn').onclick = function(){ stepCtrl.prev(); }; $('nextBtn').onclick = function(){ stepCtrl.next(); }; $('jumpBtn').onclick = function(){ stepCtrl.jumpToEnd(); }; $('autoBtn').onclick = function(){ var on=stepCtrl.toggleAuto(); $('autoBtn').textContent=on?'暂停':'自动播放'; }; $('resetBtn').onclick = function(){ stepCtrl.reset(); $('autoBtn').textContent='自动播放'; }; } init(); $('codeArea').innerHTML = renderCode('def removeNthFromEnd(head, n):\n dummy = ListNode(0, head)\n fast = slow = dummy\n for _ in range(n):\n fast = fast.next\n while fast.next:\n slow = slow.next\n fast = fast.next\n slow.next = slow.next.next\n return dummy.next', {lang:'Python'}); ''' def js_swap_nodes_in_pairs(): return _LLVIZ + _PIPELINE_HELPER + r''' var examples = [ {input:[1,2,3,4], label:'示例1: [1,2,3,4]'}, {input:[], label:'示例2: []'}, {input:[1], label:'示例3: [1]'}, {input:[1,2,3], label:'示例4: [1,2,3]'}, ]; var nodes, steps, stepCtrl; function buildSteps(arr) { nodes = arr.slice(); steps = []; if (arr.length < 2) { steps.push({stage:'done', msg:'节点数不足2,无需交换', result:nodes.slice(), phase:'done'}); return; } steps.push({stage:'init', msg:'两两交换:对每对相邻节点 (a,b) 进行三步指针交换', pairStart:0, phase:'swap'}); var result = nodes.slice(); for (var i = 0; i + 1 < result.length; i += 2) { steps.push({stage:'identify', msg:'识别交换对:' + result[i] + ' ↔ ' + result[i+1], pairStart:i, result:result.slice(), phase:'swap'}); // swap var tmp = result[i]; result[i] = result[i+1]; result[i+1] = tmp; steps.push({stage:'swap', msg:'交换 ' + nodes[i] + ' 和 ' + nodes[i+1] + ':[' + result.join(',') + ']', pairStart:i, result:result.slice(), phase:'swap'}); } steps.push({stage:'done', msg:'交换完成:[' + result.join(',') + ']', result:result.slice(), phase:'done'}); } function render(step) { var s = steps[step]; var hl = {}; if (s.pairStart !== undefined && s.pairStart + 1 < nodes.length) { if (s.stage === 'identify') { hl[s.pairStart] = 'current'; hl[s.pairStart + 1] = 'current'; } else if (s.stage === 'swap') { hl[s.pairStart] = 'active'; hl[s.pairStart + 1] = 'active'; } } var viz = llViz(s.result || nodes, {highs:hl, ptrs:{}, label:'链表状态'}); $('vizArea').innerHTML = viz; $('detailContent').innerHTML = '
' + s.msg + '
'; if (s.stage === 'done') { $('resultContent').innerHTML = '
交换结果:[' + s.result.join(' → ') + ']
'; } $('hintText').textContent = s.msg; setPipeline(['init\u2192初始化','identify\u2192识别','swap\u2192交换','done\u2192完成'], s.stage); } function init() { var sel = $('exampleSelect'); examples.forEach(function(e,i){ sel.innerHTML += ''; }); $('inputArea').value = '[1,2,3,4]'; buildSteps(examples[0].input); stepCtrl = new StepController({onStep:render}); stepCtrl.setSteps(steps.map(function(_,i){return i;})); $('stepInfo').textContent = '步骤 1 / ' + steps.length; stepCtrl.onStep = function(idx){ render(idx); $('stepInfo').textContent = '步骤 '+(idx+1)+' / '+steps.length; }; $('applyBtn').onclick = function(){ try { var arr=JSON.parse($('inputArea').value); buildSteps(arr); stepCtrl.setSteps(steps.map(function(_,i){return i;})); render(0); } catch(e){ alert('请输入合法 JSON 数组'); } }; $('exampleSelect').onchange = function(){ buildSteps(examples[parseInt($('exampleSelect').value)].input); stepCtrl.setSteps(steps.map(function(_,i){return i;})); render(0); }; $('prevBtn').onclick = function(){ stepCtrl.prev(); }; $('nextBtn').onclick = function(){ stepCtrl.next(); }; $('jumpBtn').onclick = function(){ stepCtrl.jumpToEnd(); }; $('autoBtn').onclick = function(){ var on=stepCtrl.toggleAuto(); $('autoBtn').textContent=on?'暂停':'自动播放'; }; $('resetBtn').onclick = function(){ stepCtrl.reset(); $('autoBtn').textContent='自动播放'; }; } init(); $('codeArea').innerHTML = renderCode('def swapPairs(head):\n dummy = ListNode(0, head)\n prev = dummy\n while prev.next and prev.next.next:\n a, b = prev.next, prev.next.next\n prev.next = b\n a.next = b.next\n b.next = a\n prev = a\n return dummy.next', {lang:'Python'}); ''' def js_reverse_nodes_in_k_group(): return _LLVIZ + _PIPELINE_HELPER + r''' var examples = [ {input:[1,2,3,4,5], k:2, label:'示例1: [1,2,3,4,5] k=2'}, {input:[1,2,3,4,5], k:3, label:'示例2: [1,2,3,4,5] k=3'}, {input:[1,2,3,4,5], k:1, label:'示例3: k=1 不翻转'}, ]; var nodes, steps, stepCtrl; function buildSteps(ex) { nodes = ex.input.slice(); steps = []; var k = ex.k; var result = nodes.slice(); steps.push({stage:'init', msg:'K个一组翻转链表,k=' + k + ':每组k个节点内部反转,不足k个不翻转', phase:'group', result:result.slice(), groupIdx:0}); var groupIdx = 0; for (var i = 0; i + k <= result.length; i += k) { steps.push({stage:'identify', msg:'第 ' + (groupIdx+1) + ' 组:[' + result.slice(i, i+k).join(',') + ']', phase:'group', result:result.slice(), groupStart:i, groupIdx:groupIdx}); // reverse in-place var left = i, right = i + k - 1; while (left < right) { var tmp = result[left]; result[left] = result[right]; result[right] = tmp; left++; right--; } steps.push({stage:'reverse', msg:'第 ' + (groupIdx+1) + ' 组翻转后:[' + result.slice(i, i+k).join(',') + ']', phase:'group', result:result.slice(), groupStart:i, groupIdx:groupIdx}); groupIdx++; } var remaining = result.length % k; if (remaining > 0) { steps.push({stage:'skip', msg:'剩余 ' + remaining + ' 个节点不足 k=' + k + ',不翻转', phase:'group', result:result.slice()}); } steps.push({stage:'done', msg:'翻转完成:[' + result.join(',') + ']', phase:'done', result:result.slice()}); } function render(step) { var s = steps[step]; var hl = {}; if (s.groupStart !== undefined) { var k = examples[parseInt($('exampleSelect').value)||0].k; for (var gi = s.groupStart; gi < s.groupStart + k && gi < nodes.length; gi++) { hl[gi] = s.stage === 'reverse' ? 'active' : 'current'; } } var viz = llViz(s.result || nodes, {highs:hl, label:'链表状态'}); $('vizArea').innerHTML = viz; $('detailContent').innerHTML = '
' + s.msg + '
'; if (s.stage === 'done') { $('resultContent').innerHTML = '
结果:[' + s.result.join(' → ') + ']
'; } $('hintText').textContent = s.msg; setPipeline(['init\u2192初始化','identify\u2192识别组','reverse\u2192翻转','skip\u2192跳过','done\u2192完成'], s.stage); } function init() { var sel = $('exampleSelect'); examples.forEach(function(e,i){ sel.innerHTML += ''; }); $('inputArea').value = '[1,2,3,4,5] k=2'; buildSteps(examples[0]); stepCtrl = new StepController({onStep:render}); stepCtrl.setSteps(steps.map(function(_,i){return i;})); $('stepInfo').textContent = '步骤 1 / ' + steps.length; stepCtrl.onStep = function(idx){ render(idx); $('stepInfo').textContent = '步骤 '+(idx+1)+' / '+steps.length; }; $('applyBtn').onclick = function(){ buildSteps(examples[parseInt($('exampleSelect').value)]); stepCtrl.setSteps(steps.map(function(_,i){return i;})); render(0); }; $('exampleSelect').onchange = function(){ buildSteps(examples[parseInt($('exampleSelect').value)]); stepCtrl.setSteps(steps.map(function(_,i){return i;})); render(0); }; $('prevBtn').onclick = function(){ stepCtrl.prev(); }; $('nextBtn').onclick = function(){ stepCtrl.next(); }; $('jumpBtn').onclick = function(){ stepCtrl.jumpToEnd(); }; $('autoBtn').onclick = function(){ var on=stepCtrl.toggleAuto(); $('autoBtn').textContent=on?'暂停':'自动播放'; }; $('resetBtn').onclick = function(){ stepCtrl.reset(); $('autoBtn').textContent='自动播放'; }; } init(); $('codeArea').innerHTML = renderCode('def reverseKGroup(head, k):\n def reverse(head, tail):\n prev = None\n curr = head\n while curr != tail:\n nxt = curr.next\n curr.next = prev\n prev = curr\n curr = nxt\n return prev\n\n dummy = ListNode(0, head)\n prev = dummy\n while True:\n tail = prev.next\n for _ in range(k):\n if not tail: return dummy.next\n tail = tail.next\n new_head = reverse(prev.next, tail)\n tmp = prev.next\n prev.next = new_head\n tmp.next = tail\n prev = tmp\n return dummy.next', {lang:'Python'}); ''' def js_copy_list_with_random_pointer(): return _LLVIZ + _PIPELINE_HELPER + r''' var examples = [ {nodes:[[7,null],[13,0],[11,4],[10,2],[1,0]], label:'示例1: 5个节点'}, {nodes:[[1,1],[2,1]], label:'示例2: 2个节点'}, {nodes:[[3,null],[3,0],[3,null]], label:'示例3: 3个节点'}, ]; var steps, stepCtrl; function buildSteps(ex) { steps = []; var n = ex.nodes.length; steps.push({stage:'init', msg:'交错复制法:①在每个原节点后插入复制节点 ②设置random指针 ③拆分', phase:'interleave'}); // Phase 1: interleave var interleaved = []; for (var i = 0; i < n; i++) { interleaved.push({val:ex.nodes[i][0], isCopy:false, origIdx:i}); interleaved.push({val:ex.nodes[i][0], isCopy:true, origIdx:i}); } steps.push({stage:'interleave', msg:'Phase1:在每个原节点后插入其复制 → ' + interleaved.map(function(x){return x.val+(x.isCopy?"'":"");}).join(','), phase:'interleave', interleaved:interleaved.slice()}); // Phase 2: set random pointers var randomLinks = []; for (var i = 0; i < n; i++) { var rnd = ex.nodes[i][1]; if (rnd !== null) { randomLinks.push({from:i*2+1, to:rnd*2+1, msg:"copy["+i+"].random = copy["+rnd+"] (值="+ex.nodes[rnd][0]+")"}); } else { randomLinks.push({from:i*2+1, to:-1, msg:"copy["+i+"].random = null"}); } } steps.push({stage:'random', msg:'Phase2:设置random指针 → ' + randomLinks.map(function(r){return r.msg;}).join('; '), phase:'random', interleaved:interleaved.slice(), randomLinks:randomLinks}); // Phase 3: split var original = [], copy = []; for (var i = 0; i < interleaved.length; i++) { if (i%2===0) original.push(interleaved[i].val); else copy.push(interleaved[i].val); } steps.push({stage:'split', msg:'Phase3:拆分为原链表和复制链表', phase:'split', original:original, copy:copy}); steps.push({stage:'done', msg:'深拷贝完成', phase:'done', copy:copy}); } function renderInterleaved(interleaved, hlIdx) { var html = '
'; for (var i = 0; i < interleaved.length; i++) { var n = interleaved[i]; var bg = n.isCopy ? 'var(--green-light)' : 'var(--blue-light)'; var border = n.isCopy ? 'var(--green)' : 'var(--blue)'; var label = n.val + (n.isCopy ? "'" : ''); var hl = (hlIdx === i) ? 'box-shadow:0 0 0 3px var(--orange);' : ''; html += '
'; html += '
'+label+'
'; html += '
'+(n.isCopy?'copy':'orig')+'
'; html += '
'; if (i < interleaved.length - 1) html += '→'; } html += '
'; return html; } function render(step) { var s = steps[step]; var viz = ''; if (s.phase === 'interleave' && s.interleaved) { viz = renderInterleaved(s.interleaved, -1); } else if (s.phase === 'random' && s.interleaved) { viz = renderInterleaved(s.interleaved, -1); viz += '
Random 指针设置:
'; s.randomLinks.forEach(function(r) { viz += '
' + r.msg + '
'; }); } else if (s.phase === 'split' || s.phase === 'done') { viz = llViz(s.original, {highlights:{}, label:'原链表', showNull:false}); viz += llViz(s.copy, {highlights:{}, label:'复制链表', showNull:false}); } $('vizArea').innerHTML = viz; $('detailContent').innerHTML = '
' + s.msg + '
'; if (s.stage === 'done') { $('resultContent').innerHTML = '
深拷贝链表:[' + s.copy.join(' → ') + ']
'; } $('hintText').textContent = s.msg; setPipeline(['init\u2192初始化','interleave\u2192交错插入','random\u2192设置随机','split\u2192拆分','done\u2192完成'], s.stage); } function init() { var sel = $('exampleSelect'); examples.forEach(function(e,i){ sel.innerHTML += ''; }); $('inputArea').value = '[[7,null],[13,0],[11,4],[10,2],[1,0]]'; buildSteps(examples[0]); stepCtrl = new StepController({onStep:render}); stepCtrl.setSteps(steps.map(function(_,i){return i;})); $('stepInfo').textContent = '步骤 1 / ' + steps.length; stepCtrl.onStep = function(idx){ render(idx); $('stepInfo').textContent = '步骤 '+(idx+1)+' / '+steps.length; }; $('applyBtn').onclick = function(){ buildSteps(examples[parseInt($('exampleSelect').value)]); stepCtrl.setSteps(steps.map(function(_,i){return i;})); render(0); }; $('exampleSelect').onchange = function(){ buildSteps(examples[parseInt($('exampleSelect').value)]); stepCtrl.setSteps(steps.map(function(_,i){return i;})); render(0); }; $('prevBtn').onclick = function(){ stepCtrl.prev(); }; $('nextBtn').onclick = function(){ stepCtrl.next(); }; $('jumpBtn').onclick = function(){ stepCtrl.jumpToEnd(); }; $('autoBtn').onclick = function(){ var on=stepCtrl.toggleAuto(); $('autoBtn').textContent=on?'暂停':'自动播放'; }; $('resetBtn').onclick = function(){ stepCtrl.reset(); $('autoBtn').textContent='自动播放'; }; } init(); $('codeArea').innerHTML = renderCode('def copyRandomList(head):\n if not head: return None\n # 1. interleave copies\n cur = head\n while cur:\n copy = Node(cur.val, cur.next, None)\n cur.next = copy\n cur = copy.next\n # 2. set random pointers\n cur = head\n while cur:\n if cur.random:\n cur.next.random = cur.random.next\n cur = cur.next.next\n # 3. split\n cur = head\n copy_head = head.next\n while cur:\n copy = cur.next\n cur.next = copy.next\n if copy.next:\n copy.next = copy.next.next\n cur = cur.next\n return copy_head', {lang:'Python'}); ''' def js_sort_list(): return _LLVIZ + _PIPELINE_HELPER + r''' var examples = [ {input:[4,2,1,3], label:'示例1: [4,2,1,3]'}, {input:[-1,5,3,4,0], label:'示例2: [-1,5,3,4,0]'}, {input:[], label:'示例3: []'}, ]; var steps, stepCtrl; function buildSteps(arr) { steps = []; if (arr.length === 0) { steps.push({stage:'done',msg:'空链表',result:[],phase:'done'}); return; } function mergeSort(a, depth) { if (a.length <= 1) return a; var mid = Math.floor(a.length / 2); var left = a.slice(0, mid); var right = a.slice(mid); steps.push({stage:'split', msg:'分割 ['+a.join(',')+'] → ['+left.join(',')+'] + ['+right.join(',')+']', phase:'split', depth:depth, arr:a.slice(), left:left, right:right}); var sortedL = mergeSort(left, depth+1); var sortedR = mergeSort(right, depth+1); // merge var merged = []; var i = 0, j = 0; while (i < sortedL.length && j < sortedR.length) { if (sortedL[i] <= sortedR[j]) { merged.push(sortedL[i]); i++; } else { merged.push(sortedR[j]); j++; } } while (i < sortedL.length) { merged.push(sortedL[i]); i++; } while (j < sortedR.length) { merged.push(sortedR[j]); j++; } steps.push({stage:'merge', msg:'合并 ['+sortedL.join(',')+'] + ['+sortedR.join(',')+'] → ['+merged.join(',')+']', phase:'merge', depth:depth, sortedL:sortedL, sortedR:sortedR, result:merged}); return merged; } steps.push({stage:'init', msg:'归并排序:递归分割 + 合并', phase:'init', arr:arr.slice()}); var result = mergeSort(arr.slice(), 0); steps.push({stage:'done', msg:'排序完成:[' + result.join(',') + ']', phase:'done', result:result}); } function render(step) { var s = steps[step]; var viz = ''; if (s.phase === 'init') { viz = llViz(s.arr, {label:'原始链表'}); } else if (s.phase === 'split') { viz = llViz(s.arr, {label:'分割前', showNull:false}); viz += llViz(s.left, {label:'左半', showNull:false}); viz += llViz(s.right, {label:'右半', showNull:false}); viz += '
递归深度: ' + s.depth + '
'; } else if (s.phase === 'merge') { viz = llViz(s.sortedL, {label:'左半(已排序)', showNull:false}); viz += llViz(s.sortedR, {label:'右半(已排序)', showNull:false}); viz += llViz(s.result, {highlights:{}, label:'合并结果', showNull:false}); } else if (s.phase === 'done') { viz = llViz(s.result, {label:'排序结果'}); } $('vizArea').innerHTML = viz; $('detailContent').innerHTML = '
' + s.msg + '
'; if (s.stage === 'done') { $('resultContent').innerHTML = '
排序结果:[' + s.result.join(' → ') + ']
'; } $('hintText').textContent = s.msg; setPipeline(['init\u2192开始','split\u2192分割','merge\u2192合并','done\u2192完成'], s.stage); } function init() { var sel = $('exampleSelect'); examples.forEach(function(e,i){ sel.innerHTML += ''; }); $('inputArea').value = '[4,2,1,3]'; buildSteps(examples[0].input); stepCtrl = new StepController({onStep:render}); stepCtrl.setSteps(steps.map(function(_,i){return i;})); $('stepInfo').textContent = '步骤 1 / ' + steps.length; stepCtrl.onStep = function(idx){ render(idx); $('stepInfo').textContent = '步骤 '+(idx+1)+' / '+steps.length; }; $('applyBtn').onclick = function(){ try { var arr=JSON.parse($('inputArea').value); buildSteps(arr); stepCtrl.setSteps(steps.map(function(_,i){return i;})); render(0); } catch(e){ alert('请输入合法 JSON 数组'); } }; $('exampleSelect').onchange = function(){ buildSteps(examples[parseInt($('exampleSelect').value)].input); stepCtrl.setSteps(steps.map(function(_,i){return i;})); render(0); }; $('prevBtn').onclick = function(){ stepCtrl.prev(); }; $('nextBtn').onclick = function(){ stepCtrl.next(); }; $('jumpBtn').onclick = function(){ stepCtrl.jumpToEnd(); }; $('autoBtn').onclick = function(){ var on=stepCtrl.toggleAuto(); $('autoBtn').textContent=on?'暂停':'自动播放'; }; $('resetBtn').onclick = function(){ stepCtrl.reset(); $('autoBtn').textContent='自动播放'; }; } init(); $('codeArea').innerHTML = renderCode('def sortList(head):\n if not head or not head.next:\n return head\n # find middle\n slow = fast = head\n while fast.next and fast.next.next:\n slow = slow.next\n fast = fast.next.next\n mid = slow.next\n slow.next = None\n left = sortList(head)\n right = sortList(mid)\n # merge\n dummy = ListNode(0)\n cur = dummy\n while left and right:\n if left.val <= right.val:\n cur.next = left\n left = left.next\n else:\n cur.next = right\n right = right.next\n cur = cur.next\n cur.next = left or right\n return dummy.next', {lang:'Python'}); ''' def js_merge_k_sorted_lists(): return _LLVIZ + _PIPELINE_HELPER + r''' var examples = [ {lists:[[1,4,5],[1,3,4],[2,6]], label:'示例1: 3个链表'}, {lists:[], label:'示例2: 空列表'}, {lists:[[]], label:'示例3: 1个空链表'}, ]; var steps, stepCtrl; function buildSteps(ex) { steps = []; var lists = ex.lists.filter(function(l){return l.length>0;}); if (lists.length === 0) { steps.push({stage:'done',msg:'无有效链表',result:[],phase:'done'}); return; } steps.push({stage:'init', msg:'使用最小堆合并K个有序链表:每次取当前所有链表头的最小值', phase:'init', heads:lists.map(function(l){return 0;}), result:[]}); // simulate min-heap merge var ptrs = lists.map(function(){return 0;}); // current index in each list var result = []; var totalLen = lists.reduce(function(s,l){return s+l.length;}, 0); for (var step = 0; step < totalLen; step++) { // find min among current heads var minVal = Infinity, minIdx = -1; for (var i = 0; i < lists.length; i++) { if (ptrs[i] < lists[i].length && lists[i][ptrs[i]] < minVal) { minVal = lists[i][ptrs[i]]; minIdx = i; } } steps.push({stage:'compare', msg:'比较各链表头:' + lists.map(function(l,i){return ptrs[i]0;}); var viz = ''; var hl, ptrs2; for (var li = 0; li < lists.length; li++) { hl = {}; ptrs2 = {}; if (s.heads && s.heads[li] < lists[li].length) { hl[s.heads[li]] = (s.minIdx === li) ? 'active' : 'current'; ptrs2['L'+(li+1)] = s.heads[li]; } viz += llViz(lists[li], {highs:hl, ptrs:ptrs2, label:'链表 '+(li+1), showNull:false}); } if (s.result && s.result.length > 0) { viz += llViz(s.result, {highlights:{[s.result.length-1]:'active'}, label:'合并结果', showNull:false}); } $('vizArea').innerHTML = viz; $('detailContent').innerHTML = '
' + s.msg + '
'; if (s.stage === 'done') { $('resultContent').innerHTML = '
合并结果:[' + s.result.join(' → ') + ']
'; } $('hintText').textContent = s.msg; setPipeline(['init\u2192初始化','compare\u2192比较','pick\u2192选取','done\u2192完成'], s.stage); } function init() { var sel = $('exampleSelect'); examples.forEach(function(e,i){ sel.innerHTML += ''; }); $('inputArea').value = '[[1,4,5],[1,3,4],[2,6]]'; buildSteps(examples[0]); stepCtrl = new StepController({onStep:render}); stepCtrl.setSteps(steps.map(function(_,i){return i;})); $('stepInfo').textContent = '步骤 1 / ' + steps.length; stepCtrl.onStep = function(idx){ render(idx); $('stepInfo').textContent = '步骤 '+(idx+1)+' / '+steps.length; }; $('applyBtn').onclick = function(){ buildSteps(examples[parseInt($('exampleSelect').value)]); stepCtrl.setSteps(steps.map(function(_,i){return i;})); render(0); }; $('exampleSelect').onchange = function(){ buildSteps(examples[parseInt($('exampleSelect').value)]); stepCtrl.setSteps(steps.map(function(_,i){return i;})); render(0); }; $('prevBtn').onclick = function(){ stepCtrl.prev(); }; $('nextBtn').onclick = function(){ stepCtrl.next(); }; $('jumpBtn').onclick = function(){ stepCtrl.jumpToEnd(); }; $('autoBtn').onclick = function(){ var on=stepCtrl.toggleAuto(); $('autoBtn').textContent=on?'暂停':'自动播放'; }; $('resetBtn').onclick = function(){ stepCtrl.reset(); $('autoBtn').textContent='自动播放'; }; } init(); $('codeArea').innerHTML = renderCode('import heapq\ndef mergeKLists(lists):\n heap = []\n for i, node in enumerate(lists):\n if node:\n heapq.heappush(heap, (node.val, i, node))\n dummy = ListNode(0)\n cur = dummy\n while heap:\n val, i, node = heapq.heappop(heap)\n cur.next = node\n cur = cur.next\n if node.next:\n heapq.heappush(heap, (node.next.val, i, node.next))\n return dummy.next', {lang:'Python'}); ''' def js_lru_cache(): return _LLVIZ + _PIPELINE_HELPER + r''' var examples = [ {ops:['LRUCache(2)','put(1,1)','put(2,2)','get(1)','put(3,3)','get(2)','put(4,4)','get(1)','get(3)','get(4)'], label:'示例1: capacity=2'}, {ops:['LRUCache(3)','put(1,1)','put(2,2)','put(3,3)','get(1)','put(4,4)','get(2)'], label:'示例2: capacity=3'}, ]; var steps, stepCtrl; function buildSteps(ex) { steps = []; var cap = 2; var ops = ex.ops; var capMatch = ops[0].match(/LRUCache\((\d+)\)/); if (capMatch) cap = parseInt(capMatch[1]); // DLL: head <-> ... <-> tail (head=most recent, tail=least recent) var dll = []; // [{key, val}] var map = {}; // key -> index in dll var capacity = cap; steps.push({stage:'init', msg:'LRU 缓存初始化,capacity=' + cap + '。双向链表(头=最近,尾=最久)+ 哈希表', dll:[], map:{}, capacity:capacity, op:ops[0]}); for (var opIdx = 1; opIdx < ops.length; opIdx++) { var op = ops[opIdx]; var getMatch = op.match(/get\((\d+)\)/); var putMatch = op.match(/put\((\d+),(\d+)\)/); if (getMatch) { var key = getMatch[1]; if (key in map) { // hit: move to front var idx = map[key]; var node = dll[idx]; dll.splice(idx, 1); dll.unshift(node); // rebuild map map = {}; for (var mi = 0; mi < dll.length; mi++) map[dll[mi].key] = mi; steps.push({stage:'hit', msg:'get('+key+') 命中!返回 '+node.val+',移到链表头部', dll:JSON.parse(JSON.stringify(dll)), map:JSON.parse(JSON.stringify(map)), capacity:capacity, op:op, hitVal:node.val}); } else { steps.push({stage:'miss', msg:'get('+key+') 未命中,返回 -1', dll:JSON.parse(JSON.stringify(dll)), map:JSON.parse(JSON.stringify(map)), capacity:capacity, op:op, hitVal:-1}); } } else if (putMatch) { var key = putMatch[1]; var val = putMatch[2]; if (key in map) { // update: move to front var idx = map[key]; dll[idx].val = val; var node = dll[idx]; dll.splice(idx, 1); dll.unshift(node); map = {}; for (var mi = 0; mi < dll.length; mi++) map[dll[mi].key] = mi; steps.push({stage:'update', msg:'put('+key+','+val+') 更新已有key,移到头部', dll:JSON.parse(JSON.stringify(dll)), map:JSON.parse(JSON.stringify(map)), capacity:capacity, op:op}); } else { if (dll.length >= capacity) { // evict LRU (tail) var evicted = dll.pop(); delete map[evicted.key]; steps.push({stage:'evict', msg:'put('+key+','+val+') 容量已满,淘汰尾部 key='+evicted.key+' (val='+evicted.val+')', dll:JSON.parse(JSON.stringify(dll)), map:JSON.parse(JSON.stringify(map)), capacity:capacity, op:op, evicted:evicted.key}); } dll.unshift({key:key, val:val}); map = {}; for (var mi = 0; mi < dll.length; mi++) map[dll[mi].key] = mi; steps.push({stage:'insert', msg:'put('+key+','+val+') 插入到头部', dll:JSON.parse(JSON.stringify(dll)), map:JSON.parse(JSON.stringify(map)), capacity:capacity, op:op}); } } } steps.push({stage:'done', msg:'操作完毕', dll:JSON.parse(JSON.stringify(dll)), map:JSON.parse(JSON.stringify(map)), capacity:capacity}); } function renderDLL(dll) { if (!dll || dll.length === 0) return '
[空]
'; var html = '
'; html += '
HEAD
'; for (var i = 0; i < dll.length; i++) { var n = dll[i]; var isHead = (i === 0); var isTail = (i === dll.length - 1); var bg = isHead ? 'var(--green-light)' : isTail ? 'var(--orange-light)' : 'var(--blue-light)'; var border = isHead ? 'var(--green)' : isTail ? 'var(--orange)' : 'var(--blue)'; html += '
'; html += '
k'+n.key+'
'; html += '
v'+n.val+'
'; html += '
'; if (i < dll.length - 1) html += '↔'; } html += '
TAIL
'; html += '
'; return html; } function render(step) { var s = steps[step]; var viz = '
当前操作:' + s.op + '
'; viz += renderDLL(s.dll); // Show map if (s.map && Object.keys(s.map).length > 0) { viz += '
HashMap:'; Object.keys(s.map).forEach(function(k) { viz += ''+k+'→idx'+s.map[k]+' '; }); viz += '
'; } viz += '
容量: ' + s.dll.length + '/' + s.capacity + '
'; $('vizArea').innerHTML = viz; $('detailContent').innerHTML = '
' + s.msg + '
'; if (s.hitVal !== undefined) { $('detailContent').innerHTML += '
返回值:' + s.hitVal + '
'; } if (s.stage === 'done') { $('resultContent').innerHTML = '
缓存最终状态:[' + s.dll.map(function(n){return n.key+':'+n.val;}).join(', ') + ']
'; } $('hintText').textContent = s.msg; setPipeline(['init\u2192初始化','hit\u2192命中','miss\u2192未命中','update\u2192更新','evict\u2192淘汰','insert\u2192插入','done\u2192完成'], s.stage); } function init() { var sel = $('exampleSelect'); examples.forEach(function(e,i){ sel.innerHTML += ''; }); $('inputArea').value = 'LRUCache(2) put(1,1) put(2,2) get(1) put(3,3) get(2)'; buildSteps(examples[0]); stepCtrl = new StepController({onStep:render}); stepCtrl.setSteps(steps.map(function(_,i){return i;})); $('stepInfo').textContent = '步骤 1 / ' + steps.length; stepCtrl.onStep = function(idx){ render(idx); $('stepInfo').textContent = '步骤 '+(idx+1)+' / '+steps.length; }; $('applyBtn').onclick = function(){ buildSteps(examples[parseInt($('exampleSelect').value)]); stepCtrl.setSteps(steps.map(function(_,i){return i;})); render(0); }; $('exampleSelect').onchange = function(){ buildSteps(examples[parseInt($('exampleSelect').value)]); stepCtrl.setSteps(steps.map(function(_,i){return i;})); render(0); }; $('prevBtn').onclick = function(){ stepCtrl.prev(); }; $('nextBtn').onclick = function(){ stepCtrl.next(); }; $('jumpBtn').onclick = function(){ stepCtrl.jumpToEnd(); }; $('autoBtn').onclick = function(){ var on=stepCtrl.toggleAuto(); $('autoBtn').textContent=on?'暂停':'自动播放'; }; $('resetBtn').onclick = function(){ stepCtrl.reset(); $('autoBtn').textContent='自动播放'; }; } init(); $('codeArea').innerHTML = renderCode('class LRUCache:\n def __init__(self, capacity):\n self.cap = capacity\n self.cache = {}\n self.head = DLinkedNode()\n self.tail = DLinkedNode()\n self.head.next = self.tail\n self.tail.prev = self.head\n\n def _remove(self, node):\n node.prev.next = node.next\n node.next.prev = node.prev\n\n def _add_to_head(self, node):\n node.prev = self.head\n node.next = self.head.next\n self.head.next.prev = node\n self.head.next = node\n\n def get(self, key):\n if key not in self.cache:\n return -1\n node = self.cache[key]\n self._remove(node)\n self._add_to_head(node)\n return node.val\n\n def put(self, key, value):\n if key in self.cache:\n node = self.cache[key]\n node.val = value\n self._remove(node)\n self._add_to_head(node)\n else:\n if len(self.cache) >= self.cap:\n tail = self.tail.prev\n self._remove(tail)\n del self.cache[tail.key]\n node = DLinkedNode(key, value)\n self.cache[key] = node\n self._add_to_head(node)', {lang:'Python'}); '''