数组模块算法题

146 阅读3分钟

数组

注意!!!!

js 写代码时,运算结果一定要注意是否取整!!!对于不能整除的算式结果, js 会保留小数,而不像C、C++等会自动取整,一定不要忘记。

1. 二分查找法

题目

​ 给定一个 n 个元素有序的(升序)整型数组 nums 和一个目标值 target ,写一个函数搜索 nums 中的 target,如果目标值存在返回下标,否则返回 -1

重点

  1. 区间的定义(左闭右 还是 左闭右

  2. 根据区间的定义决定:

    1. right 的初始值
    2. while (left ??? right) ???要使用 <= 还是 <
    3. nums[middle] != target 时 left 和 right 的更改
  3. 遵循循环不变量原则,即区间类型不变,即可解决2中疑问

  4. 注意点:计算middle时,要对结果进行取整操作(使用js时

代码

  1. 左闭右

    /**
     * @param {number[]} nums
     * @param {number} target
     * @return {number}
     */
    var search = function (nums, target) {
        let left = 0
        let right = nums.length - 1
        while (left <= right) {
            let middle = Math.floor((left + right) / 2) //向下舍入
            if (nums[middle] > target)
                right = middle - 1
            else if (nums[middle] < target)
                left = middle + 1
            else
                return middle
        }
        return -1
    };
    
  2. 左闭右

    /**
     * @param {number[]} nums
     * @param {number} target
     * @return {number}
     */
    var search = function (nums, target) {
        let left = 0
        let right = nums.length
        while (left < right) {
            let middle = Math.floor((left + right) / 2) //向下舍入
            if (nums[middle] > target)
                right = middle
            else if (nums[middle] < target)
                left = middle + 1
            else
                return middle
        }
        return -1
    };
    

2. 移除元素

题目

​ 给你一个数组 nums 和一个值 val,你需要 原地 移除所有数值等于 val 的元素,并返回移除后数组的新长度。 ​不要使用额外的数组空间,你必须仅使用 O(1) 额外空间并 原地 修改输入数组。 ​元素的顺序可以改变。你不需要考虑数组中超出新长度后面的元素。

双指针法

​ 即快慢指针法: 通过一个快指针和慢指针在一个for循环下完成两个for循环的工作。

思路

  1. 定义快慢指针

    • 快指针:寻找新数组的元素 ,新数组就是不含有目标元素的数组
    • 慢指针:指向更新新数组下标的位置
  2. 关键:要明确快慢指针的含义,这样后面的思路才能容易理解。

  3. 注意:双指针法(快慢指针法)在数组和链表的操作中是非常常见的,很多考察数组、链表、字符串等操作的面试题,都使用双指针法。

过程:

  1. 暴力解法

  2. 双指针法

代码

  1. 暴力解法

    /**
     * @param {number[]} nums
     * @param {number} val
     * @return {number}
     */
    var removeElement = function (nums, val) {
        let len = nums.length
        for (let i = 0; i < len; i++) {
            if (nums[i] === val) {
                for (let j = i; j < nums.length - 1; j++) {
                    nums[j] = nums[j + 1]
                }
                len--
                i--
            }
        }
        return len
    };
    
  2. 双指针法

    /**
     * @param {number[]} nums
     * @param {number} val
     * @return {number}
     */
    var removeElement = function (nums, val) {
        let slow = 0
        for (let fast = 0; fast < nums.length; fast++) {
            if (nums[fast] != val) {
                nums[slow++] = nums[fast]
            }
        }
        return slow
    };
    

3. 有序数组的平方

题目

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

注意点:数字有正有(主要考虑这种情况)

思路(数字有正有):

  1. 数组是有序的,但负数平方之后可能会打乱顺序。

  2. 数组平方的值从数组的两端向中间递减。

  3. 故考虑双指针法,i指向起始位置,j指向终止位置,对比i,j位置对应值的大小。则:

    定义一个新数组result,和nums数组一样的大小,让k指向result数组终止位置。

    1. 如果nums[i] * nums[i] > nums[j] * nums[j] 那么result[k--] = nums[i] * nums[i] 并移动 i 。
    2. 如果nums[i] * nums[i] <= nums[j] *nums[j] 那么result[k--] = nums[j] * nums[j] 并移动 j 。

    直至 i > j 结束。

过程:

代码

/**
 * @param {number[]} nums
 * @return {number[]}
 */
var sortedSquares = function (nums) {
    let result = [];
    let k = nums.length - 1;

    for (let i = 0, j = k; i <= j;) {
        if (nums[i] * nums[i] > nums[j] * nums[j]) {
            result[k--] = nums[i] * nums[i]
            i++
        } else {
            result[k--] = nums[j] * nums[j]
            j--
        }
    }

    return result
};

4. 长度最小的子数组

题目

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

滑动窗口

​ 所谓滑动窗口,就是不断的调节子序列的起始位置和终止位置,从而得出我们要想的结果。 ​ 在暴力解法中,是一个for循环滑动窗口的起始位置,一个for循环为滑动窗口的终止位置,用两个for循环完成了一个不断搜索区间的过程。而这里我们使用一个for循环来完成操作。

思路

  1. 用一个for循环,表示的是滑动窗口的起始位置,还是终止位置?——终止位置。若为起始位置,则又将陷入暴力解法。

  2. 确定如下三点:

    • 窗口内是什么?——满足其和 ≥ target 的长度最小的连续子数组。
    • 如何移动窗口的起始位置?——如果当前窗口的值大于target了,那么窗口就要向前移动了(即缩小窗口)。
    • 如何移动窗口的终止位置?——for循环里的控制变量 j 的值即为终止位置。
  3. 关键:理解滑动窗口起始位置的移动策略。

  4. 注意:对于子数组、子串相关问题可以优先考虑使用滑动窗口。

过程:

代码

/**
 * @param {number} target
 * @param {number[]} nums
 * @return {number}
 */
var minSubArrayLen = function (target, nums) {
    let sum = 0;
    let len = nums.length + 1;
    let i = 0;
    for (let j = 0; j < nums.length; j++) {
        sum += nums[j]
        while (sum >= target) {
            len = len > (j - i + 1) ? (j - i + 1) : len
            sum -= nums[i]
            i++
        }
    }

    return len === (nums.length + 1) ? 0 : len
};

5. 螺旋矩阵 II

题目

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

思路

  1. 模拟矩阵生成过程,坚持循环不变量原则(区间——采用左闭右开)

    • 填充上行从左到右
    • 填充右列从上到下
    • 填充下行从右到左
    • 填充左列从下到上
  2. 确定循环次数—— n / 2

  3. 考虑特殊情况—— n 为奇数时,中间元素需要单独赋值

  4. js 中多维数组的定义

    参考:js 多维数组

  5. 注意点:n 为奇数时,js 中 n / 2 为小数,故需要进行取整操作

图示:

代码

/**
 * @param {number} n
 * @return {number[][]}
 */
var generateMatrix = function (n) {
    let matrix = new Array(n).fill(0).map(() => new Array(n).fill(0))
    let start = 0
    let end = n - 1
    let k = 1;
    let num = Math.floor(n / 2)
    let mid = num
    while (num--) {
        for (let j = start; j < end; j++) {
            matrix[start][j] = k++
        }
        for (let i = start; i < end; i++) {
            matrix[i][end] = k++
        }
        for (let j = end; j > start; j--) {
            matrix[end][j] = k++
        }
        for (let i = end; i > start; i--) {
            matrix[i][start] = k++
        }
        start++;
        end--;
    }
    if (n % 2 != 0) {
        matrix[mid][mid] = k
    }
    return matrix
};