数组刷题五重奏:从“移除元素”到“螺旋矩阵”,我悟了!

16 阅读4分钟

用双指针、滑动窗口、二分查找……把数组玩出花来!附带 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双指针(对撞)平方、两端最大

这些题看似独立,实则共用一套思维工具箱

  • 双指针:处理“比较”、“合并”、“去重”。
  • 滑动窗口:解决“连续子数组”问题。
  • 二分查找:对付“有序”结构。
  • 模拟法:照着规则一步步走(如螺旋矩阵)。

🌟 最后送你一句话:

“刷题不是为了背答案,而是为了在混乱中看见秩序,在边界中找到自由。”

下次面试官说“写个螺旋矩阵”,你可以微微一笑:“稍等,我先削个苹果。”🍎