425 lines
18 KiB
HTML
425 lines
18 KiB
HTML
|
|
<!DOCTYPE html>
|
|||
|
|
<html lang="zh-Hans">
|
|||
|
|
<head>
|
|||
|
|
<meta charset="UTF-8">
|
|||
|
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|||
|
|
<title>020. 旋转图像 – 图解</title>
|
|||
|
|
<link rel="stylesheet" href="../shared/style.css">
|
|||
|
|
<style>
|
|||
|
|
.vis-area { min-height: 120px; padding: 16px 0; }
|
|||
|
|
.code-section { margin-top: 16px; }
|
|||
|
|
.legend { display:flex; flex-wrap:wrap; gap:12px; margin:10px 0; font-size:13px; }
|
|||
|
|
.legend span { display:inline-flex; align-items:center; gap:4px; }
|
|||
|
|
.legend .dot { width:14px; height:14px; border-radius:4px; display:inline-block; }
|
|||
|
|
.rotate-pos0 { background:#dbeafe !important; border-color:#3b82f6 !important; color:#1e40af; font-weight:700; box-shadow:0 0 0 3px rgba(59,130,246,.25); }
|
|||
|
|
.rotate-pos1 { background:#dcfce7 !important; border-color:#16a34a !important; color:#166534; font-weight:700; box-shadow:0 0 0 3px rgba(22,163,74,.25); }
|
|||
|
|
.rotate-pos2 { background:#fef3c7 !important; border-color:#f59e0b !important; color:#92400e; font-weight:700; box-shadow:0 0 0 3px rgba(245,158,11,.25); }
|
|||
|
|
.rotate-pos3 { background:#ede9fe !important; border-color:#8b5cf6 !important; color:#5b21b6; font-weight:700; box-shadow:0 0 0 3px rgba(139,92,246,.25); }
|
|||
|
|
.rotate-swapped { background:#f0fdf4 !important; border-color:#86efac !important; color:#166534; }
|
|||
|
|
.transpose-diag { background:#fef3c7 !important; border-color:#f59e0b !important; }
|
|||
|
|
.transpose-swap { background:#dbeafe !important; border-color:#3b82f6 !important; box-shadow:0 0 0 3px rgba(59,130,246,.2); }
|
|||
|
|
.flip-swap { background:#fef3c7 !important; border-color:#f59e0b !important; box-shadow:0 0 0 3px rgba(245,158,11,.2); }
|
|||
|
|
.method-tabs { display:flex; gap:4px; margin-bottom:12px; }
|
|||
|
|
.method-tab {
|
|||
|
|
padding:6px 16px; border-radius:8px; cursor:pointer; font-size:14px;
|
|||
|
|
border:1px solid #cbd5e1; background:#f8fafc; transition:all .2s;
|
|||
|
|
}
|
|||
|
|
.method-tab.active { background:#4f46e5; color:white; border-color:#4f46e5; }
|
|||
|
|
.rotation-diagram {
|
|||
|
|
display:flex; align-items:center; gap:8px; margin-top:10px;
|
|||
|
|
font-size:13px; line-height:1.8; padding:10px;
|
|||
|
|
background:#f8fafc; border-radius:8px;
|
|||
|
|
}
|
|||
|
|
.arrow-flow { font-size:16px; font-weight:700; }
|
|||
|
|
.arrow-flow.blue { color:#3b82f6; }
|
|||
|
|
.arrow-flow.green { color:#16a34a; }
|
|||
|
|
.arrow-flow.orange { color:#f59e0b; }
|
|||
|
|
.arrow-flow.purple { color:#8b5cf6; }
|
|||
|
|
.pos-label {
|
|||
|
|
display:inline-block; padding:1px 6px; border-radius:4px;
|
|||
|
|
font-size:11px; font-weight:700; margin:0 2px;
|
|||
|
|
}
|
|||
|
|
.pos-label.p0 { background:#dbeafe; color:#1e40af; }
|
|||
|
|
.pos-label.p1 { background:#dcfce7; color:#166534; }
|
|||
|
|
.pos-label.p2 { background:#fef3c7; color:#92400e; }
|
|||
|
|
.pos-label.p3 { background:#ede9fe; color:#5b21b6; }
|
|||
|
|
</style>
|
|||
|
|
</head>
|
|||
|
|
<body>
|
|||
|
|
<div class="container">
|
|||
|
|
<h1>🔄 020. 旋转图像 <span class="badge medium">中等</span></h1>
|
|||
|
|
<p class="subtitle">分类:矩阵 | LeetCode Hot 100</p>
|
|||
|
|
|
|||
|
|
<div class="method-tabs" id="methodTabs">
|
|||
|
|
<div class="method-tab active" data-method="rotation">方法一:四角旋转</div>
|
|||
|
|
<div class="method-tab" data-method="transpose">方法二:转置+翻转</div>
|
|||
|
|
</div>
|
|||
|
|
|
|||
|
|
<div class="controls" id="controls">
|
|||
|
|
<label for="inputArea">输入:</label>
|
|||
|
|
<input type="text" id="inputArea" placeholder="默认示例,可自定义">
|
|||
|
|
<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,8,9]], label: '3×3'},
|
|||
|
|
{input: [[5,1,9,11],[2,4,8,10],[13,3,6,7],[15,14,12,16]], label: '4×4'},
|
|||
|
|
];
|
|||
|
|
|
|||
|
|
let origMatrix, steps, stepCtrl, currentMethod = 'rotation';
|
|||
|
|
|
|||
|
|
// ===== Method 1: Four-corner rotation =====
|
|||
|
|
function buildRotationSteps(mat) {
|
|||
|
|
const n = mat.length;
|
|||
|
|
const work = mat.map(r => [...r]);
|
|||
|
|
steps = [];
|
|||
|
|
|
|||
|
|
steps.push({
|
|||
|
|
stage: 'init', msg: '方法一:四角原地旋转 — 每次旋转4个元素,依次赋值',
|
|||
|
|
phase: 'init', posGroup: [], swapped: {}, currentMatrix: work.map(r=>[...r])
|
|||
|
|
});
|
|||
|
|
|
|||
|
|
for (let i = 0; i < Math.floor(n / 2); i++) {
|
|||
|
|
for (let j = i; j < n - 1 - i; j++) {
|
|||
|
|
const positions = [
|
|||
|
|
[i, j], // pos0: top-left
|
|||
|
|
[j, n-1-i], // pos1: top-right
|
|||
|
|
[n-1-i, n-1-j], // pos2: bottom-right
|
|||
|
|
[n-1-j, i], // pos3: bottom-left
|
|||
|
|
];
|
|||
|
|
const vals = positions.map(([r,c]) => work[r][c]);
|
|||
|
|
|
|||
|
|
// Show 4 positions
|
|||
|
|
steps.push({
|
|||
|
|
stage: 'show_4', msg: `四角组 (${i},${j}):<span class="pos-label p0">[${positions[0]}]=${vals[0]}</span> → <span class="pos-label p1">[${positions[1]}]=${vals[1]}</span> → <span class="pos-label p2">[${positions[2]}]=${vals[2]}</span> → <span class="pos-label p3">[${positions[3]}]=${vals[3]}</span>`,
|
|||
|
|
phase: 'show', posGroup: positions, swapped: {}, values: vals,
|
|||
|
|
currentMatrix: work.map(r=>[...r])
|
|||
|
|
});
|
|||
|
|
|
|||
|
|
// Perform rotation: temp = pos0; pos0←pos3; pos3←pos2; pos2←pos1; pos1←temp
|
|||
|
|
const temp = work[i][j];
|
|||
|
|
work[i][j] = work[n-1-j][i];
|
|||
|
|
work[n-1-j][i] = work[n-1-i][n-1-j];
|
|||
|
|
work[n-1-i][n-1-j] = work[j][n-1-i];
|
|||
|
|
work[j][n-1-i] = temp;
|
|||
|
|
|
|||
|
|
const newVals = positions.map(([r,c]) => work[r][c]);
|
|||
|
|
steps.push({
|
|||
|
|
stage: 'rotated', msg: `旋转完成:<span class="pos-label p0">[${positions[0]}]=${newVals[0]}</span> <span class="pos-label p1">[${positions[1]}]=${newVals[1]}</span> <span class="pos-label p2">[${positions[2]}]=${newVals[2]}</span> <span class="pos-label p3">[${positions[3]}]=${newVals[3]}</span>`,
|
|||
|
|
phase: 'rotated', posGroup: positions, swapped: Object.fromEntries(positions.map(([r,c]) => [`${r},${c}`, true])),
|
|||
|
|
values: newVals, currentMatrix: work.map(r=>[...r])
|
|||
|
|
});
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
steps.push({
|
|||
|
|
stage: 'done', msg: '顺时针旋转90°完成!',
|
|||
|
|
phase: 'done', posGroup: [], swapped: {}, currentMatrix: work.map(r=>[...r])
|
|||
|
|
});
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// ===== Method 2: Transpose + Flip =====
|
|||
|
|
function buildTransposeSteps(mat) {
|
|||
|
|
const n = mat.length;
|
|||
|
|
const work = mat.map(r => [...r]);
|
|||
|
|
steps = [];
|
|||
|
|
|
|||
|
|
steps.push({
|
|||
|
|
stage: 'init', msg: '方法二:先沿主对角线转置,再翻转每行',
|
|||
|
|
phase: 'init', posGroup: [], swapped: {}, currentMatrix: work.map(r=>[...r])
|
|||
|
|
});
|
|||
|
|
|
|||
|
|
// Phase 1: Transpose
|
|||
|
|
steps.push({
|
|||
|
|
stage: 'transpose_start', msg: '第一步:转置 — 交换 matrix[i][j] ↔ matrix[j][i](i < j)',
|
|||
|
|
phase: 'transpose', posGroup: [], swapped: {}, currentMatrix: work.map(r=>[...r])
|
|||
|
|
});
|
|||
|
|
|
|||
|
|
for (let i = 0; i < n; i++) {
|
|||
|
|
for (let j = i + 1; j < n; j++) {
|
|||
|
|
steps.push({
|
|||
|
|
stage: 'transpose', msg: `转置:matrix[${i}][${j}]=${work[i][j]} ↔ matrix[${j}][${i}]=${work[j][i]}`,
|
|||
|
|
phase: 'transpose', posGroup: [[i,j],[j,i]], swapped: {},
|
|||
|
|
currentMatrix: work.map(r=>[...r])
|
|||
|
|
});
|
|||
|
|
[work[i][j], work[j][i]] = [work[j][i], work[i][j]];
|
|||
|
|
steps.push({
|
|||
|
|
stage: 'transpose_done', msg: `完成:matrix[${i}][${j}]=${work[i][j]}, matrix[${j}][${i}]=${work[j][i]}`,
|
|||
|
|
phase: 'transpose', posGroup: [[i,j],[j,i]], swapped: {[[i,j],[j,i]].map(([r,c])=>`${r},${c}`).join(','): true},
|
|||
|
|
currentMatrix: work.map(r=>[...r]),
|
|||
|
|
swappedCells: {[`${i},${j}`]: true, [`${j},${i}`]: true}
|
|||
|
|
});
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
steps.push({
|
|||
|
|
stage: 'transpose_complete', msg: '转置完成!',
|
|||
|
|
phase: 'transpose_done', posGroup: [], swapped: {},
|
|||
|
|
currentMatrix: work.map(r=>[...r])
|
|||
|
|
});
|
|||
|
|
|
|||
|
|
// Phase 2: Reverse each row
|
|||
|
|
steps.push({
|
|||
|
|
stage: 'reverse_start', msg: '第二步:翻转每行(左右交换)',
|
|||
|
|
phase: 'reverse', posGroup: [], swapped: {},
|
|||
|
|
currentMatrix: work.map(r=>[...r])
|
|||
|
|
});
|
|||
|
|
|
|||
|
|
for (let i = 0; i < n; i++) {
|
|||
|
|
let l = 0, r = n - 1;
|
|||
|
|
while (l < r) {
|
|||
|
|
steps.push({
|
|||
|
|
stage: 'reverse', msg: `翻转第${i}行:matrix[${i}][${l}]=${work[i][l]} ↔ matrix[${i}][${r}]=${work[i][r]}`,
|
|||
|
|
phase: 'reverse', posGroup: [[i,l],[i,r]], swapped: {},
|
|||
|
|
currentMatrix: work.map(r=>[...r])
|
|||
|
|
});
|
|||
|
|
[work[i][l], work[i][r]] = [work[i][r], work[i][l]];
|
|||
|
|
steps.push({
|
|||
|
|
stage: 'reverse_done', msg: `完成:matrix[${i}][${l}]=${work[i][l]}, matrix[${i}][${r}]=${work[i][r]}`,
|
|||
|
|
phase: 'reverse', posGroup: [[i,l],[i,r]], swapped: {},
|
|||
|
|
currentMatrix: work.map(r=>[...r]),
|
|||
|
|
swappedCells: {[`${i},${l}`]: true, [`${i},${r}`]: true}
|
|||
|
|
});
|
|||
|
|
l++; r--;
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
steps.push({
|
|||
|
|
stage: 'done', msg: '旋转完成!顺时针90° = 转置 + 翻转每行',
|
|||
|
|
phase: 'done', posGroup: [], swapped: {},
|
|||
|
|
currentMatrix: work.map(r=>[...r])
|
|||
|
|
});
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
function render(step) {
|
|||
|
|
const s = steps[step];
|
|||
|
|
|
|||
|
|
function cellClass(val, r, c) {
|
|||
|
|
const key = `${r},${c}`;
|
|||
|
|
// Method 1: 4-corner rotation
|
|||
|
|
if (currentMethod === 'rotation') {
|
|||
|
|
const idx = s.posGroup ? s.posGroup.findIndex(([pr,pc]) => pr===r && pc===c) : -1;
|
|||
|
|
if (idx >= 0) {
|
|||
|
|
return `rotate-pos${idx}`;
|
|||
|
|
}
|
|||
|
|
if (s.swappedCells && s.swappedCells[key]) return 'rotate-swapped';
|
|||
|
|
}
|
|||
|
|
// Method 2: transpose/flip
|
|||
|
|
if (currentMethod === 'transpose') {
|
|||
|
|
if (s.phase === 'transpose') {
|
|||
|
|
const idx = s.posGroup ? s.posGroup.findIndex(([pr,pc]) => pr===r && pc===c) : -1;
|
|||
|
|
if (idx >= 0) return 'transpose-swap';
|
|||
|
|
if (r === c) return 'transpose-diag';
|
|||
|
|
}
|
|||
|
|
if (s.phase === 'reverse') {
|
|||
|
|
const idx = s.posGroup ? s.posGroup.findIndex(([pr,pc]) => pr===r && pc===c) : -1;
|
|||
|
|
if (idx >= 0) return 'flip-swap';
|
|||
|
|
}
|
|||
|
|
if (s.swappedCells && s.swappedCells[key]) return 'rotate-swapped';
|
|||
|
|
}
|
|||
|
|
return '';
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
let viz = '<div class="legend">';
|
|||
|
|
if (currentMethod === 'rotation') {
|
|||
|
|
viz += '<span><span class="dot" style="background:#dbeafe;border:1px solid #3b82f6;"></span> <span class="pos-label p0">pos0</span></span>';
|
|||
|
|
viz += '<span><span class="dot" style="background:#dcfce7;border:1px solid #16a34a;"></span> <span class="pos-label p1">pos1</span></span>';
|
|||
|
|
viz += '<span><span class="dot" style="background:#fef3c7;border:1px solid #f59e0b;"></span> <span class="pos-label p2">pos2</span></span>';
|
|||
|
|
viz += '<span><span class="dot" style="background:#ede9fe;border:1px solid #8b5cf6;"></span> <span class="pos-label p3">pos3</span></span>';
|
|||
|
|
viz += '<span><span class="dot" style="background:#f0fdf4;border:1px solid #86efac;"></span> 已交换</span>';
|
|||
|
|
} else {
|
|||
|
|
viz += '<span><span class="dot" style="background:#fef3c7;border:1px solid #f59e0b;"></span> 对角线</span>';
|
|||
|
|
viz += '<span><span class="dot" style="background:#dbeafe;border:1px solid #3b82f6;"></span> 交换对</span>';
|
|||
|
|
viz += '<span><span class="dot" style="background:#f0fdf4;border:1px solid #86efac;"></span> 已交换</span>';
|
|||
|
|
}
|
|||
|
|
viz += '</div>';
|
|||
|
|
|
|||
|
|
if (s.currentMatrix) {
|
|||
|
|
viz += '<div style="margin-bottom:6px;font-weight:600;font-size:14px;">当前矩阵:</div>';
|
|||
|
|
viz += renderGrid(s.currentMatrix, { cellClass, cellSize: 48 });
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// Rotation diagram for method 1
|
|||
|
|
if (currentMethod === 'rotation' && s.posGroup && s.posGroup.length === 4) {
|
|||
|
|
viz += '<div class="rotation-diagram">';
|
|||
|
|
viz += '<span class="pos-label p0">pos0 → pos1</span>';
|
|||
|
|
viz += '<span class="arrow-flow blue">↻</span>';
|
|||
|
|
viz += '<span class="pos-label p1">pos1 → pos2</span>';
|
|||
|
|
viz += '<span class="arrow-flow green">↻</span>';
|
|||
|
|
viz += '<span class="pos-label p2">pos2 → pos3</span>';
|
|||
|
|
viz += '<span class="arrow-flow orange">↻</span>';
|
|||
|
|
viz += '<span class="pos-label p3">pos3 → pos0</span>';
|
|||
|
|
viz += '</div>';
|
|||
|
|
|
|||
|
|
if (s.values) {
|
|||
|
|
viz += '<div style="margin-top:6px;font-size:13px;padding:8px 12px;background:#f8fafc;border-radius:8px;line-height:1.8;">';
|
|||
|
|
viz += `<b>赋值链:</b>temp = ${s.values[0]}; `;
|
|||
|
|
viz += `<span class="pos-label p0">[${s.posGroup[0]}]</span>=${s.stage==='rotated' ? s.values[3] : '?'}; `;
|
|||
|
|
viz += `<span class="pos-label p3">[${s.posGroup[3]}]</span>=${s.stage==='rotated' ? s.values[2] : '?'}; `;
|
|||
|
|
viz += `<span class="pos-label p2">[${s.posGroup[2]}]</span>=${s.stage==='rotated' ? s.values[1] : '?'}; `;
|
|||
|
|
viz += `<span class="pos-label p1">[${s.posGroup[1]}]</span>=temp=${s.values[0]}`;
|
|||
|
|
viz += '</div>';
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// Transpose/flip diagram for method 2
|
|||
|
|
if (currentMethod === 'transpose' && s.posGroup && s.posGroup.length === 2) {
|
|||
|
|
const [p1, p2] = s.posGroup;
|
|||
|
|
if (s.phase === 'transpose') {
|
|||
|
|
viz += '<div style="margin-top:8px;padding:8px 12px;background:#eff6ff;border-radius:8px;font-size:13px;line-height:1.8;">';
|
|||
|
|
viz += `<b>转置交换:</b>matrix[${p1}][${p1}] ↔ matrix[${p2}][${p2}](沿主对角线 ↗)`;
|
|||
|
|
viz += '</div>';
|
|||
|
|
} else if (s.phase === 'reverse') {
|
|||
|
|
viz += '<div style="margin-top:8px;padding:8px 12px;background:#fef3c7;border-radius:8px;font-size:13px;line-height:1.8;">';
|
|||
|
|
viz += `<b>行翻转:</b>第${p1[0]}行 matrix[${p1}][${p1[1]}] ↔ matrix[${p2}][${p2[1]}](左右对称 ←→)`;
|
|||
|
|
viz += '</div>';
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
$('vizArea').innerHTML = viz;
|
|||
|
|
|
|||
|
|
// Detail
|
|||
|
|
let detail = '<div class="calc-block">' + s.msg + '</div>';
|
|||
|
|
if (currentMethod === 'rotation') {
|
|||
|
|
detail += '<div style="margin-top:6px;font-size:13px;">方法:四角原地旋转 (temp→赋值链)</div>';
|
|||
|
|
} else {
|
|||
|
|
detail += `<div style="margin-top:6px;font-size:13px;">当前阶段:${s.phase==='transpose'?'转置':s.phase==='transpose_done'?'转置完成':s.phase==='reverse'?'翻转每行':'完成'}</div>`;
|
|||
|
|
}
|
|||
|
|
$('detailContent').innerHTML = detail;
|
|||
|
|
|
|||
|
|
if (s.stage === 'done') {
|
|||
|
|
const rows = s.currentMatrix.map(r => '[' + r.join(',') + ']');
|
|||
|
|
$('resultContent').innerHTML = `<div class="final-answer">旋转后:<b>[${rows.join(', ')}]</b><div class="complexity">时间 O(n²) | 空间 O(1)</div></div>`;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
$('hintText').textContent = s.msg.replace(/<[^>]*>/g, '');
|
|||
|
|
const pipe = currentMethod === 'rotation'
|
|||
|
|
? [['init','初始化'],['show_4','显示四角'],['rotated','旋转完成'],['done','完成']]
|
|||
|
|
: [['init','初始化'],['transpose','转置'],['transpose_done','转置完成'],['reverse','翻转'],['done','完成']];
|
|||
|
|
$('pipeline').innerHTML = pipe.map(([k,l]) =>
|
|||
|
|
`<span class="pipe-step ${s.stage===k?'active':''}">${l}</span>`
|
|||
|
|
).join('<i>→</i>');
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
function rebuild() {
|
|||
|
|
try {
|
|||
|
|
const mat = JSON.parse($('inputArea').value);
|
|||
|
|
if (!Array.isArray(mat) || !Array.isArray(mat[0])) throw 0;
|
|||
|
|
origMatrix = mat.map(r => [...r]);
|
|||
|
|
if (currentMethod === 'rotation') buildRotationSteps(mat);
|
|||
|
|
else buildTransposeSteps(mat);
|
|||
|
|
stepCtrl = new StepController({ onStep: render });
|
|||
|
|
stepCtrl.setSteps(steps.map((_, i) => i));
|
|||
|
|
render(0);
|
|||
|
|
$('stepInfo').textContent = `步骤 1 / ${steps.length}`;
|
|||
|
|
stepCtrl.onStep = (idx) => { render(idx); $('stepInfo').textContent = `步骤 ${idx + 1} / ${steps.length}`; };
|
|||
|
|
} catch (e) { alert('请输入合法 n×n JSON 二维数组'); }
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
function init() {
|
|||
|
|
const sel = $('exampleSelect');
|
|||
|
|
examples.forEach((e, i) => { sel.innerHTML += `<option value="${i}">${e.label}</option>`; });
|
|||
|
|
$('inputArea').value = JSON.stringify(examples[0].input);
|
|||
|
|
|
|||
|
|
// Method tabs
|
|||
|
|
document.querySelectorAll('.method-tab').forEach(tab => {
|
|||
|
|
tab.onclick = () => {
|
|||
|
|
document.querySelectorAll('.method-tab').forEach(t => t.classList.remove('active'));
|
|||
|
|
tab.classList.add('active');
|
|||
|
|
currentMethod = tab.dataset.method;
|
|||
|
|
updateCodeArea();
|
|||
|
|
rebuild();
|
|||
|
|
};
|
|||
|
|
});
|
|||
|
|
|
|||
|
|
rebuild();
|
|||
|
|
|
|||
|
|
$('applyBtn').onclick = () => rebuild();
|
|||
|
|
$('exampleSelect').onchange = () => {
|
|||
|
|
const e = examples[parseInt($('exampleSelect').value)];
|
|||
|
|
$('inputArea').value = JSON.stringify(e.input);
|
|||
|
|
rebuild();
|
|||
|
|
};
|
|||
|
|
$('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 = '自动播放'; };
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
function updateCodeArea() {
|
|||
|
|
if (currentMethod === 'rotation') {
|
|||
|
|
$('codeArea').innerHTML = renderCode(`def rotate(matrix):
|
|||
|
|
n = len(matrix)
|
|||
|
|
# 四角原地旋转
|
|||
|
|
for i in range(n // 2):
|
|||
|
|
for j in range(i, n - 1 - i):
|
|||
|
|
# 记录4个位置
|
|||
|
|
tmp = matrix[i][j]
|
|||
|
|
matrix[i][j] = matrix[n-1-j][i] # pos3 → pos0
|
|||
|
|
matrix[n-1-j][i] = matrix[n-1-i][n-1-j] # pos2 → pos3
|
|||
|
|
matrix[n-1-i][n-1-j] = matrix[j][n-1-i] # pos1 → pos2
|
|||
|
|
matrix[j][n-1-i] = tmp # pos0 → pos1`, { lang: 'Python' });
|
|||
|
|
} else {
|
|||
|
|
$('codeArea').innerHTML = renderCode(`def rotate(matrix):
|
|||
|
|
n = len(matrix)
|
|||
|
|
# 方法二:转置 + 翻转每行
|
|||
|
|
# 1. 转置:沿主对角线交换
|
|||
|
|
for i in range(n):
|
|||
|
|
for j in range(i + 1, n):
|
|||
|
|
matrix[i][j], matrix[j][i] = matrix[j][i], matrix[i][j]
|
|||
|
|
# 2. 翻转每行
|
|||
|
|
for i in range(n):
|
|||
|
|
matrix[i].reverse()`, { lang: 'Python' });
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
init();
|
|||
|
|
updateCodeArea();
|
|||
|
|
})();
|
|||
|
|
</script>
|
|||
|
|
</body>
|
|||
|
|
</html>
|