用双指针、滑动窗口、二分查找……把数组玩出花来!附带 LeetCode 高频 5 题精讲,新手也能秒懂!
LeetCode 数组高频题实战!
别怕!这些题看起来高冷,其实套路满满。只要掌握几个核心思想(比如双指针、滑动窗口、二分法),你就能在面试官面前优雅地写出 bug-free 代码,顺便收获一句:“你这思路很清晰啊!”
下面这 5 道题,覆盖了数组操作的经典场景,堪称「数组入门五重奏」。咱们一道一道拆解,有思考、有图感、有幽默,还有彩蛋!
🧹 第一重:原地删除?双指针来救场!(27. 移除元素)
题目:给你一个数组 nums 和一个值 val,原地移除所有等于 val 的元素,返回新长度。
输入:nums = [3,2,2,3], val = 3
输出:2, nums = [2,2,_,_]
💡 思路:快慢指针,各司其职
- 快指针(fast):负责遍历整个数组,像个“侦察兵”。
- 慢指针(slow):只在遇到“非目标值”时才前进,并把好元素“搬”到前面。
就像你在清理房间:快指针翻箱倒柜找垃圾(val),慢指针只把有用的东西(≠val)整齐摆到前面。
var removeElement = function(nums, val) {
let fast = 0, slow = 0;
while (fast < nums.length) {
if (nums[fast] !== val) {
nums[slow++] = nums[fast]; // 搬家!
}
fast++;
}
return slow; // 新长度就是 slow 的最终位置
};
✅ 时间 O(n),空间 O(1) ,完美原地操作!
🌀 第二重:转圈圈的艺术——螺旋矩阵 II(59)
题目:生成一个 n×n 的矩阵,按顺时针螺旋顺序填入 1 到 n²。
输入:n = 3
输出:[[1,2,3],[8,9,4],[7,6,5]]
💡 思路:一圈一圈往里绕,像削苹果!
- 控制边界:每绕一圈,起始行/列 +1,结束行/列 -1。
- 循环次数:
Math.floor(n / 2)圈。 - 如果 n 是奇数,中心那个格子单独填。
var generateMatrix = function(n) {
const matrix = Array.from({ length: n }, () => new Array(n));
let x = 0, y = 0; // 起始坐标
let offset = 1; // 边界偏移
let count = 1;
let round = Math.floor(n / 2);
while (round--) {
let i = x, j = y;
// → 向右
for (; j < n - offset; j++) matrix[i][j] = count++;
// ↓ 向下
for (; i < n - offset; i++) matrix[i][j] = count++;
// ← 向左
for (; j > y; j--) matrix[i][j] = count++;
// ↑ 向上
for (; i > x; i--) matrix[i][j] = count++;
x++; y++; offset++; // 缩小一圈
}
// 奇数 n,填中心
if (n % 2 === 1) matrix[Math.floor(n/2)][Math.floor(n/2)] = count;
return matrix;
};
🎯 关键:四个 for 循环方向固定,边界控制精准,别让指针“越狱”!
🔥 第三重:滑动窗口登场!最小满足子数组(209)
题目:找到和 ≥ target 的最短连续子数组长度。
输入:target = 7, nums = [2,3,1,2,4,3]
输出:2 (因为 [4,3] 满足)
💡 思路:滑动窗口(Sliding Window)
- 右指针不断扩张,直到窗口和 ≥ target。
- 左指针尝试收缩,看看能不能更短。
- 动态更新最小长度。
var minSubArrayLen = function(target, nums) {
let left = 0, sum = 0, minLen = Infinity;
for (let right = 0; right < nums.length; right++) {
sum += nums[right];
while (sum >= target) {
minLen = Math.min(minLen, right - left + 1);
sum -= nums[left++]; // 收缩左边界
}
}
return minLen === Infinity ? 0 : minLen;
};
💬 比喻:就像拉窗帘——先拉开(right 扩张),再慢慢收拢(left 收缩),找到刚好遮住阳光的最小宽度!
🔍 第四重:经典中的经典——二分查找(704)
题目:在有序数组中找 target,返回下标,找不到返回 -1。
输入:nums = [-1,0,3,5,9,12], target = 9
输出:4
💡 思路:每次砍一半,O(log n) 的快乐!
var search = function(nums, target) {
let left = 0, right = nums.length - 1;
while (left <= right) {
const mid = left + Math.floor((right - left) / 2); // 防溢出写法
if (nums[mid] === target) return mid;
if (nums[mid] < target) left = mid + 1;
else right = mid - 1;
}
return -1;
};
⚠️ 注意:循环条件是
left <= right!很多人栽在left < right上,结果漏掉最后一个元素。
🧊 第五重:负数平方后怎么排?双指针妙解!(977)
题目:给定非递减数组(含负数),返回每个元素平方后的非递减数组。
输入:nums = [-4,-1,0,3,10]
输出:[0,1,9,16,100]
💡 思路:最大值在两端!从后往前填
因为负数平方后可能比正数大,所以最大值一定在数组两端。
- 左指针指向最小负数(最左),右指针指向最大正数(最右)。
- 比较两边平方,大的放结果数组末尾,然后指针移动。
var sortedSquares = function(nums) {
const n = nums.length;
const res = new Array(n);
let left = 0, right = n - 1, pos = n - 1;
while (left <= right) {
const leftSq = nums[left] * nums[left];
const rightSq = nums[right] * nums[right];
if (leftSq > rightSq) {
res[pos--] = leftSq;
left++;
} else {
res[pos--] = rightSq;
right--;
}
}
return res;
};
✨ 优雅之处:不用排序!O(n) 时间搞定,比
map().sort()快得多!
🎉 总结:数组题的“武功秘籍”
| 题号 | 核心技巧 | 关键词 |
|---|---|---|
| 27 | 双指针(快慢) | 原地删除 |
| 59 | 模拟 + 边界控制 | 螺旋、圈层 |
| 209 | 滑动窗口 | 最短子数组 |
| 704 | 二分查找 | 有序、log n |
| 977 | 双指针(对撞) | 平方、两端最大 |
这些题看似独立,实则共用一套思维工具箱:
- 双指针:处理“比较”、“合并”、“去重”。
- 滑动窗口:解决“连续子数组”问题。
- 二分查找:对付“有序”结构。
- 模拟法:照着规则一步步走(如螺旋矩阵)。
🌟 最后送你一句话:
“刷题不是为了背答案,而是为了在混乱中看见秩序,在边界中找到自由。”
下次面试官说“写个螺旋矩阵”,你可以微微一笑:“稍等,我先削个苹果。”🍎