Files
2026-08-24 04:35:13 +00:00

583 lines
22 KiB
HTML
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
<!DOCTYPE html>
<html lang="zh-Hans">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>022. 相交链表 – 图解</title>
<link rel="stylesheet" href="../shared/style.css">
<style>
.vis-area{min-height:200px;padding:16px 0}
.code-section{margin-top:16px}
/* ---- Y-shape layout ---- */
.y-list{display:inline-flex;flex-direction:column;padding:8px 0;gap:0}
.y-row{display:flex;align-items:center;gap:0;flex-wrap:nowrap;min-height:54px}
.y-head{font-size:13px;font-weight:700;min-width:60px;text-align:right;margin-right:6px;white-space:nowrap;flex-shrink:0}
.y-head.ha{color:var(--blue)}
.y-head.hb{color:var(--orange)}
.y-spacer{flex-shrink:0}
.y-conn{font-size:22px;color:var(--purple);font-weight:700;flex-shrink:0;margin:0 4px;line-height:1}
/* Node wrapper for pointer labels */
.nw{position:relative;display:inline-flex;flex-direction:column;align-items:center;flex-shrink:0}
.nw .ptr{display:flex;flex-direction:column;align-items:center;font-size:10px;font-weight:700;margin-top:2px;line-height:1.2;gap:1px}
.nw .ptr-pa{color:var(--blue)}
.nw .ptr-pb{color:var(--orange)}
.nw .ptr-ph{height:14px}
/* Shared / intersection highlight */
.ll-node.is-intersect{border-color:var(--purple);box-shadow:0 0 0 3px rgba(139,92,246,.22)}
.ll-node.is-intersect .val{background:#ede9fe;color:var(--purple-dark)}
.ll-node.is-shared{border-color:#a78bfa}
.ll-node.is-shared .val{background:#f5f3ff;color:#6d28d9}
.ll-node.is-meet{border-color:var(--green)!important;box-shadow:0 0 0 4px rgba(22,163,74,.25)!important}
.ll-node.is-meet .val{background:#dcfce7!important;color:var(--green-dark)!important}
/* null indicator in Y-list */
.null-terminal{display:inline-flex;align-items:center;flex-shrink:0}
/* ---- Walk tracker ---- */
.walk-wrap{margin-top:18px;padding:12px;background:#f8fafc;border-radius:12px;border:1px solid var(--border);overflow-x:auto}
.walk-title{font-size:13px;font-weight:600;color:var(--text-secondary);margin-bottom:8px}
.walk-row{display:flex;align-items:center;gap:0;margin:6px 0;flex-wrap:nowrap}
.walk-tag{font-size:12px;font-weight:700;min-width:26px;margin-right:6px;flex-shrink:0}
.walk-tag.wta{color:var(--blue)}
.walk-tag.wtb{color:var(--orange)}
.wc{
display:inline-flex;align-items:center;justify-content:center;
min-width:30px;height:26px;padding:1px 5px;border-radius:6px;
font-size:11px;font-weight:600;font-family:monospace;transition:all .2s;
background:#e2e8f0;color:var(--text);margin:0 1px;
}
.wc.wc-null{background:#f1f5f9;color:var(--text-muted);font-style:italic}
.wc.wc-sh{border-bottom:2px solid var(--purple)}
.wc.wc-sw{border-top:2px dashed #94a3b8}
.wc.wc-a{box-shadow:0 0 0 2px var(--blue);background:#dbeafe;color:var(--blue-dark)}
.wc.wc-b{box-shadow:0 0 0 2px var(--orange);background:#ffedd5;color:#9a3412}
.wc.wc-meet{box-shadow:0 0 0 2px var(--green);background:#bbf7d0;color:var(--green-dark);font-weight:800}
.ws{font-size:9px;color:var(--purple);font-weight:700;margin:0 2px}
/* ---- Info chips ---- */
.info-row{display:flex;gap:10px;flex-wrap:wrap;margin:10px 0 4px;font-size:13px}
.ic{padding:3px 10px;border-radius:999px;font-weight:600;font-size:12px}
.ic-a{background:#dbeafe;color:var(--blue-dark)}
.ic-b{background:#ffedd5;color:#9a3412}
.ic-s{background:#ede9fe;color:var(--purple-dark)}
.ic-g{background:#dcfce7;color:var(--green-dark)}
</style>
</head>
<body>
<div class="container">
<h1>🟢 022. 相交链表 <span class="badge easy">简单</span></h1>
<p class="subtitle">分类:链表 | LeetCode 160 · Hot 100 #022</p>
<div class="controls" id="controls">
<label for="inputArea">输入:</label>
<input type="text" id="inputArea" placeholder="listA=[4,1,8,4,5], listB=[5,6,1,8,4,5], intersect=8">
<button id="applyBtn" class="primary">生成图解</button>
<select id="exampleSelect"></select>
<span style="flex:1"></span>
<button id="prevBtn">◀ 上一步</button>
<button id="nextBtn">下一步 ▶</button>
<button id="jumpBtn">⏭ 跳到结果</button>
<button id="autoBtn">自动播放</button>
<button id="resetBtn">重置</button>
</div>
<div class="pipeline" id="pipeline"></div>
<div class="hint info" id="hintBox">
<span id="stepInfo"></span><br>
<span id="hintText"></span>
</div>
<div class="panels">
<div class="panel" id="mainPanel">
<h3>📊 可视化</h3>
<div class="vis-area" id="vizArea">点击「生成图解」开始</div>
</div>
<div class="panel-grid">
<div class="panel" id="detailPanel">
<h3>📝 当前步骤详情</h3>
<div id="detailContent">等待开始...</div>
</div>
<div class="panel" id="resultPanel">
<h3>✅ 结果</h3>
<div id="resultContent">等待完成...</div>
</div>
</div>
</div>
<div class="panel code-section">
<h3>💻 参考代码(Python)</h3>
<div id="codeArea"></div>
</div>
<footer>Powered by QwenPaw · 图解算法 · LeetCode Hot 100</footer>
</div>
<script src="../shared/algo-viz.js"></script>
<script>
"use strict";
(function(){
/* ================================================================
LeetCode 160 · 相交链表 · 双指针可视化
算法:pA 走 listA 再走 listB,pB 走 listB 再走 listA,
两者总步数相同 (m+n+2),必在交点或 null 相遇。
================================================================ */
var PIPE = ['init→初始化','step→步进','null→抵达末尾','switch→切换链表','meet→相遇'];
var examples = [
{la:[4,1,8,4,5], lb:[5,6,1,8,4,5], iv:8, lbl:'示例1: intersect=8'},
{la:[1,9,1,2,4], lb:[3,2,4], iv:2, lbl:'示例2: intersect=2'},
{la:[2,6,4], lb:[1,5], iv:null, lbl:'示例3: 不相交'}
];
var gLA, gLB, gSA, gSB, gHI;
var steps, ctrl;
// ────────── parse & compute ──────────
function parseInput(s){
var a=s.match(/listA=\[([^\]]*)\]/), b=s.match(/listB=\[([^\]]*)\]/), v=s.match(/intersect=(\S+)/);
if(!a||!b) return null;
return {
la: a[1]?a[1].split(',').map(Number):[],
lb: b[1]?b[1].split(',').map(Number):[],
iv: v?(v[1]==='null'||v[1]==='0'?null:Number(v[1])):null
};
}
function calcSkips(la,lb,iv){
if(iv==null) return {sa:la.length,sb:lb.length,hi:false};
var sa=-1;
for(var i=0;i<la.length;i++) if(la[i]===iv){sa=i;break;}
if(sa===-1) return {sa:la.length,sb:lb.length,hi:false};
var tail=la.slice(sa), sb=-1;
for(var s=0;s<=lb.length-tail.length;s++){
var ok=true;
for(var j=0;j<tail.length;j++) if(lb[s+j]!==tail[j]){ok=false;break;}
if(ok){sb=s;break;}
}
if(sb===-1) return {sa:la.length,sb:lb.length,hi:false};
return {sa:sa,sb:sb,hi:true};
}
function nid(list,idx,skip,pfx){
if(idx>=list.length) return 'null';
if(gHI && idx>=skip) return 's'+(idx-skip);
return pfx+idx;
}
// ────────── build walk sequences ──────────
function mkWalk(list,len,skip,pfx,otherList,otherLen,otherSkip,otherPfx){
var arr=[];
for(var i=0;i<len;i++)
arr.push({L:list===gLA?'A':'B',i:i,n:nid(list,i,skip,pfx),v:list[i],nu:false,sw:false});
arr.push({L:list===gLA?'A':'B',i:len,n:'null',v:null,nu:true,sw:false});
var swAt=arr.length-1;
var oL=otherList===gLA?'A':'B';
for(var i=0;i<otherLen;i++)
arr.push({L:oL,i:i,n:nid(otherList,i,otherSkip,otherPfx),v:otherList[i],nu:false,sw:true});
arr.push({L:oL,i:otherLen,n:'null2',v:null,nu:true,sw:true});
return {arr:arr,swAt:swAt};
}
// ────────── step generation ──────────
function buildSteps(){
steps=[];
var la=gLA,lb=gLB,sA=gSA,sB=gSB,hi=gHI;
var wA=mkWalk(la,la.length,sA,'a', lb,lb.length,sB,'b');
var wB=mkWalk(lb,lb.length,sB,'b', la,la.length,sA,'a');
// Step 0: init
var a0=wA.arr[0],b0=wB.arr[0];
steps.push({
stg:'init', wi:0, pa:a0, pb:b0,
msg:'初始化:pA = headA → node '+a0.n+'(val='+a0.v+'),pB = headB → node '+b0.n+'(val='+b0.v+')',
cl:4
});
if(a0.n===b0.n){steps[0].stg='meet';steps[0].msg='两指针从同一节点出发 → 直接相遇';return steps;}
var maxW=Math.max(wA.arr.length,wB.arr.length);
for(var w=1;w<maxW;w++){
if(w>=wA.arr.length||w>=wB.arr.length) break;
var a=wA.arr[w], b=wB.arr[w];
var pa=wA.arr[w-1], pb=wB.arr[w-1];
var met=(a.n===b.n);
var aRn=a.nu&&!pa.nu; // pA just reached null
var bRn=b.nu&&!pb.nu;
var aSw=a.sw&&!pa.sw; // pA just switched lists
var bSw=b.sw&&!pb.sw;
var stg,msg,cl;
if(met){
stg='meet'; cl=7;
msg=a.nu
?'pA 和 pB 都到达 null → 两链表不相交,返回 null'
:'pA 和 pB 在 node '+a.n+'(val='+a.v+') 相遇 → 找到交点!';
} else if(aRn&&bRn){
stg='null'; cl=5;
msg='pA 抵达 listA 末尾(null),pB 抵达 listB 末尾(null)';
} else if(aRn&&bSw){
stg='null'; cl=5;
msg='pA 抵达 '+a.L+' 末尾(null),pB 从 null 切换到 head'+
(b.L==='A'?'A':'B')+'(node '+b.n+', val='+b.v+')';
} else if(bRn&&aSw){
stg='null'; cl=6;
msg='pB 抵达 '+b.L+' 末尾(null),pA 从 null 切换到 head'+
(a.L==='B'?'B':'A')+'(node '+a.n+', val='+a.v+')';
} else if(aRn){
stg='null'; cl=5;
msg='pA 抵达 '+pa.L+' 末尾(null),pB 在 '+b.L+'['+b.i+']=node '+b.n+'(val='+b.v+')';
} else if(bRn){
stg='null'; cl=6;
msg='pB 抵达 '+pb.L+' 末尾(null),pA 在 '+a.L+'['+a.i+']=node '+a.n+'(val='+a.v+')';
} else if(aSw&&bSw){
stg='switch'; cl=5;
msg='pA 切换到 headB(node '+a.n+', val='+a.v+'),pB 切换到 headA(node '+b.n+', val='+b.v+')';
} else if(aSw){
stg='switch'; cl=5;
msg='pA 从 null 切换到 headB(node '+a.n+', val='+a.v+'),pB 走到 '+b.L+'['+b.i+']=node '+b.n+'(val='+b.v+')';
} else if(bSw){
stg='switch'; cl=6;
msg='pB 从 null 切换到 headA(node '+b.n+', val='+b.v+'),pA 走到 '+a.L+'['+a.i+']=node '+a.n+'(val='+a.v+')';
} else {
stg='step'; cl=5;
msg='pA: '+a.L+'['+a.i+']='+a.v+' (node '+a.n+'),pB: '+b.L+'['+b.i+']='+b.v+' (node '+b.n+'),pA ≠ pB';
}
steps.push({stg:stg,wi:w,pa:a,pb:b,met:met,msg:msg,cl:cl});
if(stg==='meet') break;
}
return steps;
}
// ────────── render: node helper ──────────
function rNode(val, cls, ptrs, arrowOverride){
var html='<div class="nw">';
html+='<div class="ll-node '+cls+'"><span class="val">'+val+'</span>';
html+='<span class="arrow">'+(arrowOverride||'→')+'</span></div>';
if(ptrs&&ptrs.length){
html+='<div class="ptr">';
for(var p=0;p<ptrs.length;p++){
html+=ptrs[p]==='pA'
?'<span class="ptr-pa">▼pA</span>'
:'<span class="ptr-pb">▼pB</span>';
}
html+='</div>';
} else {
html+='<div class="ptr-ph"></div>';
}
html+='</div>';
return html;
}
// ────────── render: Y-shape linked list ──────────
function renderYList(pa,pb){
var la=gLA,lb=gLB,sA=gSA,sB=gSB,hi=gHI;
var aExcl=la.slice(0,sA), bExcl=lb.slice(0,sB), shared=hi?la.slice(sA):[];
if(!hi){
// ── No intersection: two complete rows ──
return renderTwoRows(la,lb,pa,pb);
}
// ── Y-shape: three segments ──
var unitPx=104, headPx=66;
var aBW=headPx+aExcl.length*unitPx;
var bBW=headPx+bExcl.length*unitPx;
var maxBW=Math.max(aBW,bBW);
var sharedML=maxBW+10;
var aPad=Math.max(0,bBW-aBW);
var bPad=Math.max(0,aBW-bBW);
var h='<div class="y-list">';
// ── Branch A ──
h+='<div class="y-row">';
h+='<span class="y-head ha">headA →</span>';
for(var i=0;i<aExcl.length;i++){
var nd='a'+i;
var isPA=pa&&!pa.nu&&pa.L==='A'&&pa.i===i;
var isPB=pb&&!pb.nu&&pb.L==='A'&&pb.i===i&&pb.n===nd;
var ptrs=[]; if(isPA) ptrs.push('pA'); if(isPB) ptrs.push('pB');
h+=rNode(aExcl[i],isPA||isPB?'current':'',ptrs);
}
if(aPad>0) h+='<span class="y-spacer" style="width:'+aPad+'px"></span>';
h+='<span class="y-conn">↘</span>';
h+='</div>';
// ── Shared row ──
h+='<div class="y-row" style="margin-left:'+sharedML+'px">';
for(var i=0;i<shared.length;i++){
var nd='s'+i;
var isPA=pa&&!pa.nu&&pa.n===nd;
var isPB=pb&&!pb.nu&&pb.n===nd;
var cls=i===0?'is-intersect':'is-shared';
if(isPA&&isPB) cls+=' is-meet';
else if(isPA||isPB) cls+=' current';
var ptrs=[]; if(isPA) ptrs.push('pA'); if(isPB) ptrs.push('pB');
var isLast=i===shared.length-1;
h+=rNode(shared[i],cls,ptrs,isLast?'∅':'→');
}
if(shared.length===0){
h+='<div class="nw"><div class="ll-node null-node"><span class="val">∅</span></div><div class="ptr-ph"></div></div>';
}
h+='</div>';
// ── Branch B ──
h+='<div class="y-row">';
h+='<span class="y-head hb">headB →</span>';
for(var i=0;i<bExcl.length;i++){
var nd='b'+i;
var isPA=pa&&!pa.nu&&pa.L==='B'&&pa.i===i&&pa.n===nd;
var isPB=pb&&!pb.nu&&pb.L==='B'&&pb.i===i&&pb.n===nd;
var ptrs=[]; if(isPA) ptrs.push('pA'); if(isPB) ptrs.push('pB');
h+=rNode(bExcl[i],isPA||isPB?'current':'',ptrs);
}
if(bPad>0) h+='<span class="y-spacer" style="width:'+bPad+'px"></span>';
h+='<span class="y-conn">↗</span>';
h+='</div>';
h+='</div>';
return h;
}
// ── No intersection: two full rows ──
function renderTwoRows(la,lb,pa,pb){
var h='<div class="y-list">';
// ListA
h+='<div class="y-row">';
h+='<span class="y-head ha">headA →</span>';
for(var i=0;i<la.length;i++){
var isPA=pa&&!pa.nu&&pa.L==='A'&&pa.i===i;
var isPB=pb&&!pb.nu&&pb.L==='A'&&pb.i===i;
var ptrs=[]; if(isPA) ptrs.push('pA'); if(isPB) ptrs.push('pB');
h+=rNode(la[i],isPA||isPB?'current':'',ptrs,i===la.length-1?'∅':'→');
}
h+='</div>';
// ListB
h+='<div class="y-row" style="margin-top:12px">';
h+='<span class="y-head hb">headB →</span>';
for(var i=0;i<lb.length;i++){
var isPA=pa&&!pa.nu&&pa.L==='B'&&pa.i===i;
var isPB=pb&&!pb.nu&&pb.L==='B'&&pb.i===i;
var ptrs=[]; if(isPA) ptrs.push('pA'); if(isPB) ptrs.push('pB');
h+=rNode(lb[i],isPA||isPB?'current':'',ptrs,i===lb.length-1?'∅':'→');
}
h+='</div>';
h+='</div>';
return h;
}
// ────────── render: walk path tracker ──────────
function renderWalk(pa,pb,wi){
var la=gLA,lb=gLB,sA=gSA,sB=gSB,hi=gHI;
var wADat=mkWalk(la,la.length,sA,'a', lb,lb.length,sB,'b');
var wBDat=mkWalk(lb,lb.length,sB,'b', la,la.length,sA,'a');
var wA=wADat.arr, wB=wBDat.arr;
var swA=wADat.swAt, swB=wBDat.swAt;
function chip(c,i,tag){
var cls='wc';
if(c.nu) cls+=' wc-null';
if(!c.nu&&c.sh()) cls+=' wc-sh';
if(c.sw) cls+=' wc-sw';
if(i===wi){
var bothMet=pa&&pb&&pa.n===pb.n&&!pa.nu;
if(tag==='pA') cls+=bothMet?' wc-meet':' wc-a';
else cls+=bothMet?' wc-meet':' wc-b';
}
return '<span class="'+cls+'" title="node '+c.n+'">'+c.v+'</span>';
}
// add .sh() method to walk items
wA.forEach(function(x){ x.sh=function(){return hi&&x.n.charAt(0)==='s';}; });
wB.forEach(function(x){ x.sh=function(){return hi&&x.n.charAt(0)==='s';}; });
function row(arr,swAt,tag){
var h='<div class="walk-row"><span class="walk-tag '+(tag==='pA'?'wta':'wtb')+'">'+tag+'</span>';
for(var i=0;i<arr.length;i++){
if(i===swAt+1) h+='<span class="ws">│</span>';
h+=chip(arr[i],i,tag);
}
h+='</div>';
return h;
}
var h='<div class="walk-wrap">';
h+='<div class="walk-title">🚶 指针行走路径 <span style="margin-left:10px;font-size:11px;color:var(--text-muted)">│=切换链表分界 紫色下划线=共享节点</span></div>';
h+=row(wA,swA,'pA');
h+=row(wB,swB,'pB');
// Step counter
if(wi>0){
var totalA=la.length+1+lb.length+1, totalB=lb.length+1+la.length+1;
h+='<div style="margin-top:4px;font-size:11px;color:var(--text-secondary)">';
h+='pA 已走 '+Math.min(wi+1,totalA)+' / '+totalA+' 步';
h+=' &nbsp;·&nbsp; ';
var paOnB=wi>la.length;
var pbOnA=wi>lb.length;
if(paOnB) h+='<span style="color:var(--orange)">pA 在 listB 上</span>';
else h+='pA 在 listA 上';
h+=' &nbsp;·&nbsp; ';
if(pbOnA) h+='<span style="color:var(--blue)">pB 在 listA 上</span>';
else h+='pB 在 listB 上';
h+='</div>';
}
h+='</div>';
return h;
}
// ────────── render: info chips ──────────
function renderInfo(pa,pb){
var h='<div class="info-row">';
if(pa){
if(pa.nu) h+='<span class="ic ic-a">pA = null</span>';
else h+='<span class="ic ic-a">pA → '+pa.L+'['+pa.i+'] = node '+pa.n+'</span>';
}
if(pb){
if(pb.nu) h+='<span class="ic ic-b">pB = null</span>';
else h+='<span class="ic ic-b">pB → '+pb.L+'['+pb.i+'] = node '+pb.n+'</span>';
}
if(pa&&pb&&!pa.nu&&!pb.nu&&pa.n===pb.n) h+='<span class="ic ic-g">pA == pB ✓</span>';
if(pa&&pb&&pa.nu&&pb.nu) h+='<span class="ic ic-g">pA == pB (null) ✓</span>';
h+='</div>';
return h;
}
// ────────── pipeline ──────────
function setPipeline(cur){
$('pipeline').innerHTML=PIPE.map(function(s){
var p=s.split('→');
return '<span class="pipe-step '+(cur===p[0]?'active':'')+'">'+p[1]+'</span>';
}).join('<i>→</i>');
}
// ────────── main render ──────────
function render(idx){
if(!steps||!steps[idx]) return;
var s=steps[idx];
// Viz area
var viz=renderYList(s.pa,s.pb);
viz+=renderWalk(s.pa,s.pb,s.wi||0);
viz+=renderInfo(s.pa,s.pb);
$('vizArea').innerHTML=viz;
// Detail panel
var d='<div class="calc-block">'+s.msg+'</div>';
d+='<div style="margin-top:8px">';
d+=s.pa.nu?'<code>pA = null</code>':'<code>pA → '+s.pa.L+'['+s.pa.i+'] = node '+s.pa.n+'</code>';
d+=' &nbsp; ';
d+=s.pb.nu?'<code>pB = null</code>':'<code>pB → '+s.pb.L+'['+s.pb.i+'] = node '+s.pb.n+'</code>';
d+='</div>';
if(!s.pa.nu&&!s.pb.nu){
d+='<div style="margin-top:6px;font-size:13px">';
if(s.pa.n===s.pb.n)
d+='<span style="color:var(--green);font-weight:700">pA.nodeId === pB.nodeId → 指向同一内存节点!</span>';
else
d+='<span style="color:var(--red)">pA.nodeId ('+s.pa.n+') ≠ pB.nodeId ('+s.pb.n+') → 不同节点</span>';
d+='</div>';
}
if(s.stg==='step')
d+='<div style="margin-top:8px;font-size:12px;color:var(--text-secondary)">while pA ≠ pB → 继续前进:pA = pA.next, pB = pB.next</div>';
else if(s.stg==='null')
d+='<div style="margin-top:8px;font-size:12px;color:var(--text-secondary)">指针到达链表末尾 → 下一步将切换到另一链表头部 (pA→headB / pB→headA)</div>';
else if(s.stg==='switch')
d+='<div style="margin-top:8px;font-size:12px;color:var(--text-secondary)">走完一条链表后切换,保证两指针走的总步数相同 (lenA+lenB)</div>';
$('detailContent').innerHTML=d;
// Result panel
if(s.stg==='meet'){
if(s.pa.nu){
$('resultContent').innerHTML='<div class="final-answer" style="border-color:#f87171;background:#fef2f2">两链表不相交,返回 <b>null</b><br><span style="font-size:13px;color:#64748b">pA 和 pB 各走 lenA+lenB 步后在 null 处相遇</span><br><div class="complexity">时间 O(m+n) · 空间 O(1)</div></div>';
} else {
$('resultContent').innerHTML='<div class="final-answer">交点节点:<b>node '+s.pa.n+' (val='+s.pa.v+')</b><br><span style="font-size:13px;color:#64748b">经过 '+s.wi+' 步迭代后 pA == pB → 找到交点</span><br><div class="complexity">时间 O(m+n) · 空间 O(1)</div></div>';
}
} else {
$('resultContent').innerHTML='等待完成…';
}
// Hint & counters
$('hintText').textContent=s.msg;
$('stepInfo').textContent='步骤 '+(idx+1)+' / '+steps.length;
setPipeline(s.stg);
// Code highlight
$('codeArea').innerHTML=renderCode(PY,{highlightLines:s.cl?[s.cl]:[],lang:'Python'});
}
// ────────── Python code ──────────
var PY=[
'class Solution:',
' def getIntersectionNode(self, headA, headB):',
' if not headA or not headB:',
' return None',
' pA, pB = headA, headB',
' while pA != pB:',
' pA = pA.next if pA else headB',
' pB = pB.next if pB else headA',
' return pA'
].join('\n');
// ────────── init ──────────
function loadExample(idx){
var e=examples[idx];
gLA=e.la.slice(); gLB=e.lb.slice();
var sk=calcSkips(gLA,gLB,e.iv);
gSA=sk.sa; gSB=sk.sb; gHI=sk.hi;
$('inputArea').value='listA=['+gLA.join(',')+'], listB=['+gLB.join(',')+'], intersect='+(e.iv===null?'null':e.iv);
}
function init(){
var sel=$('exampleSelect');
examples.forEach(function(e,i){ sel.innerHTML+='<option value="'+i+'">'+e.lbl+'</option>'; });
loadExample(0);
buildSteps();
ctrl=new StepController({onStep:render,autoInterval:700});
ctrl.setSteps(steps.map(function(_,i){return i;}));
$('applyBtn').onclick=function(){
var p=parseInput($('inputArea').value);
if(!p){alert('格式: listA=[4,1,8,4,5], listB=[5,6,1,8,4,5], intersect=8');return;}
gLA=p.la; gLB=p.lb;
var sk=calcSkips(gLA,gLB,p.iv); gSA=sk.sa; gSB=sk.sb; gHI=sk.hi;
buildSteps(); ctrl.setSteps(steps.map(function(_,i){return i;})); render(0);
};
$('exampleSelect').onchange=function(){
loadExample(parseInt($('exampleSelect').value));
buildSteps(); ctrl.setSteps(steps.map(function(_,i){return i;})); render(0);
$('autoBtn').textContent='自动播放';
};
$('prevBtn').onclick=function(){ctrl.prev();};
$('nextBtn').onclick=function(){ctrl.next();};
$('jumpBtn').onclick=function(){ctrl.jumpToEnd();};
$('autoBtn').onclick=function(){var on=ctrl.toggleAuto();$('autoBtn').textContent=on?'暂停':'自动播放';};
$('resetBtn').onclick=function(){ctrl.reset();$('autoBtn').textContent='自动播放';};
render(0);
}
init();
})();
</script>
</body>
</html>