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

290 lines
11 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>015. 轮转数组 – 图解</title>
<link rel="stylesheet" href="../shared/style.css">
<style>
.vis-area { min-height: 200px; padding: 16px 0; }
.code-section { margin-top: 16px; }
.step-title {
font-size: 15px; font-weight: 700; margin: 12px 0 6px; display: flex; align-items: center; gap: 8px;
}
.step-title .num {
width: 24px; height: 24px; border-radius: 50%; background: #4f46e5; color: white;
display: inline-flex; align-items: center; justify-content: center; font-size: 13px;
}
.range-label {
display: inline-block; padding: 2px 10px; border-radius: 8px; font-size: 12px;
font-weight: 600; margin: 4px 2px;
}
.range-label.purple { background: #ede9fe; color: #5b21b6; }
.range-label.orange { background: #fff7ed; color: #9a3412; }
.range-label.cyan { background: #cffafe; color: #155e75; }
kbd { background: #f1f5f9; padding: 2px 6px; border-radius: 4px; font-size: 12px; border: 1px solid #e2e8f0; font-family: monospace; }
</style>
</head>
<body>
<div class="container">
<h1>🟡 015. 轮转数组 <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="默认示例" style="width:200px;">
<label for="kInput" style="margin-left:8px;">k =</label>
<input type="text" id="kInput" placeholder="3" style="width:60px;">
<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 = [
{input:[1,2,3,4,5,6,7], k:3, label:'示例1: [1,2,3,4,5,6,7] k=3'},
{input:[-1,-100,3,99], k:2, label:'示例2: [-1,-100,3,99] k=2'},
{input:[1,2,3,4,5,6], k:1, label:'示例3: [1,2,3,4,5,6] k=1'},
{input:[1,2], k:3, label:'示例4: k>n 取模'},
];
let origNums, k, steps, stepCtrl;
function reverse(arr, l, r) {
const a = [...arr];
while (l < r) { [a[l], a[r]] = [a[r], a[l]]; l++; r--; }
return a;
}
function buildSteps(arr, kk) {
origNums = [...arr];
k = kk % arr.length;
if (k === 0) k = arr.length; // if kk%n==0, show full cycle anyway for pedagogy
k = kk % arr.length;
steps = [];
const n = arr.length;
// Step 0: Original
steps.push({
stage:'original', arr:[...arr], reverseRange:null,
msg:`原始数组: [${arr.join(', ')}],k = ${kk}${kk !== k ? ` → k%n = ${k}` : ''},将右轮转 ${k} 个位置`
});
// Step 1: Reverse entire array
const afterFull = reverse(arr, 0, n - 1);
steps.push({
stage:'reverse_all', arr: afterFull, reverseRange: [0, n-1],
msg:`第①步:反转整个数组 [0,${n-1}] → [${afterFull.join(', ')}]`
});
// Step 2: Reverse first k elements
const afterFirst = reverse(afterFull, 0, k - 1);
steps.push({
stage:'reverse_k', arr: afterFirst, reverseRange: [0, k-1],
msg:`第②步:反转前 ${k} 个元素 [0,${k-1}] → [${afterFirst.join(', ')}]`
});
// Step 3: Reverse last n-k elements
const afterSecond = reverse(afterFirst, k, n - 1);
steps.push({
stage:'reverse_nk', arr: afterSecond, reverseRange: [k, n-1],
msg:`第③步:反转后 ${n-k} 个元素 [${k},${n-1}] → [${afterSecond.join(', ')}]`
});
// Step 4: Done
steps.push({
stage:'done', arr: afterSecond, reverseRange:null,
msg:`轮转完成!每个元素右移了 ${k} 个位置(循环)`
});
}
function renderArrWithRange(arr, range, stage, n, kk) {
const hl = {};
if (range) {
for (let i = range[0]; i <= range[1] && i < arr.length; i++) {
if (stage === 'reverse_all') hl[i] = 'purple';
else if (stage === 'reverse_k') hl[i] = 'orange';
else if (stage === 'reverse_nk') hl[i] = 'cyan';
}
}
const pointers = {};
if (range) {
pointers[stage === 'reverse_k' ? 'k' : (stage === 'reverse_nk' ? 's' : '')] = null;
}
let html = renderArray(arr, {highlights: hl});
if (range) {
const cls = stage === 'reverse_all' ? 'purple' : stage === 'reverse_k' ? 'orange' : 'cyan';
html += `<div style="margin-top:6px;">`;
html += `<span class="range-label ${cls}">反转范围 [${range[0]}, ${range[1]}]</span>`;
// show what the reversed segment looks like
const seg = arr.slice(range[0], range[1] + 1);
html += `<span style="font-size:12px;color:#64748b;margin-left:8px;">此段反转后: [${seg.join(', ')}]</span>`;
html += '</div>';
}
return html;
}
function render(step) {
const s = steps[step];
const n = origNums.length;
let viz = '';
// Show original array at top for reference
viz += '<div style="margin-bottom:16px;">';
viz += '<div style="font-size:13px;color:#475569;margin-bottom:4px;">📌 原始数组</div>';
viz += renderArray(origNums, {});
viz += '</div>';
// Show step-by-step progress
const titles = {
'original': '初始状态',
'reverse_all': '① 反转全部',
'reverse_k': '② 反转前 k 个',
'reverse_nk': '③ 反转后 n-k 个',
'done': '✅ 最终结果'
};
const showSteps = ['original', 'reverse_all', 'reverse_k', 'reverse_nk', 'done'];
const currentIdx = showSteps.indexOf(s.stage);
viz += '<div style="background:#f8fafc;border-radius:12px;padding:12px;">';
for (let si = 0; si <= currentIdx; si++) {
const sStage = showSteps[si];
const sData = steps.find(x => x.stage === sStage);
if (!sData) continue;
const isActive = sStage === s.stage;
viz += '<div style="' + (si > 0 ? 'margin-top:12px;' : '') + '">';
viz += `<div class="step-title"><span class="num">${si+1}</span>${titles[sStage]}</div>`;
viz += renderArrWithRange(sData.arr, sData.reverseRange, sStage, n, k);
viz += '</div>';
}
viz += '</div>';
// Formula
viz += '<div class="formula-box" style="margin-top:12px;">';
viz += `n = ${n}, k = ${k}<br>`;
viz += `<code>反转(nums, 0, n-1) → 反转(nums, 0, k-1) → 反转(nums, k, n-1)</code>`;
viz += '</div>';
$('vizArea').innerHTML = viz;
// Detail
let detail = '<div class="calc-block">' + s.msg + '</div>';
if (s.stage === 'done') {
const expected = [...origNums];
for (let i = 0; i < k % n; i++) expected.unshift(expected.pop());
detail += `<div class="formula-box">验证:将每个元素右移 ${k} 位 = [${expected.join(', ')}] ✓</div>`;
}
$('detailContent').innerHTML = detail;
if (s.stage === 'done') {
$('resultContent').innerHTML = `<div class="final-answer">
轮转结果:<b>[${s.arr.join(', ')}]</b><br>
将 [${origNums.join(', ')}] 右轮转 ${k} 位
<div class="complexity">时间复杂度 O(n) | 空间复杂度 O(1) | 三次反转法</div>
</div>`;
}
$('hintText').textContent = s.msg;
const stages = [['original','原始'],['reverse_all','①反转全部'],['reverse_k','②反转前k'],['reverse_nk','③反转后n-k'],['done','完成']];
$('pipeline').innerHTML = stages.map(([k2,l]) =>
`<span class="pipe-step ${s.stage===k2?'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 = JSON.stringify(examples[0].input);
$('kInput').value = '3';
buildSteps(examples[0].input, 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 = () => {
try {
const arr = JSON.parse($('inputArea').value);
const kk = parseInt($('kInput').value);
if (!Array.isArray(arr) || arr.length === 0 || isNaN(kk)) throw new Error();
buildSteps(arr, kk); stepCtrl.setSteps(steps.map((_,i)=>i)); render(0);
$('stepInfo').textContent = '步骤 1 / ' + steps.length;
} catch(e) { alert('请输入合法数组和 k 值,例如 [1,2,3,4,5,6,7] k=3'); }
};
$('exampleSelect').onchange = () => {
const e = examples[parseInt($('exampleSelect').value)];
$('inputArea').value = JSON.stringify(e.input);
$('kInput').value = e.k;
buildSteps(e.input, e.k); 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 rotate(nums, k):
n = len(nums)
k %= n
def rev(l, r):
while l < r:
nums[l], nums[r] = nums[r], nums[l]
l += 1; r -= 1
# 三次反转
rev(0, n - 1) # 反转全部
rev(0, k - 1) # 反转前 k 个
rev(k, n - 1) # 反转后 n-k 个`, {lang:'Python'});
})();
</script>
</body>
</html>