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

290 lines
12 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>075. 前 K 个高频元素 – 图解</title>
<link rel="stylesheet" href="../shared/style.css">
<style>
.vis-area { min-height: 120px; padding: 16px 0; }
.code-section { margin-top: 16px; }
.freq-table { border-collapse:collapse; margin:8px 0; }
.freq-table th, .freq-table td { border:1px solid var(--border); padding:4px 12px; text-align:center; font-size:14px; }
.freq-table th { background:#f8fafc; color:var(--text-secondary); font-weight:600; }
.freq-table td.active-cell { background:#fef3c7; color:#92400e; font-weight:700; }
.freq-table td.in-heap { background:#dbeafe; color:#1e40af; }
.freq-table td.ejected { background:#fee2e2; color:#991b1b; opacity:0.5; text-decoration:line-through; }
.freq-table td.accepted { background:#dcfce7; color:#166534; font-weight:700; }
.heap-row { display:flex; justify-content:center; gap:6px; margin:6px 0; flex-wrap:wrap; }
.heap-cell {
min-width:48px; height:32px; display:inline-flex; align-items:center; justify-content:center;
border-radius:8px; font-weight:700; font-size:13px; font-family:monospace;
transition: all 0.25s ease; padding:2px 8px;
}
.heap-cell.in-heap { background:#dbeafe; color:#1e40af; border:1px solid #3b82f6; }
.heap-cell.top { background:#fef3c7; color:#92400e; border:2px solid #f59e0b; box-shadow:0 0 0 3px rgba(245,158,11,0.2); }
.heap-cell.new { background:#dcfce7; color:#166534; border:2px solid #16a34a; }
</style>
</head>
<body>
<div class="container">
<h1>🟡 075. 前 K 个高频元素 <span class="badge medium">中等</span></h1>
<p class="subtitle">分类:堆 | LeetCode Hot 100</p>
<div class="controls" id="controls">
<label for="inputArea">输入:</label>
<input type="text" id="inputArea" value="nums=[1,1,1,2,2,3], k=2" placeholder="nums=[...], k=N">
<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 = [
{nums:[1,1,1,2,2,3], k:2, label:'示例1: [1,1,1,2,2,3], k=2'},
{nums:[1], k:1, label:'示例2: [1], k=1'},
{nums:[4,1,-1,2,-1,2,3], k:2, label:'示例3: [4,1,-1,2,-1,2,3], k=2'},
];
let nums, k, steps, stepCtrl;
function heapPush(heap, item) {
// item = [freq, val], min-heap by freq
heap.push(item);
let i = heap.length - 1;
while (i > 0) {
const p = (i - 1) >> 1;
if (heap[p][0] > heap[i][0]) { [heap[p], heap[i]] = [heap[i], heap[p]]; i = p; }
else break;
}
}
function heapPop(heap) {
const top = heap[0];
const last = heap.pop();
if (heap.length) {
heap[0] = last;
let i = 0;
while (true) {
let s = i; const l = 2*i+1, r = 2*i+2;
if (l < heap.length && heap[l][0] < heap[s][0]) s = l;
if (r < heap.length && heap[r][0] < heap[s][0]) s = r;
if (s !== i) { [heap[i], heap[s]] = [heap[s], heap[i]]; i = s; }
else break;
}
}
return top;
}
function buildSteps(arr, kVal) {
nums = arr.slice(); k = kVal;
steps = [];
// Step 1: Count frequencies
const freq = {};
arr.forEach(n => freq[n] = (freq[n] || 0) + 1);
const entries = Object.entries(freq).map(([v,f]) => [parseInt(v), f]).sort((a,b) => b[1] - a[1]);
steps.push({stage:'start', msg:`开始统计每个元素出现频率,共 ${entries.length} 种不同元素`, freq, entries, heap:[], currentVal:null, ejected:null, heapSet:new Set()});
steps.push({stage:'count', msg:`频率统计完成!共 ${entries.length} 种元素,需取前 ${k} 高频`, freq, entries, heap:[], currentVal:null, ejected:null, heapSet:new Set()});
// Step 2: Build initial min-heap with first k entries
const heap = [];
const heapSet = new Set();
for (let i = 0; i < Math.min(kVal, entries.length); i++) {
const [val, cnt] = entries[i];
heapPush(heap, [cnt, val]);
heapSet.add(val);
steps.push({stage:'build', msg:`将元素 ${val}(频率=${cnt}) 加入小顶堆`, freq, entries, heap:heap.map(h=>h.slice()), currentVal:val, ejected:null, heapSet:new Set(heapSet)});
}
if (entries.length <= kVal) {
steps.push({stage:'done', msg:`元素种类 ≤ k,全部入选。结果:[${entries.map(e=>e[0]).join(',')}]`, freq, entries, heap:heap.map(h=>h.slice()), currentVal:null, ejected:null, heapSet:new Set(heapSet), result:entries.map(e=>e[0])});
return;
}
// Step 3: Process remaining entries (already sorted by freq desc, so they all have lower freq)
for (let i = kVal; i < entries.length; i++) {
const [val, cnt] = entries[i];
const topFreq = heap[0][0];
steps.push({stage:'compare', msg:`检查元素 ${val}(频率=${cnt}),堆顶频率=${topFreq}`, freq, entries, heap:heap.map(h=>h.slice()), currentVal:val, ejected:null, heapSet:new Set(heapSet)});
if (cnt > topFreq) {
const ejected = heapPop(heap);
heapSet.delete(ejected[1]);
heapPush(heap, [cnt, val]);
heapSet.add(val);
steps.push({stage:'replace', msg:`${cnt} > ${topFreq},弹出 ${ejected[1]}(freq=${ejected[0]}),压入 ${val}(freq=${cnt})`, freq, entries, heap:heap.map(h=>h.slice()), currentVal:val, ejected:ejected[1], heapSet:new Set(heapSet)});
} else {
steps.push({stage:'skip', msg:`${cnt} ≤ ${topFreq},跳过`, freq, entries, heap:heap.map(h=>h.slice()), currentVal:val, ejected:null, heapSet:new Set(heapSet)});
}
}
const result = heap.map(h => h[1]);
steps.push({stage:'done', msg:`遍历结束,堆中 ${k} 个元素即为前 K 高频`, freq, entries, heap:heap.map(h=>h.slice()), currentVal:null, ejected:null, heapSet:new Set(heapSet), result});
}
function renderFreqTable(freq, entries, heapSet, currentVal, ejected) {
let html = '<table class="freq-table"><tr><th>元素</th>';
entries.forEach(([val]) => {
let cls = '';
if (val === currentVal) cls = 'active-cell';
else if (ejected !== null && val === ejected) cls = 'ejected';
else if (heapSet.has(val)) cls = 'in-heap';
html += `<td class="${cls}">${val}</td>`;
});
html += '</tr><tr><th>频率</th>';
entries.forEach(([val, cnt]) => {
let cls = '';
if (val === currentVal) cls = 'active-cell';
else if (ejected !== null && val === ejected) cls = 'ejected';
else if (heapSet.has(val)) cls = 'in-heap';
html += `<td class="${cls}">${cnt}</td>`;
});
html += '</tr></table>';
return html;
}
function renderHeapVis(heap) {
if (!heap || heap.length === 0) return '<div style="color:#94a3b8;font-style:italic;">空堆</div>';
let html = '<div style="text-align:center;padding:8px 0;"><b>小顶堆 (按频率)</b><br>';
html += '<div class="heap-row">';
heap.forEach((h, i) => {
const cls = i === 0 ? 'top' : 'in-heap';
html += `<span class="heap-cell ${cls}">${h[1]}:${h[0]}</span>`;
});
html += '</div></div>';
return html;
}
function render(step) {
const s = steps[step];
let viz = '<div style="margin-bottom:8px;"><b>频率统计表:</b></div>';
viz += renderFreqTable(s.freq, s.entries, s.heapSet, s.currentVal, s.ejected);
if (s.heap && s.heap.length > 0) {
viz += '<div style="margin:12px 0;">' + renderHeapVis(s.heap) + '</div>';
viz += `<div style="font-size:13px;color:#64748b;">堆顶: 元素${s.heap[0][1]}, 频率=${s.heap[0][0]}</div>`;
}
$('vizArea').innerHTML = viz;
let detail = '<div class="calc-block">' + s.msg + '</div>';
if (s.heap && s.heap.length > 0) {
detail += '<div style="margin-top:8px;font-size:13px;">当前堆内容: ' + s.heap.map(h=>`${h[1]}(×${h[0]})`).join(', ') + '</div>';
}
$('detailContent').innerHTML = detail;
if (s.stage === 'done') {
$('resultContent').innerHTML = `<div class="final-answer">前 <b>${k}</b> 个高频元素: <b>[${s.result.join(', ')}]</b><br><br>思路:① 统计频率 ② 小顶堆维护前 K 高频<br>时间 O(n log k),空间 O(n)</div>`;
}
$('hintText').textContent = s.msg;
const stages = ['start→开始','count→统计频率','build→建堆','compare→比较','replace→替换','skip→跳过','done→完成'];
$('pipeline').innerHTML = stages.map(st => {
const [key, label] = st.split('→');
return `<span class="pipe-step ${s.stage===key?'active':''}">${label}</span>`;
}).join('<i>→</i>');
}
function init() {
const sel = $('exampleSelect');
examples.forEach((e,i) => { sel.innerHTML += `<option value="${i}">${e.label}</option>`; });
buildSteps(examples[0].nums, examples[0].k);
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 = () => {
const m = $('inputArea').value.match(/nums=\[([^\]]+)\]\s*,\s*k=(\d+)/);
if (!m) { alert('格式: nums=[1,1,1,2,2,3], k=2'); return; }
const arr = m[1].split(',').map(Number);
const kVal = parseInt(m[2]);
buildSteps(arr, kVal);
stepCtrl.setSteps(steps.map((_,i) => i));
render(0);
$('stepInfo').textContent = `步骤 1 / ${steps.length}`;
};
$('exampleSelect').onchange = () => {
const e = examples[parseInt($('exampleSelect').value)];
$('inputArea').value = `nums=[${e.nums}], k=${e.k}`;
buildSteps(e.nums, e.k);
stepCtrl.setSteps(steps.map((_,i) => i));
render(0);
$('stepInfo').textContent = `步骤 1 / ${steps.length}`;
};
$('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(`import heapq
from collections import Counter
def topKFrequent(nums, k):
# 1. 统计频率
freq = Counter(nums)
# 2. 小顶堆维护前 k 高频
heap = []
for val, cnt in freq.items():
if len(heap) < k:
heapq.heappush(heap, (cnt, val))
elif cnt > heap[0][0]:
heapq.heapreplace(heap, (cnt, val))
# 3. 返回堆中元素
return [val for cnt, val in heap]
# 复杂度:O(n log k) 时间,O(n) 空间`, {lang:'Python'});
})();
</script>
</body>
</html>