387 lines
7.6 KiB
Markdown
387 lines
7.6 KiB
Markdown
# 算法解题思路改进方案
|
||
|
||
## 当前状态分析
|
||
|
||
经过分析,现有LeetCode题目的解题思路已经包含了:
|
||
- ✅ 核心思想
|
||
- ✅ 算法流程
|
||
- ✅ 复杂度分析
|
||
- ✅ 代码实现
|
||
- ✅ 部分题目的图解
|
||
|
||
## 改进方向
|
||
|
||
### 1. 解题思路部分需要增强
|
||
|
||
**改进前** (以三数之和为例):
|
||
```markdown
|
||
### 核心思想
|
||
**排序 + 双指针**:先排序,固定第一个数,再用双指针找后两个数。
|
||
|
||
### 算法流程
|
||
1. **排序数组**:便于去重和双指针操作
|
||
2. **遍历第一个数**:
|
||
- 跳过重复元素
|
||
- 如果当前数 > 0,直接退出
|
||
3. **双指针找后两个数**:
|
||
- left = i + 1, right = len(nums) - 1
|
||
- 根据 sum 与 0 的关系移动指针
|
||
```
|
||
|
||
**改进后**:
|
||
```markdown
|
||
### 核心思想
|
||
**排序 + 双指针**:先排序,固定第一个数,再用双指针找后两个数。
|
||
|
||
**为什么这样思考?**
|
||
1. **排序的作用**:
|
||
- 去除重复结果(相同数相邻)
|
||
- 使数组有序,才能使用双指针
|
||
- 提前终止(如果当前数>0,后面都>0)
|
||
|
||
2. **双指针的原理**:
|
||
- 数组有序后,如果 sum < target,需要增大 → left++
|
||
- 如果 sum > target,需要减小 → right--
|
||
- 利用单调性,避免暴力枚举
|
||
|
||
3. **降维思想**:
|
||
- 三数之和 → 固定一个数 → 两数之和
|
||
- O(n³) → O(n²)
|
||
|
||
### 解题思路推导过程
|
||
|
||
**第一步:暴力解法分析**
|
||
```
|
||
暴力解法:三层循环枚举所有可能
|
||
for i in range(n):
|
||
for j in range(i+1, n):
|
||
for k in range(j+1, n):
|
||
if nums[i] + nums[j] + nums[k] == 0:
|
||
result.add([nums[i], nums[j], nums[k]])
|
||
|
||
时间复杂度: O(n³)
|
||
空间复杂度: O(1)
|
||
问题:效率太低,无法通过
|
||
```
|
||
|
||
**第二步:优化思考 - 能否降维?**
|
||
- 观察到固定第一个数后,问题变成"两数之和"
|
||
- 两数之和可以用双指针 O(n) 解决
|
||
- 总复杂度: O(n) × O(n) = O(n²)
|
||
|
||
**第三步:双指针的前提条件**
|
||
- 为什么排序后才能用双指针?
|
||
- 如果 nums[left] + nums[right] < 0
|
||
- 由于数组升序,增大left → 和会变大
|
||
- 减小right → 和会变小
|
||
- 这就是"单调性"的作用
|
||
|
||
### 详细算法流程
|
||
|
||
**步骤1:预处理 - 排序**
|
||
```python
|
||
nums.sort() # O(n log n)
|
||
```
|
||
- 作用:去重、双指针基础、提前终止
|
||
|
||
**步骤2:外层循环 - 固定第一个数**
|
||
```python
|
||
for i in range(len(nums) - 2):
|
||
# 去重:跳过重复元素
|
||
if i > 0 and nums[i] == nums[i-1]:
|
||
continue
|
||
|
||
# 提前终止:如果最小数>0,后面不可能=0
|
||
if nums[i] > 0:
|
||
break
|
||
|
||
# 双指针找后两个数
|
||
twoSum(nums, i+1, -nums[i])
|
||
```
|
||
|
||
**关键点**:
|
||
- 为什么循环到 `len(nums)-2`? 需要留2个数给双指针
|
||
- 为什么判断 `i > 0`? 第一个元素不用判断重复
|
||
|
||
**步骤3:内层双指针 - 两数之和**
|
||
```python
|
||
def twoSum(nums, start, target):
|
||
left, right = start, len(nums) - 1
|
||
|
||
while left < right:
|
||
current_sum = nums[left] + nums[right]
|
||
|
||
if current_sum == target:
|
||
result.append([-target, nums[left], nums[right]])
|
||
|
||
# 去重:跳过重复的left
|
||
while left < right and nums[left] == nums[left+1]:
|
||
left += 1
|
||
# 去重:跳过重复的right
|
||
while left < right and nums[right] == nums[right-1]:
|
||
right -= 1
|
||
|
||
# 同时移动,寻找下一组解
|
||
left += 1
|
||
right -= 1
|
||
|
||
elif current_sum < target:
|
||
left += 1 # 需要更大的和
|
||
else:
|
||
right -= 1 # 需要更小的和
|
||
```
|
||
|
||
**关键点**:
|
||
- 为什么找到答案后还要跳过重复? 避免重复结果
|
||
- 为什么找到答案后要同时移动? 继续寻找其他组合
|
||
|
||
### 关键细节说明
|
||
|
||
**细节1:为什么是 `if i > 0`?**
|
||
```python
|
||
# 错误写法
|
||
if nums[i] == nums[i-1]: # i=0时会越界!
|
||
|
||
# 正确写法
|
||
if i > 0 and nums[i] == nums[i-1]: # 第一个元素不用判断
|
||
```
|
||
|
||
**细节2:为什么找到答案后要同时移动?**
|
||
```
|
||
假设: [-2, 0, 1, 1, 2]
|
||
i L R
|
||
|
||
找到: -2 + 0 + 2 = 0 ✓
|
||
|
||
如果只移动一个指针:
|
||
- L++: [-2, 0, 1, 1, 2] → -2 + 1 + 2 = 1 > 0 → R--
|
||
但这样可能错过其他组合
|
||
|
||
正确做法:同时移动
|
||
- L++ and R--: 继续寻找其他可能的组合
|
||
```
|
||
|
||
**细节3:为什么break而不是continue?**
|
||
```python
|
||
if nums[i] > 0:
|
||
break # 正确:后面的数都>0,不可能和为0
|
||
# continue # 错误:会继续无意义的循环
|
||
```
|
||
```
|
||
### 边界条件分析
|
||
|
||
**边界1:数组长度不足**
|
||
```
|
||
输入: [0, 1]
|
||
输出: []
|
||
原因:长度<3,无法组成三元组
|
||
```
|
||
|
||
**边界2:全部为0**
|
||
```
|
||
输入: [0, 0, 0, 0]
|
||
输出: [[0, 0, 0]]
|
||
去重逻辑:只保留一个组合
|
||
```
|
||
|
||
**边界3:有重复元素**
|
||
```
|
||
输入: [-1, -1, 0, 1]
|
||
输出: [[-1, 0, 1]]
|
||
去重逻辑:跳过第二个-1
|
||
```
|
||
|
||
### 复杂度分析(详细版)
|
||
|
||
**时间复杂度**:
|
||
```
|
||
- 排序: O(n log n)
|
||
- 外层循环: O(n)
|
||
- 内层双指针: O(n)
|
||
- 总计: O(n log n) + O(n²) = O(n²)
|
||
|
||
为什么主项是O(n²)?
|
||
- n² >> n log n (当n较大时)
|
||
- 渐近复杂度取最高阶
|
||
```
|
||
|
||
**空间复杂度**:
|
||
```
|
||
- 排序:O(log n) (快速排序栈空间)
|
||
- 结果存储:O(k) (k为结果数量)
|
||
- 指针变量:O(1)
|
||
- 总计:O(log n) (不考虑结果存储)
|
||
```
|
||
```
|
||
|
||
### 2. 增加思考过程部分
|
||
|
||
在"解题思路"之前,增加"思路推导"部分:
|
||
- 从暴力解法开始
|
||
- 分析暴力解法的瓶颈
|
||
- 逐步优化思路
|
||
- 最终得到最优解
|
||
|
||
### 3. 增加图解说明
|
||
|
||
为每个关键步骤添加:
|
||
- 初始状态图
|
||
- 中间过程图
|
||
- 最终结果图
|
||
- 使用ASCII art或文字描述
|
||
|
||
### 4. 增加常见错误
|
||
|
||
列出易错点:
|
||
- 边界条件错误
|
||
- 去重逻辑错误
|
||
- 指针移动条件错误
|
||
- 提前终止条件错误
|
||
|
||
### 5. 增加变体问题
|
||
|
||
扩展到相关题目:
|
||
- 参数变化(四数之和)
|
||
- 条件变化(最接近的和)
|
||
- 返回值变化(返回索引而非值)
|
||
|
||
## 改进后的模板
|
||
|
||
```markdown
|
||
# [题目名称]
|
||
|
||
LeetCode [题号]. [难度]
|
||
|
||
## 题目描述
|
||
[题目原文]
|
||
|
||
## 思路推导
|
||
|
||
### 暴力解法分析
|
||
```python
|
||
[暴力解法代码]
|
||
```
|
||
**时间复杂度**: O(?)
|
||
**问题**: [分析瓶颈]
|
||
|
||
### 优化思考
|
||
**观察**: [发现规律或可优化点]
|
||
**思路**: [优化方向]
|
||
|
||
### 为什么这样思考?
|
||
1. [核心原理1]
|
||
2. [核心原理2]
|
||
3. [降维/转换思想]
|
||
|
||
## 解题思路
|
||
|
||
### 核心思想
|
||
[一句话总结]
|
||
|
||
### 算法流程(详细版)
|
||
|
||
**步骤1:[步骤名称]**
|
||
```
|
||
[图解或示例]
|
||
```
|
||
- 关键点1:[说明]
|
||
- 关键点2:[说明]
|
||
|
||
**步骤2:[步骤名称]**
|
||
[...]
|
||
|
||
### 关键细节说明
|
||
|
||
**细节1:[细节名称]**
|
||
```python
|
||
[代码示例]
|
||
```
|
||
[为什么这样写]
|
||
|
||
**细节2:[细节名称]**
|
||
[...]
|
||
|
||
### 边界条件分析
|
||
|
||
**边界1:[条件名称]**
|
||
```
|
||
输入:[示例]
|
||
输出:[示例]
|
||
处理:[说明]
|
||
```
|
||
|
||
### 复杂度分析(详细版)
|
||
|
||
**时间复杂度**:
|
||
```
|
||
[计算过程]
|
||
总计:O(?)
|
||
```
|
||
|
||
**空间复杂度**:
|
||
```
|
||
[计算过程]
|
||
总计:O(?)
|
||
```
|
||
|
||
## 代码实现
|
||
|
||
```python
|
||
[完整代码,包含详细注释]
|
||
```
|
||
|
||
## 执行过程演示
|
||
|
||
```
|
||
[示例输入的完整执行过程]
|
||
```
|
||
|
||
## 常见错误
|
||
|
||
### 错误1:[错误名称]
|
||
❌ 错误写法:
|
||
```python
|
||
[错误代码]
|
||
```
|
||
✅ 正确写法:
|
||
```python
|
||
[正确代码]
|
||
```
|
||
**原因**:[说明]
|
||
|
||
## 变体问题
|
||
|
||
### 变体1:[变体描述]
|
||
[解法思路]
|
||
|
||
## 总结
|
||
|
||
**核心要点**:
|
||
1. [要点1]
|
||
2. [要点2]
|
||
3. [要点3]
|
||
|
||
**易错点**:
|
||
- [易错点1]
|
||
- [易错点2]
|
||
```
|
||
|
||
## 实施建议
|
||
|
||
1. **优先级**:先完善高频题目(Hot 100)
|
||
2. **分阶段**:
|
||
- 第一阶段:增加"思路推导"部分
|
||
- 第二阶段:增加"执行过程演示"
|
||
- 第三阶段:增加"常见错误"
|
||
|
||
3. **保持一致性**:所有题目使用统一模板
|
||
|
||
4. **可读性优先**:
|
||
- 使用代码块突出关键部分
|
||
- 使用列表提高可读性
|
||
- 适当使用emoji(谨慎使用)
|
||
|
||
## 示例对比
|
||
|
||
查看 `三数之和-改进版.md` 了解改进后的完整效果。
|