Day2 有序数组的平方、长度最小的子数组、螺旋矩阵Ⅱ

977.有序数组的平方

力扣题目链接(opens new window)

给你一个按 非递减顺序 排序的整数数组 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]

有序数组 ——> 双指针法(左右指针)

/**
 * @param {number[]} nums
 * @return {number[]}
 */
// 时间复杂度O(n) 空间复杂度O(n)
// 平方后大数在两端,左右指针
// 返回每个数字的平方组成的新数组(长度和原数组一致),非递减顺序(大数在右端)
var sortedSquares = function (nums) {
    let left = 0, right = nums.length - 1;
    let squares = new Array(nums.length);
    let k = squares.length - 1;
    while (left <= right) {
        // 新数组从后往前(从大到小)赋值
        if (nums[left] * nums[left] <= nums[right] * nums[right]) {
            squares[k] = nums[right] * nums[right];
            k--; right--;
        }
        else {
            squares[k] = nums[left] * nums[left];
            k--; left++;
        }
    }
    return squares;
};
// res[k--] = right; k--后置递减运算符,先赋值后递减
// res[--k] = right; --k前置递减运算符,先递减后赋值

209.长度最小的子数组

力扣题目链接(opens new window)

给定一个含有 n 个正整数的数组和一个正整数 s ,找出该数组中满足其和 ≥ s 的长度最小的 连续 子数组,并返回其长度。如果不存在符合条件的子数组,返回 0。

示例:

  • 输入:s = 7, nums = [2,3,1,2,4,3]
  • 输出:2
  • 解释:子数组 [4,3] 是该条件下的长度最小的子数组。

提示:

  • 1 <= target <= 10^9
  • 1 <= nums.length <= 10^5
  • 1 <= nums[i] <= 10^5

找某条件下长度最小/最大的子数组 ——> 滑动窗口(快慢指针)

所谓滑动窗口,就是不断的调节子序列的起始位置和终止位置,从而得出我们要想的结果

只用一个for循环,循环的索引表示 滑动窗口的终止位置。根据当前子序列满足某条件的情况,不断调节子序列的起始位置。

/**
 * @param {number} target
 * @param {number[]} nums
 * @return {number}
 */
// 某条件下长度最小/最大的子数组 ——> 滑动窗口(快慢指针)
var minSubArrayLen = function (target, nums) {
    let result = Infinity;  // 声明子数组长度并赋值正无穷,Infinity(找最小,初始为Infinity)
    let sum = 0, subLength = 0;  // 用于记录总和和满足条件的数组的长度
    let slow = 0, fast = 0;  // 快慢指针用于滑动窗口
    while (fast < nums.length) {  // 窗口的结束位置(快指针)小于数组长度
        sum += nums[fast];  // 当前窗口中的总和
        while (sum >= target) {  // 总和大于等于target
            subLength = (fast - slow + 1);  // 记录窗口大小
            result = result < subLength ? result : subLength;  // 比较得到满足条件的长度最小的子数组
            sum -= nums[slow++];  // 总和减去窗口起始位置的值,并向后移动窗口的起始位置(慢指针)
        }  // 总和小于target时结束循环
        fast++;  // 向后移动窗口的结束位置
    }  // 窗口的结束位置滑到数组末尾后结束循环
    return result !== Infinity ? result : 0;  // 判断是否存在符合条件的子数组,存在返回其长度,不存在返回0
};

相关题目1:904.水果成篮

力扣题目链接(opens new window)

你正在探访一家农场,农场从左到右种植了一排果树。这些树用一个整数数组 fruits 表示,其中 fruits[i] 是第 i 棵树上的水果 种类 。

你想要尽可能多地收集水果。然而,农场的主人设定了一些严格的规矩,你必须按照要求采摘水果:

  • 你只有 两个 篮子,并且每个篮子只能装 单一类型 的水果。每个篮子能够装的水果总量没有限制。
  • 你可以选择任意一棵树开始采摘,你必须从 每棵 树(包括开始采摘的树)上 恰好摘一个水果 。采摘的水果应当符合篮子中的水果类型。每采摘一次,你将会向右移动到下一棵树,并继续采摘。
  • 一旦你走到某棵树前,但水果不符合篮子的水果类型,那么就必须停止采摘。

给你一个整数数组 fruits ,返回你可以收集的水果的 最大 数目。

某条件下长度最大的子数组 ——> 滑动窗口

/**
 * @param {number[]} fruits
 * @return {number}
 */
 // 某条件下长度最大的子数组 ——> 滑动窗口
 // 哈希表存储键值对 ——> 限制条件
 // hashMap.set(key,value)添加/修改元素;hashMap.get(key)获取当前元素对应的值;hashMap.delete(key)删除该元素
 // 时间复杂度O(n);空间复杂度O(1) Map对象中的键值对数量不会超过2
