Files

228 lines
9.4 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>072. 每日温度 – 图解</title>
<link rel="stylesheet" href="../shared/style.css">
<style>
.vis-area { min-height: 120px; padding: 16px 0; }
.code-section { margin-top: 16px; }
.temp-bar-container { display:flex; align-items:flex-end; gap:4px; height:140px; padding:8px; background:#f8fafc; border:1px solid var(--border); border-radius:8px; margin:8px 0; }
.temp-bar-wrap { display:flex; flex-direction:column; align-items:center; gap:2px; flex:1; max-width:50px; }
.temp-bar { width:100%; border-radius:4px 4px 0 0; transition:all 0.3s; min-height:4px; }
.temp-label { font-size:11px; font-weight:600; }
.wait-label { font-size:11px; color:var(--green); font-weight:700; min-height:16px; }
.index-label { font-size:10px; color:var(--text-muted); }
</style>
</head>
<body>
<div class="container">
<h1>🟡 072. 每日温度 <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" placeholder="temperatures=[73,74,75,71,69,72,76,73]">
<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 = [
{temps:[73,74,75,71,69,72,76,73], label:'示例1: [73,74,75,71,69,72,76,73]'},
{temps:[30,40,50,60], label:'示例2: 递增'},
{temps:[30,60,90], label:'示例3: [30,60,90]'},
];
let temps, steps, stepCtrl;
function buildSteps(temperatures) {
temps = temperatures;
steps = [];
const n = temperatures.length;
const answer = new Array(n).fill(0);
const stack = []; // indices
steps.push({stage:'init', msg:'初始化:单调栈为空,答案全为 0', currentIdx:-1, stack:[], answer:[...answer], currentTemp:null, poppedIdx:-1, matchIdx:-1});
// Find min and max for bar scaling
const maxTemp = Math.max(...temperatures);
const minTemp = Math.min(...temperatures);
const range = maxTemp - minTemp || 1;
for (let i = 0; i < n; i++) {
const t = temperatures[i];
// Pop while current temp > stack top temp
while (stack.length > 0 && t > temperatures[stack[stack.length - 1]]) {
const poppedIdx = stack.pop();
answer[poppedIdx] = i - poppedIdx;
steps.push({stage:'pop', msg:`T[${i}]=${t} > T[${poppedIdx}]=${temperatures[poppedIdx]},弹出 ${poppedIdx},answer[${poppedIdx}] = ${i}-${poppedIdx} = ${i-poppedIdx}`, currentIdx:i, stack:[...stack], answer:[...answer], currentTemp:t, poppedIdx, matchIdx:i});
}
if (stack.length > 0) {
steps.push({stage:'push', msg:`T[${i}]=${t} ≤ T[${stack[stack.length-1]}]=${temperatures[stack[stack.length-1]]},${t} 入栈等待更高温度`, currentIdx:i, stack:[...stack, i], answer:[...answer], currentTemp:t, poppedIdx:-1, matchIdx:-1});
} else {
steps.push({stage:'push', msg:`T[${i}]=${t},栈为空或栈顶更大,${t} 入栈`, currentIdx:i, stack:[...stack, i], answer:[...answer], currentTemp:t, poppedIdx:-1, matchIdx:-1});
}
stack.push(i);
}
// Remaining in stack have answer 0
if (stack.length > 0) {
steps.push({stage:'remaining', msg:`遍历完毕,栈中剩余 [${stack.join(',')}] 的答案均为 0(之后没有更高温度)`, currentIdx:-1, stack:[...stack], answer:[...answer], currentTemp:null, poppedIdx:-1, matchIdx:-1});
}
steps.push({stage:'done', msg:`结果:[${answer.join(', ')}]`, currentIdx:-1, stack:[], answer:[...answer], currentTemp:null, poppedIdx:-1, matchIdx:-1});
}
function render(step) {
const s = steps[step];
const arr = temps;
const n = arr.length;
const maxTemp = Math.max(...arr);
const minTemp = Math.min(...arr);
const range = maxTemp - minTemp || 1;
// Temperature bars
let viz = '<div class="temp-bar-container">';
for (let i = 0; i < n; i++) {
const pct = ((arr[i] - minTemp) / range) * 80 + 20;
let barColor = '#93c5fd'; // default
if (i === s.currentIdx) barColor = '#f59e0b';
if (i === s.poppedIdx) barColor = '#ef4444';
if (i === s.matchIdx) barColor = '#22c55e';
if (s.stack.includes(i)) barColor = '#8b5cf6';
viz += `<div class="temp-bar-wrap">`;
viz += `<div class="wait-label">${s.answer[i] > 0 ? s.answer[i] : ''}</div>`;
viz += `<div class="temp-label" style="color:${i===s.currentIdx?'#f59e0b':i===s.poppedIdx?'#ef4444':'var(--text)'};">${arr[i]}°</div>`;
viz += `<div class="temp-bar" style="height:${pct}%;background:${barColor};"></div>`;
viz += `<div class="index-label">${i}</div>`;
viz += '</div>';
}
viz += '</div>';
// Monotone stack
viz += '<div style="margin-top:10px;"><b>单调栈(存索引,对应温度递减):</b>';
viz += renderStack(s.stack.map(i => `${i}(${arr[i]}°)`), {topIndex: s.stack.length - 1});
viz += '</div>';
// Answer array
const hl = {};
if (s.currentIdx >= 0) hl[s.currentIdx] = 'orange';
if (s.poppedIdx >= 0) hl[s.poppedIdx] = 'red';
if (s.matchIdx >= 0) hl[s.matchIdx] = 'green';
viz += '<div style="margin-top:10px;"><b>答案数组:</b>';
viz += renderArray(s.answer, {highlights: hl});
viz += '</div>';
$('vizArea').innerHTML = viz;
let detail = `<div class="calc-block">${s.msg}</div>`;
detail += '<div style="margin-top:4px;font-size:12px;color:var(--text-secondary);">核心:单调递减栈,遇到更高温度则弹出并计算天数差</div>';
$('detailContent').innerHTML = detail;
if (s.stage === 'done') {
$('resultContent').innerHTML = `<div class="final-answer">返回 <b>[${s.answer.join(', ')}]</b><br>时间复杂度 O(n),空间 O(n)</div>`;
}
$('hintText').textContent = s.msg;
const stages = ['init→初始化','push→入栈','pop→弹出','remaining→剩余','done→完成'];
$('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 = 'temperatures=[73,74,75,71,69,72,76,73]';
buildSteps(examples[0].temps);
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(/temperatures=\[([^\]]+)\]/);
if (!m) { alert('格式: temperatures=[73,74,75,71,69,72,76,73]'); return; }
const arr = m[1].split(',').map(Number);
buildSteps(arr);
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 = `temperatures=[${e.temps}]`;
buildSteps(e.temps);
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 dailyTemperatures(temperatures):
n = len(temperatures)
answer = [0] * n
stack = [] # 单调递减栈,存索引
for i, t in enumerate(temperatures):
while stack and t > temperatures[stack[-1]]:
idx = stack.pop()
answer[idx] = i - idx
stack.append(i)
return answer`, {lang:'Python'});
})();
</script>
</body>
</html>