数据结构算法——数组(二)| 青训营笔记

101 阅读5分钟

这是我参与「第四届青训营 」笔记创作活动的第10天

话接上回数据结构算法——数组(一)

四、有序数组的平方

力扣原题链接

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

提示:

1、关键字:非递减,顺序排列,整数数组

2、因为数组是有序的,负数平方后可能成为最大数,所以数组平方的最大值必然在数组的两端

题解

//有序数组的平方
const sortedSquares = function(nums){
    let res = [],
        j = nums.length-1,
        i = 0,
        k = nums.length-1 //使用k来指向数组下标,代替使用数组方法来实现
    //进入循环
    while(i<=j){
        //取绝对值
        const left = Math.abs(nums[i]),
              right = Math.abs(nums[j])
        //判断左右两值大小
        if(right>left){
            res[k--] = right*right
            j--
        }else{
            res[k--] = left * left
            i++
        }
    }
    return res
}

//有序数组的平方
const sortedSquares2 = function(nums){
    let res = [],
        j = nums.length-1,
        i = 0,
        k = nums.length-1 //使用k来指向数组下标,代替使用数组方法来实现
    //进入循环
    while(i<=j){
        //取绝对值
        const left = nums[i]*nums[i],
              right = nums[j]*nums[j]
        //判断左右两值大小
        if(right>left){
            res[k--] = right
            //使用数组方法赋值res.unshift(right)
            j--
        }else{
            res[k--] = left
            //使用数组方法赋值res.unshift(left)
            i++
        }
    }
    return res
}

①和②的区别在于进入循环后

解释:

1、初始化:i,j左右指针分别指向首位下标

2、进入循环:因为考虑到有负数情况,所以可以使用方法①先用Math.abs()方法取绝对值后判断左右指针指向的值的大小;要么采用方法②“直接判断值的平方”

3、k值的作用:使用下标辅助数组的赋值,而不是直接用数组的方法进行赋值,运行更快。

 

五、长度最小的子数组

力扣原题链接

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

image.png

提示:可以使用移动窗口

什么是移动窗口?

这是数组操作的一个重要方法,就是不断地调节子序列地起始位置和终止位置,从而得出我们想要的结果。其实移动窗口的动画实现效果和双指针差不多,但是更像窗口的移动,所以命名移动窗口,可以也看作是双指针的一个特例方法。

题解:

//长度最小的子数组
var minSubArraylen = function(target,nums){
    const len = nums.length
    let l = r = sum = 0
    //以上代码相当于let l = 0,r = 0,sum = 0
    let res = len + 1//子数组最大不会超过自身
    while(r < len){
        //先更新nums后 右窗沿右移
        sum += nums[r++]
        //窗口滑动
        while(sum>=target){
            //取更短的,始终保留最小的长度
            res = res < r-l ?res :r-l
            //更新sum
            sum-= nums[l++]
        }
    }
    return res>len ?0 : res
}

解释:

1、窗口内是什么?就是满足其和>=target的长度最小的连续子数组

2、如何移动窗口的起始位置?当前窗口的值大于target时,窗口前移,即左边沿右移(即窗口的起始位置)

来个小疑问: 如何移动窗口的结束位置?从下面寻找答案吧

3、首先要了解两个关键:窗长r - l,窗口[ l,r ) 为什么是右开区间? 因为第一层while中的第一行sums+=nums[r++]表明右窗沿已经++指向下一个元素了,所以此时r下标指向的元素不属于窗口之内

4、let l = r = sum = 0:为什么可以连续赋值?因为等号=右边的值时基本数据类型,所以连续赋值操作可行,如果右边的值是引用类型则不行,因为基本数据类型和引用类型数据这两者的存储方式存储空间的不同。

5、进入第一层循环条件r < len:右窗沿小于len之前移动

6、进入第二层循环条件sum>=target:当前窗口值之和大于等于target时,窗口要前移l++

7、第二层循环内的sum-= nums[l++]:这里体现滑动窗口的精髓,不断变更l(窗口/子序列的起始位置)

 

省流版:

  • 初始 l = r = sum = 0 ,res = len+1

  • 进入第一层while( r < len),更新sum+=nums[r++]

  • 第二层while(sum >= target),先判断 取最小的窗口长度res = res < r-l ? res: r-l,再更新sum窗口前移 sum -= nums[l++]

时间复杂度:O(n),不要看有两层while就以为是O(n^2),主要看每个元素被操作的次数,每个元素在滑动窗口进来操作一次,出去操作一次,也就是每个元素被操作两次,所以时间复杂度2*n 也就是O(n)

空间复杂度:O(1)

六、螺旋矩阵II

力扣原题链接

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

image.png

image.png

提示: 关键词‘顺时针,螺旋排序,n*n阶正方形矩阵’

 

题解:

//螺旋矩阵
var generateMatrix = function(n){
    //起始位置
    let startX = startY = 0
    //旋转圈数
    let loop =  Math.floor(n/2)
    //中间位置
    let mid = Math.floor(n/2)
    //控制每一层填充元素个数
    let offset = 1
    //更新填充数字
    let count = 1
    //生成一个n*n 的正方形矩阵数组
    let res = new Array(n).fill(0).map(()=> new Array(n).fill(0))
    //先进入第一圈后再减一
    while(loop--){
        let row = startX ,col = startY
        //上行,从左到右,左闭右开
        for(;col < startY + n - offset ;col++){
            res[row][col] = count++
        }
        //右列,从上到下,左闭右开
        for(;row < startX + n - offset ;row++){
            res[row][col] = count++
        }
        //下行,从右到左,左闭右开
        for(;col > startY ;col--){
            res[row][col] = count++
        }
        //左列,从下到上,左闭右开
        for(;col > startX ;row--){
            res[row][col] = count++
        }
        //更新起始位置
        startX++
        startY++
        //更新offset,自加二意味着一圈完毕后,有两行两列被走过了
        offset +=2
    }
    //如果n为奇数,需单独给矩阵的最中间位置赋值
    if(n%2 === 1){
        res[mid][mid] = count
    }
    return res
}

上行:col 范围 [startY , startY +n - offset ) ,col ++

右列:row 范围 [startX , startX +n - offset ) ,row ++

下行:col 范围 [col,startY) ,col --

左列:row范围 [row,startX) ,row--

解释:

1、无论是哪一边,坚持左闭右开区间,为什么呢?来看下图

image.png

按 ‘红橙黄绿青蓝紫黑’ 排列的螺旋矩阵。

比如:

红色的范围[ 1,3 ]或者是[ 1,4 ) 对应公式[startY , startY +n - offset )左闭右开

橙色的范围[ 4,6 ]或者是[ 4,7 ) 对应公式[startX , startX +n - offset )左闭右开

等等

2、初始化:startX = startY = 0  loop--后startX++ ,startY++  ;

offset = 1  loop--后offset+=2(不可遍历已经被遍历的个数)

3、具体解释可以直接看代码中的注释,基本可以理解。

其他:

1、Array.map(function callback(item ,index ,arr){}):生成一个新数组,将更新后的元素数组返回

2、Fill(x, i ,j):使用x(字母、数组、字符串、数组等)替换数组中的元素。i是起始位置,j是终止位置,i j可省略,默认全部替换为x