var totalFruit = function (fruits) {
    let fast = 0, slow = 0;  // 快慢指针用于滑动窗口
    let sum = 0;  // 可以采摘的果树的最大数目(找最大,初始为0)
    let basket = new Map();  // 使用哈希表存储水果类型和个数,两个篮子->两种类型(不超过两个键值对)
    while (fast < fruits.length) {  // 快指针滑到末尾之前
        // 添加当前水果到篮子中
        basket.set(fruits[fast], (basket.get(fruits[fast]) || 0) + 1);
        // 如果超过两种类型,则从左端移除水果直到只有两种类型
        while (basket.size > 2) {
            basket.set(fruits[slow], basket.get(fruits[slow]) - 1);
            if (basket.get(fruits[slow]) === 0) basket.delete(fruits[slow]);
            slow++;
        }
        sum = Math.max(sum, fast - slow + 1);  // 可以采摘的果树的最大数目
        fast++;  // 快指针向前遍历
    }
    return sum;
};

相关题目2:76.最小覆盖字串

力扣题目链接(opens new window)

给你一个字符串 s 、一个字符串 t 。返回 s 中涵盖 t 所有字符的最小子串。如果 s 中不存在涵盖 t 所有字符的子串,则返回空字符串 "" 。

注意:

  • 对于 t 中重复字符,我们寻找的子字符串中该字符数量必须不少于 t 中该字符数量。
  • 如果 s 中存在这样的子串,我们保证它是唯一的答案。

某条件下长度最小的字串 ——> 滑动窗口

/**
 * @param {string} s
 * @param {string} t
 * @return {string}
 */
// 某条件下长度最小的字串 ——> 滑动窗口
// hashMap.has(key)判断哈希表中是否存在某个元素;hashMap.get(key)获取某个元素对应的值
// string.substr(startIndex,length)字符串从起始索引截取并返回某个长度的子串
// 时间复杂度O(n^2) 空间复杂度O(n)
var minWindow = function (s, t) {
    let slow = 0, fast = 0;  // 快慢指针 ——> 记录窗口起始位置

    let need = new Map();  // 用哈希表存储t子字符串中所需字符及对应个数
    for (let c of t) need.set(c, (need.get(c) || 0) + 1);  // t中字符及对应个数

    let window = new Map();  // 用哈希表存储当前窗口中t需要的字符及对应个数
    let valid = 0;  // 满足条件的字符
    let start = 0, len = Infinity;  // 记录字串起始索引及长度(找最小,初始为Infinity)

    while (fast < s.length) {  // 快指针滑到字符串尾之前
        if (need.get(s[fast]) !== 0) {  // 如果s[fast]是t中需要的字符
            window.set(s[fast], (window.get(s[fast]) || 0) + 1);  // 存储在window对象中
            if (window.get(s[fast]) === need.get(s[fast])) valid++;  // 如果window中某一字符数量满足t中需要,vaild++
        }
        fast++;  // 扩大窗口
        while (valid === need.size) {  // 如果need中字符数量均满足(覆盖子串)
            if (fast - slow < len) {  // 更新长度以找到最小覆盖子串
                start = slow;  // 更新子串起始索引
                len = fast - slow;  // 更新子串长度(上面已经fast++了,所以fast-slow是字串长度,不需要再加1)
            }
            if (need.get(s[slow]) !== 0) {  // 如果s[slow]是t中需要的字符
                window.set(s[slow], window.get(s[slow]) - 1);  // window中该字符个数减1
                if (window.get(s[slow]) < need.get(s[slow])) valid--;  // 直到window中该字符不满足t需要,valid--
            }
            slow++;  // 缩小窗口
        }
    }
    // 没有符合条件的子字符串,返回空字符串
    // substr()	从起始索引提取字符串中指定个数的字符
    return len === Infinity ? "" : s.substr(start, len);
};

59.螺旋矩阵II

力扣题目链接(opens new window)

给定一个正整数 n,生成一个包含 1 到 n^2 所有元素,且元素按顺时针顺序螺旋排列的正方形矩阵。

示例:

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

方法一:坚持循环不变量原则(左开右闭),模拟顺时针画矩阵的过程:

  • 填充上行从左到右
  • 填充右列从上到下
  • 填充下行从右到左
  • 填充左列从下到上
/**
 * @param {number} n
 * @return {number[][]}
 */
var generateMatrix = function (n) {
    let loop = Math.floor(n / 2);  // 顺时针为1圈,需要转的圈数
    let mid = Math.floor(n / 2);  // 如果n为奇数,需要额外填入中心点
    let offset = 1;  // 四条边每条边控制一个左闭右开的区间,最后一个元素不填
    let x = 0, y = 0;  // 每一圈的起始点
    let matrix = new Array(n).fill(0).map(() => new Array(n).fill(0));  // n*n的二维数组,默认填充0
    let value = 1;  // 要填充的元素
    while (loop--) {
        let row = x, col = y;  // 每一圈的起始行和列
        for (; col < n - offset; col++) {  // 从左到右依次填充
            matrix[row][col] = value++;
        }
        for (; row < n - offset; row++) {  // 从上到下依次填充
            matrix[row][col] = value++;
        }
        for (; col > y; col--) {  // 从右到左依次填充
            matrix[row][col] = value++;
        }
        for (; row > x; row--) {  // 从下到上依次填充
            matrix[row][col] = value++;
        }
        x++; y++;  // 下一圈的起始点
        offset++;  // 更新偏移量
    }
    if (n % 2 !== 0) {  // 如果n为奇数
        matrix[mid][mid] = value;  // 填充中心元素
    }
    return matrix;
};

