Skip to content

Commit 42c2153

Browse files
committed
feat: 添加算法思路弹窗功能
1 parent d840b1e commit 42c2153

File tree

3 files changed

+241
-0
lines changed

3 files changed

+241
-0
lines changed
Lines changed: 148 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,148 @@
1+
.hintButton {
2+
display: flex;
3+
align-items: center;
4+
gap: 6px;
5+
padding: 6px 12px;
6+
background-color: #334155;
7+
color: #e2e8f0;
8+
border: none;
9+
border-radius: 6px;
10+
cursor: pointer;
11+
font-size: 13px;
12+
font-weight: 500;
13+
transition: background-color 0.2s, transform 0.2s;
14+
}
15+
16+
.hintButton:hover {
17+
background-color: #475569;
18+
transform: translateY(-1px);
19+
}
20+
21+
.icon {
22+
width: 18px;
23+
height: 18px;
24+
color: #fbbf24;
25+
}
26+
27+
.overlay {
28+
position: fixed;
29+
top: 0;
30+
left: 0;
31+
right: 0;
32+
bottom: 0;
33+
background-color: rgba(0, 0, 0, 0.6);
34+
display: flex;
35+
align-items: center;
36+
justify-content: center;
37+
z-index: 1000;
38+
animation: fadeIn 0.2s ease-out;
39+
}
40+
41+
@keyframes fadeIn {
42+
from {
43+
opacity: 0;
44+
}
45+
to {
46+
opacity: 1;
47+
}
48+
}
49+
50+
.modal {
51+
background-color: #1e293b;
52+
border: 1px solid #334155;
53+
border-radius: 12px;
54+
width: 90%;
55+
max-width: 600px;
56+
max-height: 80vh;
57+
overflow: hidden;
58+
display: flex;
59+
flex-direction: column;
60+
animation: slideIn 0.2s ease-out;
61+
}
62+
63+
@keyframes slideIn {
64+
from {
65+
opacity: 0;
66+
transform: translateY(-20px);
67+
}
68+
to {
69+
opacity: 1;
70+
transform: translateY(0);
71+
}
72+
}
73+
74+
.header {
75+
display: flex;
76+
align-items: center;
77+
justify-content: space-between;
78+
padding: 16px 20px;
79+
border-bottom: 1px solid #334155;
80+
}
81+
82+
.title {
83+
margin: 0;
84+
font-size: 18px;
85+
font-weight: 600;
86+
color: #f1f5f9;
87+
}
88+
89+
.closeButton {
90+
display: flex;
91+
align-items: center;
92+
justify-content: center;
93+
width: 32px;
94+
height: 32px;
95+
background-color: transparent;
96+
border: none;
97+
border-radius: 6px;
98+
cursor: pointer;
99+
color: #94a3b8;
100+
transition: background-color 0.2s, color 0.2s;
101+
}
102+
103+
.closeButton:hover {
104+
background-color: #334155;
105+
color: #e2e8f0;
106+
}
107+
108+
.closeButton svg {
109+
width: 20px;
110+
height: 20px;
111+
}
112+
113+
.content {
114+
padding: 20px;
115+
overflow-y: auto;
116+
color: #e2e8f0;
117+
line-height: 1.6;
118+
}
119+
120+
.h2 {
121+
margin: 0 0 16px;
122+
font-size: 20px;
123+
font-weight: 600;
124+
color: #60a5fa;
125+
}
126+
127+
.h3 {
128+
margin: 16px 0 8px;
129+
font-size: 16px;
130+
font-weight: 600;
131+
color: #22c55e;
132+
}
133+
134+
.p {
135+
margin: 4px 0;
136+
color: #cbd5e1;
137+
}
138+
139+
.numbered {
140+
margin: 8px 0;
141+
padding-left: 8px;
142+
color: #e2e8f0;
143+
}
144+
145+
.li {
146+
margin: 4px 0 4px 20px;
147+
color: #cbd5e1;
148+
}
Lines changed: 92 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,92 @@
1+
import { useState } from 'react';
2+
import styles from './AlgorithmHint.module.css';
3+
4+
const ALGORITHM_HINT = `## 动态规划解法
5+
6+
### 问题分析
7+
给定一个 m × n 的网格,每个格子包含一个非负整数,找出一条从左上角到右下角的路径,使得路径上的数字总和最小。
8+
9+
### 核心思路
10+
1. **状态定义**:dp[i][j] 表示从起点 (0,0) 到达位置 (i,j) 的最小路径和
11+
12+
2. **状态转移方程**:
13+
- 第一个格子:dp[0][0] = grid[0][0]
14+
- 第一列:dp[i][0] = dp[i-1][0] + grid[i][0](只能从上方来)
15+
- 第一行:dp[0][j] = dp[0][j-1] + grid[0][j](只能从左方来)
16+
- 其他格子:dp[i][j] = min(dp[i-1][j], dp[i][j-1]) + grid[i][j]
17+
18+
3. **边界条件**:
19+
- 起点 dp[0][0] = grid[0][0]
20+
- 第一行和第一列只有一种走法
21+
22+
### 时间复杂度
23+
O(m × n),需要遍历整个网格
24+
25+
### 空间复杂度
26+
O(m × n),需要一个同样大小的 dp 数组(可优化为 O(n))
27+
28+
### 关键点
29+
- 每个位置只能从上方或左方到达
30+
- 使用动态规划避免重复计算
31+
- 最终答案在 dp[m-1][n-1]`;
32+
33+
export function AlgorithmHint() {
34+
const [isOpen, setIsOpen] = useState(false);
35+
36+
return (
37+
<>
38+
<button
39+
className={styles.hintButton}
40+
onClick={() => setIsOpen(true)}
41+
title="查看算法思路"
42+
>
43+
<svg
44+
className={styles.icon}
45+
viewBox="0 0 24 24"
46+
fill="currentColor"
47+
>
48+
<path d="M9 21c0 .55.45 1 1 1h4c.55 0 1-.45 1-1v-1H9v1zm3-19C8.14 2 5 5.14 5 9c0 2.38 1.19 4.47 3 5.74V17c0 .55.45 1 1 1h6c.55 0 1-.45 1-1v-2.26c1.81-1.27 3-3.36 3-5.74 0-3.86-3.14-7-7-7zm2.85 11.1l-.85.6V16h-4v-2.3l-.85-.6A4.997 4.997 0 017 9c0-2.76 2.24-5 5-5s5 2.24 5 5c0 1.63-.8 3.16-2.15 4.1z" />
49+
</svg>
50+
<span>算法思路</span>
51+
</button>
52+
53+
{isOpen && (
54+
<div className={styles.overlay} onClick={() => setIsOpen(false)}>
55+
<div className={styles.modal} onClick={(e) => e.stopPropagation()}>
56+
<div className={styles.header}>
57+
<h2 className={styles.title}>算法思路</h2>
58+
<button
59+
className={styles.closeButton}
60+
onClick={() => setIsOpen(false)}
61+
>
62+
<svg viewBox="0 0 24 24" fill="currentColor">
63+
<path d="M19 6.41L17.59 5 12 10.59 6.41 5 5 6.41 10.59 12 5 17.59 6.41 19 12 13.41 17.59 19 19 17.59 13.41 12z" />
64+
</svg>
65+
</button>
66+
</div>
67+
<div className={styles.content}>
68+
{ALGORITHM_HINT.split('\n').map((line, index) => {
69+
if (line.startsWith('## ')) {
70+
return <h2 key={index} className={styles.h2}>{line.slice(3)}</h2>;
71+
}
72+
if (line.startsWith('### ')) {
73+
return <h3 key={index} className={styles.h3}>{line.slice(4)}</h3>;
74+
}
75+
if (line.startsWith('- ')) {
76+
return <li key={index} className={styles.li}>{line.slice(2)}</li>;
77+
}
78+
if (line.match(/^\d+\./)) {
79+
return <p key={index} className={styles.numbered}>{line}</p>;
80+
}
81+
if (line.trim() === '') {
82+
return <br key={index} />;
83+
}
84+
return <p key={index} className={styles.p}>{line}</p>;
85+
})}
86+
</div>
87+
</div>
88+
</div>
89+
)}
90+
</>
91+
);
92+
}
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
export { AlgorithmHint } from './AlgorithmHint';

0 commit comments

Comments
 (0)