LeetCode体操-2 | 977.有序数组的平方 、209.长度最小的子数组、59.螺旋矩阵II

105 阅读6分钟

双指针法

简介

双指针法是一种在数组或链表上使用两个指针来解决问题的算法技术。它通常用于处理排序数组中的查找问题、子数组问题、矩阵问题等。两个指针可以分别控制搜索范围的开始和结束,或者用于遍历和比较。双指针技术的优势在于减少时间复杂度,尤其是在需要多次遍历数据时。例如,在合并两个有序数组或寻找最长不重复子数组长度时,双指针法可以显著提高效率。

基本流程

  1. 开始:算法的起始点。
  2. 初始化指针:设置两个指针ij,通常ij都初始化为0或数组的起始位置。
  3. 指针 i 和 j:两个指针分别遍历数组或列表。
  4. 条件判断:检查指针ij是否满足遍历条件(例如,是否小于数组长度n)。
  5. 执行操作:根据算法需求,对指针ij指向的元素执行操作。
  6. 更新指针:根据算法逻辑更新指针ij的位置。
  7. 结束条件:当指针ij不再满足遍历条件时,算法结束。
  8. 返回结果:根据算法需求返回最终结果。

Untitled diagram-2024-07-04-114013.png

977.有序数组的平方

难度: 简单

原题: 给你一个按 非递减顺序 排序的整数数组 nums,返回 每个数字的平方 组成的新数组,要求也按 非递减顺序 排序。

示例 1:

输入:nums = [-4,-1,0,3,10]

输出:[0,1,9,16,100]

解释:平方后,数组变为 [16,1,0,9,100] 排序后,数组变为 [0,1,9,16,100]

示例 2:

输入:nums = [-7,-3,2,3,11]

输出:[4,9,9,49,121]

解题方法: 双指针法

特征: 通过一个快指针和慢指针在一个for循环下完成两个for循环的工作;2、定义快慢指针,快指针:寻找新数组的元素 ,新数组就是不含有目标元素的数组。慢指针:指向更新 新数组下标的位置;

重难点: 1、确定指针的移动策略;2、处理边界条件;

1、双指针法(快慢指针法)

思路:

  1. 定义快慢指针;
  2. 利用两个指针分别用于遍历和标记的不同行为高效解题(一个for循环下完成两个for循环的工作);
function sortedSquares(nums) {
    const { length } = nums;
    const resList = Array(length);
    let i = 0, j = length - 1, p = length - 1;
    while (i <= j) {
        const left = nums[i] * nums[i];
        const right = nums[j] * nums[j];
        if (left < right) {
            resList[p--] = right;
            j--;
        } else {
            resList[p--] = left;
            i++;
        }
    }
    return resList;
}
  • 时间复杂度为O(n)
  • 空间复杂度:O(1)

209.长度最小的子数组

难度: 中等

原题: 给定一个含有 n 个正整数的数组和一个正整数 target。找出该数组中满足其总和大于等于target的长度最小的连续子数组[numsl, numsl+1, ..., numsr-1, numsr] ,并返回其长度。如果不存在符合条件的子数组,返回 0示例 1:

输入:target = 7, nums = [2,3,1,2,4,3]

输出:2

解释:子数组 [4,3] 是该条件下的长度最小的子数组。

示例 2:

输入:target = 4, nums = [1,4,4]

输出:1

示例 3:

输入:target = 11, nums = [1,1,1,1,1,1,1,1]

输出:0

解题方法: 滑动窗口

特征: 通过不断的调节子序列的起止位置,实现一个for循环完成与暴力解法两个for循环相同的效果

重难点: for循环的索引定义为滑动窗口的终止位置,驱动其前进,而窗口中的内容是否满足题解则驱动起始位置的前进,由此形成窗口滑动(即一种特殊的双指针)

1、滑动窗口

思路:

  1. 定义窗口:满足其和≥s的长度最小的连续子数组
  2. 定义移动起始位置:如果当前窗口的值大于s,则起始位置就要向前移动了
  3. 定义移动终止位置:终止位置就是遍历数组的指针(即for循环索引)
function minSubArrayLen(target, nums) {
    let [start, end, sum, ans] = [0, 0, 0, Infinity];
    const { length } = nums;

    while (end < length) {
        sum += nums[end];
        while (sum >= target) {
            ans = Math.min(ans, end - start + 1);
            sum -= nums[start];
            start++;
        }
        end++;
    }
    return ans === Infinity ? 0 : ans;
}
  • 时间复杂度:O(n)
  • 空间复杂度:O(1)

59.螺旋矩阵II

难度: 中等

原题: 给你一个正整数 n ,生成一个包含 1 到 n2 所有元素,且元素按顺时针顺序螺旋排列的 n x n 正方形矩阵 matrix 。

示例1:

输入: n = 3

输出: [[1,2,3],[8,9,4],[7,6,5]]

示例2:

输入: n = 1

输出: [[1]]

解题方法: 将数据螺旋排列抽象成二维数据填充数据的过程

特征: 无对应算法,抽象成代码进行实现即可

重难点: 抽象过程、定义起止点、妥善处理边界问题(坚持左开右闭,一以贯之)

1、根据题意抽象成代码实现

思路:

  1. 定义起止点、圈数、中心点位置、偏移量、边界下标、二维数据等变量
  2. 模拟螺旋过程,while循环将数据填充到二维数组中
  3. 处理奇数场景补充中心点坐标)
function generateMatrix(n) {
    // 定义螺旋X、Y起始点
    let [startX, startY] = [0, 0];
    // 螺旋圈数、中间位置(向下取整仅考虑偶数个,奇数中心点最后单独处理)
    let [loop, mid] = [Math.floor(n / 2), Math.floor(n / 2)];
    // 每一层已填充元素个数、填充单元格的下标
    let [offset, count] = [1, 1];
    // 初始化矩阵(二维数组),每次螺旋边的长度始终遵循左开右闭原则,即长度为n-offset
    const resList = Array(n)
        .fill(0)
        .map(() => Array(n).fill(0));

    // 依据螺旋方向,每次while循环填充一层数据
    while (loop--) {
        // 定义行、列的坐标
        let [row, col] = [startX, startY];
        // 上行从左到右
        for (; col < n - offset; col++) {
            resList[row][col] = count++;
        }
        // 右列从上到下
        for (; row < n - offset; row++) {
            resList[row][col] = count++;
        }
        // 下行从右到左
        for (; col > startY; col--) {
            resList[row][col] = count++;
        }
        // 左列做下到上
        for (; row > startX; row--) {
            resList[row][col] = count++;
        }

        // 更新起始位置及offset(随着螺旋圈数的增加,每一层的起始位置和offset都会发生变化,递增,即每一层的螺旋边长下标都会减少1)
        startX++;
        startY++;
        offset++;
    }
    // 如果n为奇数,则单独给矩阵中心位置赋值
    if (n % 2 === 1) {
        resList[mid][mid] = count;
    }
    return resList;
}
  • 时间复杂度 O(n^2)
  • 空间复杂度 O(1)