代码随想录-数组专题

71 阅读5分钟

数组和链表的区别

数组理论基础

数组链表
数组是在内存中是连续分布。链表在内存中不是连续分布的。
数组在定义的时候,长度就是固定的,如果想改动数组的长度,就需要重新定义一个新的数组。链表的长度可以是不固定的,并且可以动态增删, 适合数据量不固定,频繁增删,较少查询的场景。

704. 二分查找

var search = function(nums, target) {
    // TODO: 确定初始左右边界
    let left=0, right = nums.length-1
    // TODO: while循环中 二分不断缩小范围找到traget的下标
    // 边界始终始终是左闭右闭[left, right]
    // 所以,left == right 有意义,但是left > right 时没有意义,比如[2, 1],此时跳出while
    while(left<=right){
        // TODO: 计算mid
        // 防止溢出,注意向下取整
        let mid = Math.floor(left + (right-left)/2)
        let val = nums[mid]
        // TODO: 如果中间值小于目标值往中间值右边找,大于目标值往中间值左边找,相等返回结果。
        if (val === target) return mid
        else if (val < target) left = mid + 1
        else right = mid - 1
    }
    return -1
};

27. 移除元素

题目注意:

  • 原地删除(考虑双指针)
  • 要删除的val可能存在多个

示例 1:

输入:nums = [3,2,2,3], val = 3 
输出:2, nums = [2,2]

示例 2:

输入:nums = [0,1,2,2,3,0,4,2], val = 2
输出:5, nums = [0,1,4,0,3]
var removeElement = function(nums, val) {
    // TODO: 初始化双指针
    let left = right = 0
    // TODO: 用right指针遍历nums数组,超过nums长度就退出循环
    while(right < nums.length) {
        // TODO: 分情况讨论
        // right不是要删除的val:用right覆盖left的值,left再++,始终保证[0, left)区间的值没有val,[left, ...)有val;right++继续遍历下一个值
        // right是要删除的val:跳过val/right值,right++继续遍历下一个值
        if ( nums[right] !== val) {
            nums[left] =  nums[right]
            left++
        }
        right++ // 无论如何right++继续遍历下一个值
    }
    return left // 删除val后的数组[0,left)长度就是left
};

977.有序数组的平方

题目注意:

  • 非递减顺序:数组升序,还可能元素相等
  • 返回新数组(考虑新建数组)

示例 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]
var sortedSquares = function(nums) {
    // TODO: 初始化双指针
    let left = 0, right = idx = nums.length - 1
    // TODO: 创建一个新数组,初始化为0
    let res = new Array(nums.length).fill(0)
    // TODO: 遍历一遍nums数组
    // left从头开始,right从尾开始,left>right说明每一个元素都遍历了一遍退出循环
    while(left <= right) {
        // TODO: 准备好left平方值,和right平方值
        let left_squares = nums[left]*nums[left]
        let right_squares = nums[right]*nums[right]
        // TODO: 从nums两头取元素,计算平方值,从res的右往左填写
        // nums两头元素的平方值一定是最大的,而返回结果res必须是非递减顺序,所以从大往小,即从右往左填写res最方便
        // 如果left平方大,res填写left平方;如果right平方大,res填写right平方
        // idx控制res数组位置。填完idx--,left++或者right--
        // 注意:=在哪里无所谓
        if (left_squares >= right_squares) {
            res[idx--] = left_squares
            left++
        }else {
            res[idx--] = right_squares
            right--
         }
    }
    return res
};

209.长度最小的子数组

题目注意:

  • 返回:长度最小的>=target的连续集合
    • 值的总和>=target
    • 最小(收缩滑动窗口)
    • 连续子数组(连续 考虑滑动窗口)

示例 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
  • 滑动窗口,是双指针的一种
    • 重点1:第一个while循环是移动起始位置,还是终止位置?
      • while里的必须移动终止位置:因为如果是移动起始位置,那么还得用2个for循环遍历每个起始位置和终止位置的数组
    • 重点2:如何移动起始位置?
      • right指针遍历每一个终止位置,用滑动的策略移动起始位置
var minSubArrayLen = function(target, nums) {
    let left = right = 0
    let minL = Infinity
    let sum = 0
    // TODO: right指针遍历nums数组每个元素,超过nums长度就退出循环
    while (right < nums.length){
        // TODO: 扩大滑动窗口
        sum+=nums[right]
        // TODO: 如果集合值总和sum 大于等于 target,集合满足条件,就记录最小长度minL,再缩小窗口,直到获取最小长度的窗口。
        // 必须用while而不是if,是为了缩小滑动窗口,找到最小的集合。比如 1 1 1 100 , target = 100, 用if,结果集合是 [1,1,1,100],用while, 结果集合是最小的 [1,100]
        while(sum >= target){
            // TODO: 记录满足条件的最小集合长度
            subL = right - left + 1  // 集合的长度
            minL = Math.min(minL, subL) // 最小集合长度
            // TODO: 缩小滑动窗口,找到最小的符合 >=target的集合
            sum-=nums[left]
            left++
        }
        right++ // 遍历每一个终止位置
    }
    return minL === Infinity ? 0 : minL
};

59.螺旋矩阵II

模拟过程,不涉及算法,十分考察对代码的掌控能力

示例 1:

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

示例 2:

输入: n = 1
输出: [[1]]
var generateMatrix = function(n) {
    // TODO: 初始化
    // loop - 循环圈数, mid - 最中间的的位置
    let loop = mid = Math.floor(n/2)
    // 每圈初始坐标
    let startX = startY = 0
    let endX = endY = n-1
    // i,j也可以在每个for循环里定义
    let i, j
    // 结果是一个二维数组
    let res = new Array(n).fill(0).map(()=> new Array(n).fill(0))
    // 数组里的值是递增的
    let val = 1
    // TODO: 每次while循环填写一圈的值。
    // loop减到0,退出循环。
    while(loop--){
        // 填写上边:i固定startX,j移动填写[startY, endY)
        for(j = startY; j < endY; j++) res[startX][j] = val++
        // 右边:j固定endY,i移动[startX, endX)
        for(i = startX; i < endX; i++) res[i][endY] = val++
        // 下边:i固定endX,j移动[endY, startY)
        for(j = endY; j > startY; j--) res[endX][j] = val++
        // 左边:j固定startY,i移动[endX, startX)
        for(i = endX; i > startX; i--) res[i][startY] = val++
        // 起止位置、终止位置缩小1(边界缩小一圈)
        startX++
        startY++
        endX--
        endY--
    }
    // n是奇数,填写漏下的最中间的值
    if (n%2===1) res[mid][mid] = val
    return res