455 lines
19 KiB
HTML
455 lines
19 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>018. 矩阵置零 – 图解</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; }
|
|||
|
|
.marker-row-cell { background:#fef3c7 !important; border-color:#f59e0b !important; color:#92400e; font-weight:700; }
|
|||
|
|
.zeroed-cell { background:#fee2e2 !important; border-color:#ef4444 !important; color:#991b1b; }
|
|||
|
|
.first-row-marker { background:#ede9fe !important; border-color:#8b5cf6 !important; color:#5b21b6; }
|
|||
|
|
.first-col-marker { background:#cffafe !important; border-color:#06b6d4 !important; color:#155e75; }
|
|||
|
|
.origin-zero { background:#fef3c7 !important; border-color:#f59e0b !important; color:#92400e; }
|
|||
|
|
.phase-tag { display:inline-block; padding:2px 8px; border-radius:999px; font-size:11px; font-weight:700; margin-right:6px; }
|
|||
|
|
.phase-tag.mark { background:#fef3c7; color:#92400e; }
|
|||
|
|
.phase-tag.zero { background:#fee2e2; color:#991b1b; }
|
|||
|
|
.phase-tag.first { background:#ede9fe; color:#5b21b6; }
|
|||
|
|
.phase-tag.done { background:#dcfce7; color:#166534; }
|
|||
|
|
.info-row { display:flex; gap:16px; flex-wrap:wrap; margin-top:8px; font-size:13px; }
|
|||
|
|
.info-row .tag { padding:2px 8px; border-radius:6px; font-weight:600; }
|
|||
|
|
.tag.green { background:#dcfce7; color:#166534; }
|
|||
|
|
.tag.red { background:#fee2e2; color:#991b1b; }
|
|||
|
|
.tag.blue { background:#dbeafe; color:#1e40af; }
|
|||
|
|
.tag.purple { background:#ede9fe; color:#5b21b6; }
|
|||
|
|
.tag.cyan { background:#cffafe; color:#155e75; }
|
|||
|
|
.border-label { font-size:11px; color:var(--text-muted); text-align:center; padding:2px 0; }
|
|||
|
|
</style>
|
|||
|
|
</head>
|
|||
|
|
<body>
|
|||
|
|
<div class="container">
|
|||
|
|
<h1>🔲 018. 矩阵置零 <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="默认示例,可自定义">
|
|||
|
|
<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,1,1],[1,0,1],[1,1,1]], label: '3×3 含1个0'},
|
|||
|
|
{input: [[0,1,2,0],[3,4,5,2],[1,3,1,5]], label: '3×4 含2个0'},
|
|||
|
|
{input: [[1,2,3,4],[5,0,7,8],[9,10,11,12],[13,14,15,0]], label: '4×4 含2个0'},
|
|||
|
|
];
|
|||
|
|
|
|||
|
|
let origMatrix, steps, stepCtrl;
|
|||
|
|
|
|||
|
|
function buildSteps(mat) {
|
|||
|
|
origMatrix = mat.map(r => [...r]);
|
|||
|
|
const m = mat.length, n = mat[0].length;
|
|||
|
|
const work = mat.map(r => [...r]);
|
|||
|
|
steps = [];
|
|||
|
|
|
|||
|
|
let firstRowZero = false, firstColZero = false;
|
|||
|
|
|
|||
|
|
// --- Step 0: Init ---
|
|||
|
|
steps.push({
|
|||
|
|
stage: 'init', msg: '用第一行和第一列做标记空间,实现 O(1) 额外空间的矩阵置零',
|
|||
|
|
phase: 'init', firstRowZero: null, firstColZero: null,
|
|||
|
|
currentCell: null, markerCells: {}, zeroedCells: {},
|
|||
|
|
scanPos: null, scanRow: -1, scanCol: -1,
|
|||
|
|
codeLine: 1
|
|||
|
|
});
|
|||
|
|
|
|||
|
|
// --- Step 1: Scan first row for zeros ---
|
|||
|
|
for (let j = 0; j < n; j++) {
|
|||
|
|
if (work[0][j] === 0) firstRowZero = true;
|
|||
|
|
steps.push({
|
|||
|
|
stage: 'scan_first_row', msg: `检查第一行:matrix[0][${j}] = ${work[0][j]}${work[0][j]===0 ? ' → 发现0!' : ''}`,
|
|||
|
|
phase: 'scan', firstRowZero: firstRowZero || null, firstColZero: null,
|
|||
|
|
currentCell: `0,${j}`, markerCells: {}, zeroedCells: {},
|
|||
|
|
scanPos: `0,${j}`, scanRow: 0, scanCol: j,
|
|||
|
|
codeLine: 2
|
|||
|
|
});
|
|||
|
|
}
|
|||
|
|
steps.push({
|
|||
|
|
stage: 'first_row_result', msg: `第一行扫描完毕:${firstRowZero ? '✅ 含有0,首行需最终置零' : '❌ 无0,首行无需置零'}`,
|
|||
|
|
phase: 'scan', firstRowZero, firstColZero: null,
|
|||
|
|
currentCell: null, markerCells: {}, zeroedCells: {},
|
|||
|
|
scanPos: null, scanRow: 0, scanCol: -1,
|
|||
|
|
codeLine: 2
|
|||
|
|
});
|
|||
|
|
|
|||
|
|
// --- Step 2: Scan first col for zeros ---
|
|||
|
|
for (let i = 0; i < m; i++) {
|
|||
|
|
if (work[i][0] === 0) firstColZero = true;
|
|||
|
|
steps.push({
|
|||
|
|
stage: 'scan_first_col', msg: `检查第一列:matrix[${i}][0] = ${work[i][0]}${work[i][0]===0 ? ' → 发现0!' : ''}`,
|
|||
|
|
phase: 'scan', firstRowZero, firstColZero: firstColZero || null,
|
|||
|
|
currentCell: `${i},0`, markerCells: {}, zeroedCells: {},
|
|||
|
|
scanPos: `${i},0`, scanRow: i, scanCol: 0,
|
|||
|
|
codeLine: 3
|
|||
|
|
});
|
|||
|
|
}
|
|||
|
|
steps.push({
|
|||
|
|
stage: 'first_col_result', msg: `第一列扫描完毕:${firstColZero ? '✅ 含有0,首列需最终置零' : '❌ 无0,首列无需置零'}`,
|
|||
|
|
phase: 'scan', firstRowZero, firstColZero,
|
|||
|
|
currentCell: null, markerCells: {}, zeroedCells: {},
|
|||
|
|
scanPos: null, scanRow: -1, scanCol: 0,
|
|||
|
|
codeLine: 3
|
|||
|
|
});
|
|||
|
|
|
|||
|
|
// --- Step 3: Mark inner cells ---
|
|||
|
|
steps.push({
|
|||
|
|
stage: 'mark_start', msg: '扫描内部区域 (i≥1, j≥1):发现0则在对应的首行/首列位置做标记',
|
|||
|
|
phase: 'mark', firstRowZero, firstColZero,
|
|||
|
|
currentCell: null, markerCells: {}, zeroedCells: {},
|
|||
|
|
scanPos: null, scanRow: -1, scanCol: -1,
|
|||
|
|
codeLine: 5
|
|||
|
|
});
|
|||
|
|
|
|||
|
|
const markerCells = {};
|
|||
|
|
for (let i = 1; i < m; i++) {
|
|||
|
|
for (let j = 1; j < n; j++) {
|
|||
|
|
if (work[i][j] === 0) {
|
|||
|
|
markerCells[`${i},0`] = true;
|
|||
|
|
markerCells[`0,${j}`] = true;
|
|||
|
|
const snapshot = {...markerCells};
|
|||
|
|
steps.push({
|
|||
|
|
stage: 'mark', msg: `发现 matrix[${i}][${j}] = 0!→ 标记 row_marker[${i}][0] = 0,col_marker[0][${j}] = 0`,
|
|||
|
|
phase: 'mark', firstRowZero, firstColZero,
|
|||
|
|
currentCell: `${i},${j}`, markerCells: {...snapshot}, zeroedCells: {},
|
|||
|
|
scanPos: `${i},${j}`, scanRow: i, scanCol: j,
|
|||
|
|
codeLine: 7
|
|||
|
|
});
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
steps.push({
|
|||
|
|
stage: 'mark_done', msg: `标记阶段完毕,共在首行/首列做了 ${Object.keys(markerCells).length} 处标记`,
|
|||
|
|
phase: 'mark', firstRowZero, firstColZero,
|
|||
|
|
currentCell: null, markerCells: {...markerCells}, zeroedCells: {},
|
|||
|
|
scanPos: null, scanRow: -1, scanCol: -1,
|
|||
|
|
codeLine: 5
|
|||
|
|
});
|
|||
|
|
|
|||
|
|
// --- Step 4: Zero rows based on first column markers ---
|
|||
|
|
const zeroedCells = {};
|
|||
|
|
steps.push({
|
|||
|
|
stage: 'zero_start', msg: '根据标记,对内部区域置零:先按行标记置零,再按列标记置零',
|
|||
|
|
phase: 'zero', firstRowZero, firstColZero,
|
|||
|
|
currentCell: null, markerCells: {...markerCells}, zeroedCells: {},
|
|||
|
|
scanPos: null, scanRow: -1, scanCol: -1,
|
|||
|
|
codeLine: 9
|
|||
|
|
});
|
|||
|
|
|
|||
|
|
for (let i = 1; i < m; i++) {
|
|||
|
|
if (work[i][0] === 0) {
|
|||
|
|
steps.push({
|
|||
|
|
stage: 'zero_row', msg: `第一列标记 matrix[${i}][0] = 0 → 第${i}行全部置零`,
|
|||
|
|
phase: 'zero', firstRowZero, firstColZero,
|
|||
|
|
currentCell: `${i},0`, markerCells: {...markerCells}, zeroedCells: {...zeroedCells},
|
|||
|
|
scanPos: `${i},0`, scanRow: i, scanCol: -1,
|
|||
|
|
codeLine: 10
|
|||
|
|
});
|
|||
|
|
for (let j = 1; j < n; j++) {
|
|||
|
|
zeroedCells[`${i},${j}`] = true;
|
|||
|
|
}
|
|||
|
|
steps.push({
|
|||
|
|
stage: 'zero_row_done', msg: `第${i}行已置零(${n-1}个元素)`,
|
|||
|
|
phase: 'zero', firstRowZero, firstColZero,
|
|||
|
|
currentCell: null, markerCells: {...markerCells}, zeroedCells: {...zeroedCells},
|
|||
|
|
scanPos: null, scanRow: i, scanCol: -1,
|
|||
|
|
codeLine: 11
|
|||
|
|
});
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
for (let j = 1; j < n; j++) {
|
|||
|
|
if (work[0][j] === 0) {
|
|||
|
|
steps.push({
|
|||
|
|
stage: 'zero_col', msg: `第一行标记 matrix[0][${j}] = 0 → 第${j}列全部置零`,
|
|||
|
|
phase: 'zero', firstRowZero, firstColZero,
|
|||
|
|
currentCell: `0,${j}`, markerCells: {...markerCells}, zeroedCells: {...zeroedCells},
|
|||
|
|
scanPos: `0,${j}`, scanRow: -1, scanCol: j,
|
|||
|
|
codeLine: 13
|
|||
|
|
});
|
|||
|
|
for (let i = 1; i < m; i++) {
|
|||
|
|
zeroedCells[`${i},${j}`] = true;
|
|||
|
|
}
|
|||
|
|
steps.push({
|
|||
|
|
stage: 'zero_col_done', msg: `第${j}列已置零(${m-1}个元素)`,
|
|||
|
|
phase: 'zero', firstRowZero, firstColZero,
|
|||
|
|
currentCell: null, markerCells: {...markerCells}, zeroedCells: {...zeroedCells},
|
|||
|
|
scanPos: null, scanRow: -1, scanCol: j,
|
|||
|
|
codeLine: 14
|
|||
|
|
});
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// --- Step 5: First row ---
|
|||
|
|
if (firstRowZero) {
|
|||
|
|
for (let j = 0; j < n; j++) zeroedCells[`0,${j}`] = true;
|
|||
|
|
steps.push({
|
|||
|
|
stage: 'zero_first_row', msg: `首行有0标记,首行${n}个元素全部置零`,
|
|||
|
|
phase: 'first', firstRowZero, firstColZero,
|
|||
|
|
currentCell: null, markerCells: {}, zeroedCells: {...zeroedCells},
|
|||
|
|
scanPos: null, scanRow: 0, scanCol: -1,
|
|||
|
|
codeLine: 16
|
|||
|
|
});
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
if (firstColZero) {
|
|||
|
|
for (let i = 0; i < m; i++) zeroedCells[`${i},0`] = true;
|
|||
|
|
steps.push({
|
|||
|
|
stage: 'zero_first_col', msg: `首列有0标记,首列${m}个元素全部置零`,
|
|||
|
|
phase: 'first', firstRowZero, firstColZero,
|
|||
|
|
currentCell: null, markerCells: {}, zeroedCells: {...zeroedCells},
|
|||
|
|
scanPos: null, scanRow: -1, scanCol: 0,
|
|||
|
|
codeLine: 18
|
|||
|
|
});
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// --- Build result ---
|
|||
|
|
const result = mat.map(r => [...r]);
|
|||
|
|
for (const key of Object.keys(zeroedCells)) {
|
|||
|
|
const [r, c] = key.split(',').map(Number);
|
|||
|
|
result[r][c] = 0;
|
|||
|
|
}
|
|||
|
|
steps.push({
|
|||
|
|
stage: 'done', msg: '完成!矩阵已原地修改,空间复杂度 O(1)',
|
|||
|
|
phase: 'done', firstRowZero, firstColZero,
|
|||
|
|
currentCell: null, markerCells: {}, zeroedCells: {...zeroedCells}, result,
|
|||
|
|
scanPos: null, scanRow: -1, scanCol: -1,
|
|||
|
|
codeLine: -1
|
|||
|
|
});
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
function render(step) {
|
|||
|
|
const s = steps[step];
|
|||
|
|
const m = origMatrix.length, n = origMatrix[0].length;
|
|||
|
|
|
|||
|
|
// Build cell highlights
|
|||
|
|
function cellClass(val, r, c) {
|
|||
|
|
const key = `${r},${c}`;
|
|||
|
|
// Zeroed cells (red)
|
|||
|
|
if (s.zeroedCells[key]) return 'zeroed-cell';
|
|||
|
|
// Current cell
|
|||
|
|
if (s.currentCell === key) return 'current';
|
|||
|
|
// Marker cells in first row (purple)
|
|||
|
|
if (s.markerCells[key] && r === 0) return 'first-row-marker';
|
|||
|
|
// Marker cells in first col (cyan)
|
|||
|
|
if (s.markerCells[key] && c === 0) return 'first-col-marker';
|
|||
|
|
// Original zeros being scanned
|
|||
|
|
if (s.scanPos === key && val === 0) return 'origin-zero';
|
|||
|
|
return '';
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// Legend
|
|||
|
|
let viz = '<div class="legend">';
|
|||
|
|
viz += '<span><span class="dot" style="background:#fef3c7;border:1px solid #f59e0b;"></span> 原始0</span>';
|
|||
|
|
viz += '<span><span class="dot" style="background:#ede9fe;border:1px solid #8b5cf6;"></span> 首行标记</span>';
|
|||
|
|
viz += '<span><span class="dot" style="background:#cffafe;border:1px solid #06b6d4;"></span> 首列标记</span>';
|
|||
|
|
viz += '<span><span class="dot" style="background:#fee2e2;border:1px solid #ef4444;"></span> 已置零</span>';
|
|||
|
|
viz += '<span><span class="dot" style="background:#fef3c7;border:2px solid #f59e0b;box-shadow:0 0 0 2px rgba(245,158,11,.3);"></span> 当前位置</span>';
|
|||
|
|
viz += '</div>';
|
|||
|
|
|
|||
|
|
// Grid with row/col labels
|
|||
|
|
viz += '<div style="display:flex;gap:12px;flex-wrap:wrap;">';
|
|||
|
|
viz += '<div>';
|
|||
|
|
viz += '<div style="margin-bottom:6px;font-weight:600;font-size:14px;">原始矩阵</div>';
|
|||
|
|
viz += renderGrid(origMatrix, { cellClass, cellSize: 44 });
|
|||
|
|
viz += '</div>';
|
|||
|
|
|
|||
|
|
// Show result when available
|
|||
|
|
if (s.result) {
|
|||
|
|
viz += '<div>';
|
|||
|
|
viz += '<div style="margin-bottom:6px;font-weight:600;font-size:14px;">置零结果</div>';
|
|||
|
|
viz += renderGrid(s.result, { cellSize: 44 });
|
|||
|
|
viz += '</div>';
|
|||
|
|
}
|
|||
|
|
viz += '</div>';
|
|||
|
|
|
|||
|
|
// Phase info box
|
|||
|
|
if (s.phase === 'mark') {
|
|||
|
|
const markers = Object.keys(s.markerCells);
|
|||
|
|
const rowsMarked = [...new Set(markers.filter(k => k.split(',')[1]==='0').map(k=>k.split(',')[0]))];
|
|||
|
|
const colsMarked = [...new Set(markers.filter(k => k.split(',')[0]==='0').map(k=>k.split(',')[1]))];
|
|||
|
|
viz += '<div style="margin-top:12px;padding:10px 14px;background:#fef3c7;border-radius:8px;font-size:13px;line-height:1.7;">';
|
|||
|
|
viz += '<b>标记状态:</b>需置零的行 = [' + rowsMarked.join(', ') + '],需置零的列 = [' + colsMarked.join(', ') + ']';
|
|||
|
|
viz += '</div>';
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
if (s.phase === 'zero' || s.phase === 'first') {
|
|||
|
|
const count = Object.keys(s.zeroedCells).length;
|
|||
|
|
viz += '<div style="margin-top:12px;padding:10px 14px;background:#fee2e2;border-radius:8px;font-size:13px;line-height:1.7;">';
|
|||
|
|
viz += `<b>置零进度:</b>已置零 ${count} 个格子`;
|
|||
|
|
viz += '</div>';
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
$('vizArea').innerHTML = viz;
|
|||
|
|
|
|||
|
|
// Detail panel
|
|||
|
|
const phaseLabels = {init:'初始化', scan:'扫描首行首列', mark:'标记', zero:'根据标记置零', first:'首行首列置零', done:'完成'};
|
|||
|
|
const phaseClass = {init:'blue', scan:'blue', mark:'mark', zero:'zero', first:'first', done:'done'};
|
|||
|
|
let detail = `<div style="margin-bottom:8px;"><span class="phase-tag ${phaseClass[s.phase]||''}">${phaseLabels[s.phase]||s.phase}</span></div>`;
|
|||
|
|
detail += '<div class="calc-block">' + s.msg + '</div>';
|
|||
|
|
detail += '<div class="info-row">';
|
|||
|
|
if (s.firstRowZero !== null) detail += `<span class="tag ${s.firstRowZero?'red':'green'}">首行含0: ${s.firstRowZero?'是':'否'}</span>`;
|
|||
|
|
if (s.firstColZero !== null) detail += `<span class="tag ${s.firstColZero?'red':'green'}">首列含0: ${s.firstColZero?'是':'否'}</span>`;
|
|||
|
|
const mCount = Object.keys(s.markerCells).length;
|
|||
|
|
if (mCount > 0) detail += `<span class="tag purple">标记数: ${mCount}</span>`;
|
|||
|
|
const zCount = Object.keys(s.zeroedCells).length;
|
|||
|
|
if (zCount > 0) detail += `<span class="tag red">已置零: ${zCount}</span>`;
|
|||
|
|
detail += '</div>';
|
|||
|
|
$('detailContent').innerHTML = detail;
|
|||
|
|
|
|||
|
|
// Result panel
|
|||
|
|
if (s.stage === 'done') {
|
|||
|
|
const rows = s.result.map(r => '[' + r.join(',') + ']');
|
|||
|
|
$('resultContent').innerHTML = `<div class="final-answer">置零结果:<b>[${rows.join(', ')}]</b><div class="complexity">时间 O(mn) | 空间 O(1)</div></div>`;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
$('hintText').textContent = s.msg;
|
|||
|
|
const pipe = [
|
|||
|
|
['init','初始化'],['scan_first_row','扫描首行'],['scan_first_col','扫描首列'],
|
|||
|
|
['mark','标记0位置'],['zero_row','行置零'],['zero_col','列置零'],
|
|||
|
|
['zero_first_row','首行置零'],['zero_first_col','首列置零'],['done','完成']
|
|||
|
|
];
|
|||
|
|
const stageToPhase = s => {
|
|||
|
|
if (s.startsWith('scan_first_row')) return 'scan_first_row';
|
|||
|
|
if (s.startsWith('scan_first_col')) return 'scan_first_col';
|
|||
|
|
if (s.startsWith('mark')) return 'mark';
|
|||
|
|
if (s.startsWith('zero_row')) return 'zero_row';
|
|||
|
|
if (s.startsWith('zero_col')) return 'zero_col';
|
|||
|
|
if (s.startsWith('zero_first_row')) return 'zero_first_row';
|
|||
|
|
if (s.startsWith('zero_first_col')) return 'zero_first_col';
|
|||
|
|
return s;
|
|||
|
|
};
|
|||
|
|
const activePhase = stageToPhase(s.stage);
|
|||
|
|
$('pipeline').innerHTML = pipe.map(([k,l]) =>
|
|||
|
|
`<span class="pipe-step ${activePhase===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 = JSON.stringify(examples[0].input);
|
|||
|
|
|
|||
|
|
buildSteps(examples[0].input);
|
|||
|
|
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 mat = JSON.parse($('inputArea').value);
|
|||
|
|
if (!Array.isArray(mat) || !Array.isArray(mat[0])) throw 0;
|
|||
|
|
buildSteps(mat);
|
|||
|
|
stepCtrl.setSteps(steps.map((_, i) => i));
|
|||
|
|
render(0);
|
|||
|
|
$('stepInfo').textContent = `步骤 1 / ${steps.length}`;
|
|||
|
|
} catch (e) { alert('请输入合法 JSON 二维数组,如 [[1,1,1],[1,0,1],[1,1,1]]'); }
|
|||
|
|
};
|
|||
|
|
$('exampleSelect').onchange = () => {
|
|||
|
|
const e = examples[parseInt($('exampleSelect').value)];
|
|||
|
|
$('inputArea').value = JSON.stringify(e.input);
|
|||
|
|
buildSteps(e.input);
|
|||
|
|
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 setZeroes(matrix):
|
|||
|
|
m, n = len(matrix), len(matrix[0])
|
|||
|
|
# 1. 记录首行/首列是否本身含0
|
|||
|
|
first_row_zero = any(matrix[0][j] == 0 for j in range(n))
|
|||
|
|
first_col_zero = any(matrix[i][0] == 0 for i in range(m))
|
|||
|
|
|
|||
|
|
# 2. 用首行/首列作为标记:内部0对应位置置0
|
|||
|
|
for i in range(1, m):
|
|||
|
|
for j in range(1, n):
|
|||
|
|
if matrix[i][j] == 0:
|
|||
|
|
matrix[i][0] = 0 # 行标记
|
|||
|
|
matrix[0][j] = 0 # 列标记
|
|||
|
|
|
|||
|
|
# 3. 根据标记对内部区域置零
|
|||
|
|
for i in range(1, m):
|
|||
|
|
if matrix[i][0] == 0:
|
|||
|
|
for j in range(1, n):
|
|||
|
|
matrix[i][j] = 0
|
|||
|
|
for j in range(1, n):
|
|||
|
|
if matrix[0][j] == 0:
|
|||
|
|
for i in range(1, m):
|
|||
|
|
matrix[i][j] = 0
|
|||
|
|
|
|||
|
|
# 4. 处理首行/首列
|
|||
|
|
if first_row_zero:
|
|||
|
|
for j in range(n):
|
|||
|
|
matrix[0][j] = 0
|
|||
|
|
if first_col_zero:
|
|||
|
|
for i in range(m):
|
|||
|
|
matrix[i][0] = 0`, { lang: 'Python' });
|
|||
|
|
})();
|
|||
|
|
</script>
|
|||
|
|
</body>
|
|||
|
|
</html>
|