"""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 += '
';
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 += '
';
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 += '