跳到主要内容

解题思路与方法论

拿到题目后的第一件事

不要急着写代码!

很多人一看到题目就开始敲代码,结果写到一半发现思路不对,又要重来。正确的做法是:

读题 → 分析 → 设计 → 编码 → 测试

第一步:读懂题目

1. 提取关键信息

关键点要明确的问题
输入数据类型?范围?有序还是无序?
输出返回什么?数组、数字、布尔值?
约束时间复杂度要求?空间复杂度要求?
特殊情况空输入?重复元素?负数?

2. 用自己的话复述

读完题目后,尝试用一句话概括:

"这道题是要我 ____,给我 ____,让我返回 ____"

例如:两数之和

"这道题是要我找两个数,给我一个数组和目标值,让我返回这两个数的下标"

3. 手动模拟例子

拿题目给的例子,用手在纸上走一遍,不要用脑子"想",要动手"做"。

例:两数之和
nums = [2, 7, 11, 15], target = 9

手动模拟:
- 看 2,需要找 9-2=7,数组里有 7 吗?有!在下标 1
- 所以答案是 [0, 1]

第二步:分析思路

1. 暴力解法先行

先想最笨的方法,哪怕是 O(n2)O(n^2)O(n3)O(n^3) 也行。

暴力解法的好处:

  • 帮你理解问题
  • 作为优化的基准
  • 面试时至少有东西可写
// 两数之和 - 暴力解法 O(n²)
function twoSum(nums: number[], target: number): number[] {
for (let i = 0; i < nums.length; i++) {
for (let j = i + 1; j < nums.length; j++) {
if (nums[i] + nums[j] === target) {
return [i, j];
}
}
}
return [];
}

2. 寻找优化点

问自己:哪里做了重复的工作?

暴力解法的问题:每次都要遍历整个数组找配对的数
优化思路:用哈希表记录已经看过的数,查找只需 O(1)

3. 联想常见套路

根据题目特征,联想学过的算法:

题目特征可能的算法
有序数组二分查找、双指针
找最值/方案数动态规划
连续子串/子数组滑动窗口
链表操作双指针、递归
树的遍历DFS、BFS、递归
所有可能/排列组合回溯
快速查找哈希表

第三步:设计方案

1. 确定数据结构

选择合适的数据结构是成功的一半:

// 两数之和 - 优化解法
// 选择哈希表存储 {值: 下标}
const map = new Map<number, number>();

2. 写出伪代码

用中文/伪代码描述步骤,不要急着写真正的代码:

1. 创建一个哈希表
2. 遍历数组,对于每个数 num:
- 计算 complement = target - num
- 如果 complement 在哈希表中,返回结果
- 否则,把 num 和它的下标存入哈希表
3. 返回空数组(找不到)

3. 分析复杂度

在写代码前,先估算时间和空间复杂度:

时间复杂度:O(n) - 只需遍历一次
空间复杂度:O(n) - 哈希表最多存 n 个元素

第四步:编码实现

1. 先搭框架

function twoSum(nums: number[], target: number): number[] {
// TODO: 实现
return [];
}

2. 填充逻辑

按照伪代码,一步步实现:

function twoSum(nums: number[], target: number): number[] {
const map = new Map<number, number>();

for (let i = 0; i < nums.length; i++) {
const complement = target - nums[i];

if (map.has(complement)) {
return [map.get(complement)!, i];
}

map.set(nums[i], i);
}

return [];
}

3. 代码风格

  • 变量命名清晰complementc
  • 适当加注释:解释"为什么"而不是"是什么"
  • 保持简洁:能用一行就不用三行

第五步:测试验证

1. 用例子验证

手动跑一遍题目给的例子:

nums = [2, 7, 11, 15], target = 9

i=0: complement=7, map为空, map={2:0}
i=1: complement=2, map中有2! 返回 [0, 1] ✓

2. 考虑边界情况

边界情况测试用例
空数组[], 1
只有一个元素[1], 2
目标在最后[1,2,3], 5
有重复元素[3,3], 6
负数[-1,2], 1

3. 检查常见错误

  • 数组越界
  • 空指针/undefined
  • 整数溢出
  • 死循环
  • Off-by-one 错误(差一错误)

面试时的沟通技巧

1. 边想边说

不要闷头做题,把思考过程说出来:

"我先想一个暴力解法...两层循环可以解决,但时间复杂度是 O(n2)O(n^2)..."

"有没有更优的方法呢?我想用哈希表来优化..."

2. 主动提问

不确定的地方要问清楚:

  • "数组是有序的吗?"
  • "有重复元素怎么处理?"
  • "返回任意一个答案还是所有答案?"

3. 承认不会

如果真的不会,不要假装会:

"这道题我没有思路,但我可以先写一个暴力解法,再想想怎么优化..."


解题 Checklist

□ 读懂题目了吗?能用一句话概括吗?
□ 手动模拟过例子吗?
□ 想到暴力解法了吗?
□ 能优化吗?用什么数据结构/算法?
□ 写出伪代码了吗?
□ 分析过复杂度吗?
□ 代码写完了吗?
□ 用例子验证过了吗?
□ 考虑过边界情况吗?

常见错误与避坑

1. 没看清题目

❌ 题目要返回下标,结果返回了值
❌ 题目要求原地修改,结果新建了数组
❌ 题目说有序,却用了 O(n²) 解法

2. 边界处理

// ❌ 错误:没考虑空数组
function findMax(nums: number[]): number {
let max = nums[0]; // 如果 nums 为空会报错
// ...
}

// ✅ 正确:先判断
function findMax(nums: number[]): number {
if (nums.length === 0) return -1; // 或抛出错误
let max = nums[0];
// ...
}

3. Off-by-one 错误

// ❌ 错误:多遍历了一个
for (let i = 0; i <= nums.length; i++) // length 应该取不到

// ✅ 正确
for (let i = 0; i < nums.length; i++)

刷题建议

1. 按专题刷

不要随机刷题,按专题来:

Week 1: 数组 + 双指针
Week 2: 链表
Week 3: 哈希表
Week 4: 二叉树
Week 5: 动态规划入门
...

2. 重复练习

一道题做完不是结束:

  • 第1遍:看题解理解思路
  • 第2遍:不看题解自己写
  • 第3遍:隔几天再写一次

3. 总结模板

把常见题型的模板记下来:

// 二分查找模板
function binarySearch(nums: number[], target: number): number {
let left = 0;
let right = nums.length - 1;

while (left <= right) {
const mid = Math.floor((left + right) / 2);
if (nums[mid] === target) return mid;
if (nums[mid] < target) left = mid + 1;
else right = mid - 1;
}

return -1;
}

相关链接