刷了200道数组题,笔试面试还是不会做?这10道搞懂就够了
刷了200道数组题,面试还是不会做?
问题不是你刷得不够多,而是没抓住核心套路。
我整理了35道大厂真题,发现其实就5个核心技巧。今天把最重要的10道题和背后的套路,全部分享给你。
offer直通车-大厂校招大礼包:入口
📝 这10道题是:
-
两数之和 - 哈希表基础
-
三数之和 - 双指针经典
-
合并两个有序数组 - 双指针应用
-
盛最多水的容器 - 对撞指针
-
接雨水 - 单调栈经典
-
最大子序和 - 动态规划入门
-
螺旋矩阵 - 矩阵遍历
-
旋转图像 - 矩阵操作
-
缺失的第一个正数 - 原地哈希
-
最长连续序列 - 哈希表优化
📊 为什么是这10道题?
数据不会骗人
我分析了35道数组高频题,发现:
-
这10道题覆盖了5个核心技巧
-
掌握这10道,其他题目都能举一反三
-
不是题目多就好,而是要抓住本质
不是题海战术,是套路识别
很多人刷题的问题在于:
-
刷了100道,每道都是新题
-
看到题目,不知道用什么方法
-
面试时,脑子一片空白
真正高效的刷题方法:
-
先掌握核心套路
-
再通过题目强化套路
-
最后做到看到题目就知道用什么套路
这10道题,就是帮你建立"套路识别"能力的最佳选择。
🎯 核心技巧1:双指针
为什么双指针这么重要?
双指针是数组题的"瑞士军刀",适用场景最广。
核心思想:用两个指针,从不同位置或方向遍历数组,降低时间复杂度。
三种模式:
-
对撞指针:从两端向中间
-
快慢指针:一个快一个慢
-
滑动窗口:维护一个区间
题目1:两数之和
题目描述: 给定一个整数数组和一个目标值,找出数组中和为目标值的两个数。
为什么重要:
-
最基础的哈希表应用
-
面试出现频率极高
-
是三数之和、四数之和的基础
核心思路:
用哈希表存储已遍历的数字,查找时间从O(n)降到O(1)。
代码实现:
vector<int> twoSum(vector<int>& nums, int target) {
unordered_map<int, int> hash;
for (int i = 0; i < nums.size(); i++) {
int complement = target - nums[i];
if (hash.count(complement)) {
return {hash[complement], i};
}
hash[nums[i]] = i;
}
return {};
}
时间复杂度 : O(n) 空间复杂度: O(n)
面试要点:
-
能说出暴力解法(O(n²))和优化解法(O(n))
-
注意边界:数组为空、只有两个元素
-
注意重复元素的处理
题目2:三数之和
题目描述: 给定一个数组,找出所有和为0的三元组。
为什么重要:
-
双指针的经典应用
-
考察去重能力
-
是N数之和的通用模板
核心思路:
-
先排序
-
固定一个数,剩下两个数用双指针
-
注意去重
代码实现:
vector<vector<int>> threeSum(vector<int>& nums) {
vector<vector<int>> result;
sort(nums.begin(), nums.end());
for (int i = 0; i < nums.size(); i++) {
if (i > 0 && nums[i] == nums[i-1]) continue; // 去重
int left = i + 1, right = nums.size() - 1;
while (left < right) {
int sum = nums[i] + nums[left] + nums[right];
if (sum == 0) {
result.push_back({nums[i], nums[left], nums[right]});
while (left < right && nums[left] == nums[left+1]) left++; // 去重
while (left < right && nums[right] == nums[right-1]) right--; // 去重
left++;
right--;
} else if (sum < 0) {
left++;
} else {
right--;
}
}
}
return result;
}
时间复杂度 : O(n²) 空间复杂度: O(1)
面试要点:
-
为什么要先排序?
-
如何去重?(三个位置都要去重)
-
能扩展到四数之和吗?
题目3:合并两个有序数组
题目描述: 将两个有序数组合并到第一个数组中,保持有序。
为什么重要:
-
双指针最简单的应用
-
考察从后往前遍历的技巧
-
归并排序的基础
核心思路:
从后往前填充,避免覆盖未处理的元素。
代码实现:
void merge(vector<int>& nums1, int m, vector<int>& nums2, int n) {
int i = m - 1, j = n - 1, k = m + n - 1;
while (i >= 0 && j >= 0) {
if (nums1[i] > nums2[j]) {
nums1[k--] = nums1[i--];
} else {
nums1[k--] = nums2[j--];
}
}
while (j >= 0) {
nums1[k--] = nums2[j--];
}
}
时间复杂度 : O(m+n) 空间复杂度: O(1)
面试要点:
-
为什么从后往前?
-
如果从前往后会怎样?
-
只需要处理nums2剩余元素,为什么?
题目4:盛最多水的容器
题目描述: 给定n个非负整数,每个数代表坐标中的一个点(i, ai)。找出其中的两条线,使得它们与x轴共同构成的容器可以容纳最多的水。
为什么重要:
-
对撞指针的经典应用
-
考察贪心思想
-
面试高频题
核心思路:
双指针从两端向中间移动,每次移动较短的那条边。
为什么移动短边?
-
容器的容量由短边决定
-
移动长边,容量只会变小
-
移动短边,容量可能变大
代码实现:
int maxArea(vector<int>& height) {
int left = 0, right = height.size() - 1;
int maxWater = 0;
while (left < right) {
int h = min(height[left], height[right]);
int w = right - left;
maxWater = max(maxWater, h * w);
if (height[left] < height[right]) {
left++;
} else {
right--;
}
}
return maxWater;
}
时间复杂度 : O(n) 空间复杂度: O(1)
面试要点:
-
能解释为什么移动短边
-
能证明这个贪心策略的正确性
-
和接雨水的区别是什么?
🎯 核心技巧2:单调栈
什么是单调栈?
单调栈是一种特殊的栈,栈内元素保持单调递增或递减。
适用场景:
-
找下一个更大/更小的元素
-
找左右边界
-
矩形面积问题
题目5:接雨水
题目描述: 给定n个非负整数表示每个宽度为1的柱子的高度图,计算按此排列的柱子,下雨之后能接多少雨水。
为什么重要:
-
单调栈的经典应用
-
有多种解法(动态规划、双指针、单调栈)
-
面试超高频题
核心思路(单调栈):
维护一个单调递减栈,遇到比栈顶大的元素时,说明可以接水。
代码实现:
int trap(vector<int>& height) {
stack<int> st;
int water = 0;
for (int i = 0; i < height.size(); i++) {
while (!st.empty() && height[i] > height[st.top()]) {
int top = st.top();
st.pop();
if (st.empty()) break;
int distance = i - st.top() - 1;
int h = min(height[i], height[st.top()]) - height[top];
water += distance * h;
}
st.push(i);
}
return water;
}
时间复杂度 : O(n) 空间复杂度: O(n)
面试要点:
-
能说出至少2种解法
-
单调栈解法的核心思想
-
和盛最多水的容器有什么区别?
🎯 核心技巧3:动态规划
动态规划的核心
四步走:
-
定义状态
-
写出状态转移方程
-
初始化
-
确定遍历顺序
题目6:最大子序和
题目描述: 给定一个整数数组,找到一个具有最大和的连续子数组。
为什么重要:
-
动态规划入门题
-
Kadane算法的经典应用
-
面试必考
核心思路:
dp[i]表示以i结尾的最大子序和。
状态转移方程:
dp[i] = max(dp[i-1] + nums[i], nums[i])
代码实现:
int maxSubArray(vector<int>& nums) {
int maxSum = nums[0];
int currentSum = nums[0];
for (int i = 1; i < nums.size(); i++) {
currentSum = max(currentSum + nums[i], nums[i]);
maxSum = max(maxSum, currentSum);
}
return maxSum;
}
时间复杂度 : O(n) 空间复杂度: O(1)
面试要点:
-
能写出状态转移方程
-
能优化空间复杂度(滚动变量)
-
如果要返回子数组的起止位置怎么办?
🎯 核心技巧4:矩阵操作
矩阵题的通用技巧
-
找规律(旋转、螺旋)
-
原地修改(用特殊值标记)
-
分层处理
题目7:螺旋矩阵
题目描述: 给定一个m x n的矩阵,按照螺旋顺序返回矩阵中的所有元素。
为什么重要:
-
考察边界处理能力
-
模拟类题目的代表
-
面试常考
核心思路:
按层遍历,每层按照右→下→左→上的顺序。
代码实现:
vector<int> spiralOrder(vector<vector<int>>& matrix) {
vector<int> result;
if (matrix.empty()) return result;
int top = 0, bottom = matrix.size() - 1;
int left = 0, right = matrix[0].size() - 1;
while (top <= bottom && left <= right) {
// 右
for (int i = left; i <= right; i++) {
result.push_back(matrix[top][i]);
}
top++;
// 下
for (int i = top; i <= bottom; i++) {
result.push_back(matrix[i][right]);
}
right--;
// 左
if (top <= bottom) {
for (int i = right; i >= left; i--) {
result.push_back(matrix[bottom][i]);
}
bottom--;
}
// 上
if (left <= right) {
for (int i = bottom; i >= top; i--) {
result.push_back(matrix[i][left]);
}
left++;
}
}
return result;
}
时间复杂度 : O(m×n) 空间复杂度: O(1)
面试要点:
-
边界条件的处理(为什么左和上要加判断?)
-
能画图说明遍历过程
-
螺旋矩阵II(生成螺旋矩阵)会做吗?
题目8:旋转图像
题目描述: 给定一个n×n的二维矩阵表示一个图像,将图像顺时针旋转90度。要求原地旋转。
为什么重要:
-
考察找规律能力
-
原地操作的经典题
-
面试常考
核心思路:
先转置,再翻转每一行。
代码实现:
void rotate(vector<vector<int>>& matrix) {
int n = matrix.size();
// 转置
for (int i = 0; i < n; i++) {
for (int j = i + 1; j < n; j++) {
swap(matrix[i][j], matrix[j][i]);
}
}
// 翻转每一行
for (int i = 0; i < n; i++) {
reverse(matrix[i].begin(), matrix[i].end());
}
}
时间复杂度 : O(n²) 空间复杂度: O(1)
面试要点:
-
为什么先转置再翻转?
-
如果是逆时针旋转怎么办?
-
如果是旋转180度呢?
🎯 核心技巧5:原地修改 + 哈希表
什么时候用原地修改?
-
空间复杂度要求O(1)
-
数组元素范围有限
-
可以用特殊值标记
题目9:缺失的第一个正数
题目描述: 给定一个未排序的整数数组,找出其中没有出现的最小的正整数。要求O(n)时间和O(1)空间。
为什么重要:
-
原地哈希的经典应用
-
考察空间优化能力
-
面试难题
核心思路:
把每个数放到它应该在的位置(nums[i] = i+1)。
代码实现:
int firstMissingPositive(vector<int>& nums) {
int n = nums.size();
// 把每个数放到正确位置
for (int i = 0; i < n; i++) {
while (nums[i] > 0 && nums[i] <= n && nums[nums[i] - 1] != nums[i]) {
swap(nums[i], nums[nums[i] - 1]);
}
}
// 找第一个不在正确位置的数
for (int i = 0; i < n; i++) {
if (nums[i] != i + 1) {
return i + 1;
}
}
return n + 1;
}
时间复杂度 : O(n) 空间复杂度: O(1)
面试要点:
-
为什么时间复杂度是O(n)?(每个元素最多交换一次)
-
如何实现原地哈希?
-
边界条件的处理
题目10:最长连续序列
题目描述: 给定一个未排序的整数数组,找出最长连续序列的长度。要求O(n)时间。
为什么重要:
-
哈希表的巧妙应用
-
考察优化思维
-
面试高频题
核心思路:
用哈希表存储所有数字,然后只从序列的起点开始计数。
代码实现:
int longestConsecutive(vector<int>& nums) {
unordered_set<int> numSet(nums.begin(), nums.end());
int maxLen = 0;
for (int num : numSet) {
// 只从序列起点开始
if (!numSet.count(num - 1)) {
int currentNum = num;
int currentLen = 1;
while (numSet.count(currentNum + 1)) {
currentNum++;
currentLen++;
}
maxLen = max(maxLen, currentLen);
}
}
return maxLen;
}
时间复杂度 : O(n) 空间复杂度: O(n)
面试要点:
-
为什么只从序列起点开始?(避免重复计算)
-
如何保证时间复杂度是O(n)?
-
如果要求O(1)空间怎么办?(先排序,O(nlogn)时间)
🎓 如何举一反三?
套路识别清单
看到题目,先问自己:
1. 是否需要查找/去重? → 考虑哈希表
2. 是否需要优化时间复杂度? → 考虑双指针、二分查找
3. 是否有"下一个更大/更小"? → 考虑单调栈
4. 是否有"最优子结构"? → 考虑动态规划
5. 是否需要原地操作? → 考虑原地哈希、标记法
刷题建议
第一遍:理解套路
-
这10道题,每道都要手写一遍
-
理解为什么用这个方法
-
能说出时间和空间复杂度
第二遍:变形练习
-
两数之和 → 三数之和 → 四数之和
-
螺旋矩阵 → 螺旋矩阵II
-
旋转图像 → 逆时针旋转
第三遍:总结模板
-
双指针模板
-
单调栈模板
-
动态规划模板
面试技巧
1. 先说思路,再写代码
-
不要上来就写
-
先和面试官确认思路
-
得到认可再动手
2. 注意边界条件
-
数组为空
-
只有一个元素
-
所有元素相同
3. 分析复杂度
-
写完代码后主动说明
-
能说出优化方向
4. 测试用例
-
正常用例
-
边界用例
-
特殊用例