Files

242 lines
10 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>068. 寻找两个正序数组的中位数 – 图解</title>
<link rel="stylesheet" href="../shared/style.css">
<style>
.vis-area { min-height: 120px; padding: 16px 0; }
.code-section { margin-top: 16px; }
.cut-line { display:inline-block; width:3px; height:36px; background:var(--red); margin:0 2px; vertical-align:bottom; border-radius:2px; position:relative; }
.cut-label { position:absolute; top:-16px; left:50%; transform:translateX(-50%); font-size:10px; color:var(--red); font-weight:700; white-space:nowrap; }
.side-label { display:inline-block; padding:2px 8px; border-radius:6px; font-size:12px; font-weight:600; margin:0 4px; }
.side-label.left-side { background:#dbeafe; color:#1e40af; }
.side-label.right-side { background:#fef3c7; color:#92400e; }
</style>
</head>
<body>
<div class="container">
<h1>🔴 068. 寻找两个正序数组的中位数 <span class="badge hard">困难</span></h1>
<p class="subtitle">分类:二分查找 | LeetCode Hot 100</p>
<div class="controls" id="controls">
<label for="inputArea">输入:</label>
<input type="text" id="inputArea" placeholder="nums1=[1,3], nums2=[2]">
<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() {
const examples = [
{nums1:[1,3], nums2:[2], label:'示例1: [1,3]+[2]'},
{nums1:[1,2], nums2:[3,4], label:'示例2: [1,2]+[3,4]'},
{nums1:[1], nums2:[2,3,4,5,6], label:'示例3: [1]+[2,3,4,5,6]'},
{nums1:[], nums2:[1], label:'示例4: []+[1]'},
];
let A, B, steps, stepCtrl;
function buildSteps(a, b) {
// Ensure A is the shorter array
if (a.length > b.length) { [a, b] = [b, a]; }
A = a; B = b;
steps = [];
const m = a.length, n = b.length;
const halfLen = Math.floor((m + n + 1) / 2);
steps.push({stage:'init', msg:`确保较短的数组为A(长度${m}),较长为B(长度${n}),halfLen = ⌊(${m}+${n}+1)/2⌋ = ${halfLen}`, iLeft:0, iRight:m, iA:-1, iB:-1, leftMax:null, rightMin:null, valid:false});
let iLeft = 0, iRight = m;
while (iLeft <= iRight) {
const iA = Math.floor((iLeft + iRight) / 2);
const iB = halfLen - iA;
const Aleft = iA > 0 ? a[iA - 1] : -Infinity;
const Aright = iA < m ? a[iA] : Infinity;
const Bleft = iB > 0 ? b[iB - 1] : -Infinity;
const Bright = iB < n ? b[iB] : Infinity;
steps.push({stage:'calc', msg:`iA=${iA}, iB=${iB} → A_left=${Aleft}, A_right=${Aright}, B_left=${Bleft}, B_right=${Bright}`,
iLeft, iRight, iA, iB, Aleft, Aright, Bleft, Bright, leftMax:null, rightMin:null, valid:false});
if (Aleft <= Bright && Bleft <= Aright) {
const leftMax = Math.max(Aleft, Bleft);
const rightMin = Math.min(Aright, Bright);
steps.push({stage:'found', msg:`A_left(${Aleft}) ≤ B_right(${Bright}) ✓ 且 B_left(${Bleft}) ≤ A_right(${Aright}) ✓ → 切割有效!`,
iLeft, iRight, iA, iB, Aleft, Aright, Bleft, Bright, leftMax, rightMin, valid:true});
return;
} else if (Aleft > Bright) {
steps.push({stage:'goLeft', msg:`A_left(${Aleft}) > B_right(${Bright}),A的切割太靠右,iRight = ${iA}-1 = ${iA-1}`,
iLeft, iRight:iA-1, iA, iB, Aleft, Aright, Bleft, Bright, leftMax:null, rightMin:null, valid:false});
iRight = iA - 1;
} else {
steps.push({stage:'goRight', msg:`B_left(${Bleft}) > A_right(${Aright}),A的切割太靠左,iLeft = ${iA}+1 = ${iA+1}`,
iLeft:iA+1, iRight, iA, iB, Aleft, Aright, Bleft, Bright, leftMax:null, rightMin:null, valid:false});
iLeft = iA + 1;
}
}
}
function renderCutArray(arr, cutIdx, label) {
// Use renderArray from shared lib for the base display, then overlay cut info
if (arr.length === 0) return `<div style="margin:8px 0;"><b>${label}:</b> [空]</div>`;
const hl = {};
for (let i = 0; i < cutIdx; i++) hl[i] = 'blue';
for (let i = cutIdx; i < arr.length; i++) hl[i] = 'orange';
let html = `<div style="margin:8px 0;"><b>${label}:</b> `;
html += renderArray(arr, {highlights: hl});
if (cutIdx > 0 && cutIdx <= arr.length) {
html += `<span style="color:var(--red);font-weight:700;font-size:12px;margin-left:4px;">↑ cut at ${cutIdx}</span>`;
}
html += '</div>';
return html;
}
function render(step) {
const s = steps[step];
let viz = '';
viz += renderCutArray(A, s.iA >= 0 ? s.iA : 0, 'A (较短)');
viz += renderCutArray(B, s.iB >= 0 ? s.iB : 0, 'B (较长)');
if (s.Aleft !== undefined) {
viz += `<div style="margin-top:10px;">`;
viz += `<span class="side-label left-side">左侧: A[${s.iA-1}]=${s.Aleft}, B[${s.iB-1}]=${s.Bleft}</span>`;
viz += `<span class="side-label right-side">右侧: A[${s.iA}]=${s.Aright}, B[${s.iB}]=${s.Bright}</span>`;
viz += `</div>`;
}
if (s.valid) {
const totalLen = A.length + B.length;
const isOdd = totalLen % 2 === 1;
const median = isOdd ? s.leftMax : (s.leftMax + s.rightMin) / 2;
viz += `<div class="current-answer">✅ 切割有效!left_max = ${s.leftMax}, right_min = ${s.rightMin}<br>中位数 = ${median}</div>`;
}
viz += `<div style="margin-top:6px;font-size:13px;color:var(--text-secondary);">iA = ${s.iA} iB = ${s.iB} 搜索范围: [${s.iLeft}..${s.iRight}]</div>`;
$('vizArea').innerHTML = viz;
$('detailContent').innerHTML = `<div class="calc-block">${s.msg}</div>`;
if (s.valid) {
const totalLen = A.length + B.length;
const isOdd = totalLen % 2 === 1;
const median = isOdd ? s.leftMax : (s.leftMax + s.rightMin) / 2;
$('resultContent').innerHTML = `<div class="final-answer">中位数 = <b>${median}</b><br>
总长度 ${totalLen}(${isOdd?'奇数':'偶数'})<br>
${isOdd ? `left_max = ${s.leftMax}` : `(left_max + right_min) / 2 = (${s.leftMax} + ${s.rightMin}) / 2 = ${median}`}<br>
时间复杂度 O(log(min(m,n)))</div>`;
}
$('hintText').textContent = s.msg;
const stages = ['init→初始化','calc→计算切割','goLeft→左缩','goRight→右缩','found→找到'];
$('pipeline').innerHTML = stages.map(st => {
const [k,l] = st.split('→');
return `<span class="pipe-step ${s.stage===k?'active':''}">${l}</span>`;
}).join('<i>→</i>');
}
function init() {
const sel = $('exampleSelect');
examples.forEach((e,i) => { sel.innerHTML += `<option value="${i}">${e.label}</option>`; });
$('inputArea').value = 'nums1=[1,3], nums2=[2]';
buildSteps(examples[0].nums1, examples[0].nums2);
stepCtrl = new StepController({onStep: render});
stepCtrl.setSteps(steps.map((_,i)=>i));
$('stepInfo').textContent = `步骤 1 / ${steps.length}`;
stepCtrl.onStep = (idx) => { render(idx); $('stepInfo').textContent = `步骤 ${idx+1} / ${steps.length}`; };
$('applyBtn').onclick = () => {
try {
const m = $('inputArea').value.match(/nums1=\[([^\]]*)\].*nums2=\[([^\]]*)\]/);
if (!m) { alert('格式: nums1=[1,3], nums2=[2]'); return; }
const a = m[1] ? m[1].split(',').map(Number) : [];
const b = m[2] ? m[2].split(',').map(Number) : [];
buildSteps(a, b);
stepCtrl.setSteps(steps.map((_,i)=>i)); render(0);
$('stepInfo').textContent = `步骤 1 / ${steps.length}`;
} catch(e) { alert('输入格式错误'); }
};
$('exampleSelect').onchange = () => {
const e = examples[parseInt($('exampleSelect').value)];
$('inputArea').value = `nums1=[${e.nums1}], nums2=[${e.nums2}]`;
buildSteps(e.nums1, e.nums2);
stepCtrl.setSteps(steps.map((_,i)=>i)); render(0);
};
$('prevBtn').onclick = () => stepCtrl.prev();
$('nextBtn').onclick = () => stepCtrl.next();
$('jumpBtn').onclick = () => stepCtrl.jumpToEnd();
$('autoBtn').onclick = () => { const on = stepCtrl.toggleAuto(); $('autoBtn').textContent = on ? '暂停' : '自动播放'; };
$('resetBtn').onclick = () => { stepCtrl.reset(); $('autoBtn').textContent = '自动播放'; };
}
init();
$('codeArea').innerHTML = renderCode(`def findMedianSortedArrays(nums1, nums2):
# 确保 A 是较短的数组
if len(nums1) > len(nums2):
nums1, nums2 = nums2, nums1
m, n = len(nums1), len(nums2)
half = (m + n + 1) // 2
l, r = 0, m
while l <= r:
i = (l + r) // 2
j = half - i
Aleft = nums1[i-1] if i > 0 else float('-inf')
Aright = nums1[i] if i < m else float('inf')
Bleft = nums2[j-1] if j > 0 else float('-inf')
Bright = nums2[j] if j < n else float('inf')
if Aleft <= Bright and Bleft <= Aright:
if (m + n) % 2:
return max(Aleft, Bleft)
return (max(Aleft, Bleft) + min(Aright, Bright)) / 2
elif Aleft > Bright:
r = i - 1
else:
l = i + 1`, {lang:'Python'});
})();
</script>
</body>
</html>