Files

583 lines
22 KiB
HTML
Raw Permalink Normal View History

<!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>