Skip to content

Commit 2355d14

Browse files
committed
完成5个待办任务:画布拖拽缩放、树居中、状态标签、算法思路弹窗、代码注释
1 parent 601ccef commit 2355d14

File tree

11 files changed

+1670
-224
lines changed

11 files changed

+1670
-224
lines changed

src/algorithm/diameterAlgorithm.ts

Lines changed: 310 additions & 147 deletions
Large diffs are not rendered by default.
Lines changed: 239 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,239 @@
1+
/* 弹窗遮罩层 */
2+
.modal-overlay {
3+
position: fixed;
4+
top: 0;
5+
left: 0;
6+
right: 0;
7+
bottom: 0;
8+
background: rgba(0, 0, 0, 0.75);
9+
display: flex;
10+
align-items: center;
11+
justify-content: center;
12+
z-index: 1000;
13+
padding: 20px;
14+
backdrop-filter: blur(4px);
15+
animation: fadeIn 0.2s ease;
16+
}
17+
18+
@keyframes fadeIn {
19+
from { opacity: 0; }
20+
to { opacity: 1; }
21+
}
22+
23+
/* 弹窗内容 */
24+
.modal-content {
25+
background: #1a1a2e;
26+
border-radius: 12px;
27+
max-width: 700px;
28+
width: 100%;
29+
max-height: 85vh;
30+
overflow-y: auto;
31+
position: relative;
32+
border: 1px solid rgba(255, 161, 22, 0.3);
33+
box-shadow: 0 20px 60px rgba(0, 0, 0, 0.5);
34+
animation: slideUp 0.3s ease;
35+
}
36+
37+
@keyframes slideUp {
38+
from {
39+
opacity: 0;
40+
transform: translateY(20px);
41+
}
42+
to {
43+
opacity: 1;
44+
transform: translateY(0);
45+
}
46+
}
47+
48+
/* 关闭按钮 */
49+
.modal-close {
50+
position: absolute;
51+
top: 16px;
52+
right: 16px;
53+
background: rgba(255, 255, 255, 0.1);
54+
border: none;
55+
border-radius: 8px;
56+
width: 36px;
57+
height: 36px;
58+
display: flex;
59+
align-items: center;
60+
justify-content: center;
61+
cursor: pointer;
62+
color: #a0a0a0;
63+
transition: all 0.2s ease;
64+
}
65+
66+
.modal-close:hover {
67+
background: rgba(255, 107, 107, 0.2);
68+
color: #ff6b6b;
69+
}
70+
71+
/* 标题 */
72+
.modal-title {
73+
padding: 24px 24px 16px;
74+
margin: 0;
75+
font-size: 22px;
76+
font-weight: 700;
77+
color: #ffa116;
78+
display: flex;
79+
align-items: center;
80+
gap: 10px;
81+
border-bottom: 1px solid rgba(255, 255, 255, 0.1);
82+
}
83+
84+
.title-icon {
85+
font-size: 24px;
86+
}
87+
88+
/* 弹窗主体 */
89+
.modal-body {
90+
padding: 20px 24px 24px;
91+
}
92+
93+
/* 章节 */
94+
.algo-section {
95+
margin-bottom: 24px;
96+
}
97+
98+
.algo-section:last-child {
99+
margin-bottom: 0;
100+
}
101+
102+
.algo-section h3 {
103+
font-size: 16px;
104+
font-weight: 600;
105+
color: #4ecdc4;
106+
margin: 0 0 12px 0;
107+
}
108+
109+
.algo-section p {
110+
font-size: 14px;
111+
line-height: 1.7;
112+
color: #d0d0d0;
113+
margin: 0 0 10px 0;
114+
}
115+
116+
.algo-section strong {
117+
color: #ffa116;
118+
}
119+
120+
/* 公式框 */
121+
.formula-box {
122+
background: rgba(255, 161, 22, 0.1);
123+
border: 1px solid rgba(255, 161, 22, 0.3);
124+
border-radius: 8px;
125+
padding: 12px 16px;
126+
margin: 12px 0;
127+
}
128+
129+
.formula-box code {
130+
font-family: 'Fira Code', 'Consolas', monospace;
131+
font-size: 14px;
132+
color: #ffa116;
133+
}
134+
135+
/* 步骤列表 */
136+
.step-list {
137+
margin: 0;
138+
padding-left: 20px;
139+
color: #d0d0d0;
140+
}
141+
142+
.step-list li {
143+
margin-bottom: 10px;
144+
line-height: 1.6;
145+
font-size: 14px;
146+
}
147+
148+
.step-list li strong {
149+
color: #a78bfa;
150+
}
151+
152+
/* 示例框 */
153+
.example-box {
154+
background: rgba(78, 205, 196, 0.1);
155+
border: 1px solid rgba(78, 205, 196, 0.3);
156+
border-radius: 8px;
157+
padding: 16px;
158+
overflow-x: auto;
159+
}
160+
161+
.example-box pre {
162+
margin: 0;
163+
font-family: 'Fira Code', 'Consolas', monospace;
164+
font-size: 13px;
165+
line-height: 1.5;
166+
color: #e0e0e0;
167+
white-space: pre;
168+
}
169+
170+
/* 复杂度列表 */
171+
.complexity-list {
172+
margin: 0;
173+
padding-left: 20px;
174+
color: #d0d0d0;
175+
}
176+
177+
.complexity-list li {
178+
margin-bottom: 8px;
179+
font-size: 14px;
180+
line-height: 1.6;
181+
}
182+
183+
.complexity-list strong {
184+
color: #60a5fa;
185+
}
186+
187+
/* 代码框 */
188+
.code-box {
189+
background: #0d0d1a;
190+
border: 1px solid rgba(255, 255, 255, 0.1);
191+
border-radius: 8px;
192+
padding: 16px;
193+
overflow-x: auto;
194+
}
195+
196+
.code-box pre {
197+
margin: 0;
198+
font-family: 'Fira Code', 'Consolas', monospace;
199+
font-size: 13px;
200+
line-height: 1.5;
201+
color: #e0e0e0;
202+
white-space: pre;
203+
}
204+
205+
/* 关键点列表 */
206+
.key-points {
207+
margin: 0;
208+
padding-left: 20px;
209+
color: #d0d0d0;
210+
}
211+
212+
.key-points li {
213+
margin-bottom: 8px;
214+
font-size: 14px;
215+
line-height: 1.6;
216+
}
217+
218+
.key-points strong {
219+
color: #22c55e;
220+
}
221+
222+
/* 滚动条样式 */
223+
.modal-content::-webkit-scrollbar {
224+
width: 8px;
225+
}
226+
227+
.modal-content::-webkit-scrollbar-track {
228+
background: rgba(255, 255, 255, 0.05);
229+
border-radius: 4px;
230+
}
231+
232+
.modal-content::-webkit-scrollbar-thumb {
233+
background: rgba(255, 161, 22, 0.3);
234+
border-radius: 4px;
235+
}
236+
237+
.modal-content::-webkit-scrollbar-thumb:hover {
238+
background: rgba(255, 161, 22, 0.5);
239+
}
Lines changed: 151 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,151 @@
1+
import { useEffect, useCallback } from 'react';
2+
import './AlgorithmModal.css';
3+
4+
interface AlgorithmModalProps {
5+
isOpen: boolean;
6+
onClose: () => void;
7+
}
8+
9+
/**
10+
* 算法思路弹窗组件
11+
* 详细解释二叉树直径算法的核心思路
12+
*/
13+
export function AlgorithmModal({ isOpen, onClose }: AlgorithmModalProps) {
14+
// ESC键关闭弹窗
15+
const handleKeyDown = useCallback((e: KeyboardEvent) => {
16+
if (e.key === 'Escape') {
17+
onClose();
18+
}
19+
}, [onClose]);
20+
21+
useEffect(() => {
22+
if (isOpen) {
23+
document.addEventListener('keydown', handleKeyDown);
24+
document.body.style.overflow = 'hidden';
25+
}
26+
return () => {
27+
document.removeEventListener('keydown', handleKeyDown);
28+
document.body.style.overflow = '';
29+
};
30+
}, [isOpen, handleKeyDown]);
31+
32+
if (!isOpen) return null;
33+
34+
return (
35+
<div className="modal-overlay" onClick={onClose}>
36+
<div className="modal-content" onClick={e => e.stopPropagation()}>
37+
<button className="modal-close" onClick={onClose} title="关闭">
38+
<svg viewBox="0 0 24 24" width="20" height="20">
39+
<path fill="currentColor" d="M19,6.41L17.59,5L12,10.59L6.41,5L5,6.41L10.59,12L5,17.59L6.41,19L12,13.41L17.59,19L19,17.59L13.41,12L19,6.41Z" />
40+
</svg>
41+
</button>
42+
43+
<h2 className="modal-title">
44+
<span className="title-icon">💡</span>
45+
算法思路详解
46+
</h2>
47+
48+
<div className="modal-body">
49+
<section className="algo-section">
50+
<h3>📌 问题定义</h3>
51+
<p>
52+
<strong>二叉树的直径</strong>是指树中任意两个节点之间最长路径的<strong>边数</strong>
53+
这条路径可能经过根节点,也可能不经过。
54+
</p>
55+
</section>
56+
57+
<section className="algo-section">
58+
<h3>🎯 核心思路</h3>
59+
<p>对于每个节点,经过该节点的最长路径 = <strong>左子树深度 + 右子树深度</strong></p>
60+
<div className="formula-box">
61+
<code>diameter = max(diameter, leftDepth + rightDepth)</code>
62+
</div>
63+
<p>我们需要遍历所有节点,找出经过每个节点的最长路径,取最大值即为答案。</p>
64+
</section>
65+
66+
<section className="algo-section">
67+
<h3>🔄 递归过程</h3>
68+
<ol className="step-list">
69+
<li>
70+
<strong>递归计算深度</strong>:对于每个节点,递归计算其左右子树的深度
71+
</li>
72+
<li>
73+
<strong>更新直径</strong>:在每个节点处,用 leftDepth + rightDepth 更新全局最大直径
74+
</li>
75+
<li>
76+
<strong>返回深度</strong>:返回 max(leftDepth, rightDepth) + 1 作为当前子树的深度
77+
</li>
78+
</ol>
79+
</section>
80+
81+
<section className="algo-section">
82+
<h3>📊 示例分析</h3>
83+
<div className="example-box">
84+
<pre>{`
85+
1
86+
/ \\
87+
2 3
88+
/ \\
89+
4 5
90+
91+
节点4: 左深度=0, 右深度=0, 经过的路径=0
92+
节点5: 左深度=0, 右深度=0, 经过的路径=0
93+
节点2: 左深度=1, 右深度=1, 经过的路径=2
94+
节点3: 左深度=0, 右深度=0, 经过的路径=0
95+
节点1: 左深度=2, 右深度=1, 经过的路径=3 ✓
96+
97+
最长路径: 4 → 2 → 1 → 3 (或 5 → 2 → 1 → 3)
98+
直径 = 3
99+
`}</pre>
100+
</div>
101+
</section>
102+
103+
<section className="algo-section">
104+
<h3>⏱️ 复杂度分析</h3>
105+
<ul className="complexity-list">
106+
<li><strong>时间复杂度</strong>:O(n),每个节点只访问一次</li>
107+
<li><strong>空间复杂度</strong>:O(h),h为树的高度,递归栈的深度</li>
108+
</ul>
109+
</section>
110+
111+
<section className="algo-section">
112+
<h3>💻 代码实现</h3>
113+
<div className="code-box">
114+
<pre>{`class Solution {
115+
private int diameter = 0;
116+
117+
public int diameterOfBinaryTree(TreeNode root) {
118+
depth(root);
119+
return diameter;
120+
}
121+
122+
private int depth(TreeNode node) {
123+
if (node == null) return 0;
124+
125+
int leftDepth = depth(node.left);
126+
int rightDepth = depth(node.right);
127+
128+
// 更新直径:经过当前节点的最长路径
129+
diameter = Math.max(diameter, leftDepth + rightDepth);
130+
131+
// 返回当前子树的深度
132+
return Math.max(leftDepth, rightDepth) + 1;
133+
}
134+
}`}</pre>
135+
</div>
136+
</section>
137+
138+
<section className="algo-section">
139+
<h3>🔑 关键点</h3>
140+
<ul className="key-points">
141+
<li>直径是<strong>边数</strong>,不是节点数</li>
142+
<li>最长路径<strong>不一定经过根节点</strong></li>
143+
<li>利用后序遍历,在返回深度的同时更新直径</li>
144+
<li>使用全局变量记录最大直径,避免重复计算</li>
145+
</ul>
146+
</section>
147+
</div>
148+
</div>
149+
</div>
150+
);
151+
}

src/components/CodePanel/CodePanel.css

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
.code-panel {
2-
width: 420px;
2+
width: 546px;
33
display: flex;
44
flex-direction: column;
55
background: rgba(0, 0, 0, 0.3);

0 commit comments

Comments
 (0)