方法二:动态更新边界,在每个方向填充完成后检查边界是否交叉,一旦交叉立即退出循环(更通用)

/**
 * @param {number} n
 * @return {number[][]}
 */
// 动态更新边界top left bottom right
// 在每个方向填充完成后检查边界是否交叉,一旦交叉立即退出循环
// 无需特殊处理中心点
var generateMatrix = function (n) {
    let matrix = new Array(n).fill(0).map(() => new Array(n).fill(0));  // n*n的二维数组,默认填充0
    let value = 1;  // 要填充的元素
    let top = 0, left = 0;  // 矩阵上和左边界
    let bottom = n - 1, right = n - 1;  // 矩阵下和右边界
    while (true) {
        for (let i = left; i <= right; i++) {  // 从左到右依次填充
            matrix[top][i] = value++;
        }
        if (++top > bottom) break;  // 更新上边界,超出下边界时停止循环

        for (let i = top; i <= bottom; i++) {  // 从上到下依次填充
            matrix[i][right] = value++;
        }
        if (--right < left) break;  // 更新右边界,超出左边界时停止循环

        for (let i = right; i >= left; i--) {  // 从右到左依次填充
            matrix[bottom][i] = value++;
        }
        if (--bottom < top) break;  // 更新下边界,超出上边界时停止循环

        for (let i = bottom; i >= top; i--) {  // 从下到上依次填充
            matrix[i][left] = value++;
        }
        if (++left > right) break;  // 更新左边界,超出右边界时停止循环
    }
    return matrix;
};

相关题目3:54.螺旋矩阵

力扣题目链接(opens new window)

给你一个 m 行 n 列的矩阵 matrix ,请按照 顺时针螺旋顺序 ,返回矩阵中的所有元素。

示例 1:

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

示例 2:

输入:matrix = [[1,2,3,4],[5,6,7,8],[9,10,11,12]]
输出:[1,2,3,4,8,12,11,10,9,5,6,7]

模拟任务过程,动态更新边界,判断边界是否交叉

/**
 * @param {number[][]} matrix
 * @return {number[]}
 */
// array.push()向数组末尾添加元素并返回新的长度
// 注意是 先更新边界 后判断边界是否交叉
// top bottom 是行,left right是列
var spiralOrder = function (matrix) {
    let m = matrix.length;  // m行
    let n = matrix[0].length;  // n列
    let orderMatrix = new Array();  // 一维数组(长度应为m*n),按顺时针螺旋顺序记录矩阵中的元素
    let top = 0, left = 0;  // 矩阵的上和左边界
    let bottom = m - 1, right = n - 1;  // 矩阵的下和右边界
    while (true) {
        // 从左到右遍历矩阵
        for (let i = left; i <= right; i++) {
            orderMatrix.push(matrix[top][i]);  // 向数组末尾添加元素
        }
        // 更新上边界,如果超出下边界停止循环
        if (++top > bottom) break;

        // 从上到下遍历矩阵
        for (let i = top; i <= bottom; i++) {
            orderMatrix.push(matrix[i][right]);  // 记录元素
        }
        // 更新右边界,如果超出左边界停止循环
        if (--right < left) break;

        // 从右到左遍历矩阵
        for (let i = right; i >= left; i--) {
            orderMatrix.push(matrix[bottom][i]);  // 记录元素
        }
        // 更新下边界,如果超出上边界停止循环
        if (--bottom < top) break;

        // 从下到上遍历矩阵
        for (let i = bottom; i >= top; i--) {
            orderMatrix.push(matrix[i][left]);  // 记录元素
        }
        // 更新左边界,如果超出右边界停止循环
        if (++left > right) break;
    }
    if (orderMatrix.length === m * n) return orderMatrix;  // 最终返回的数组长度理应是m*n
    else return [];
};

数组总结:

二分法(左右指针):循环不变量原则,注意区间的定义(左闭右闭/左闭右开)

双指针法(快慢指针):通过一个快指针和慢指针在一个for循环下完成两个for循环的工作

滑动窗口:移动窗口起始位置,达到动态更新窗口大小,从而得出长度最小/最大的符合条件的长度

模拟行为:循环不变量原则,统一区间定义;模拟任务过程,动态更新边界,判断边界是否交叉

  • 18
    点赞
  • 8
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值