代码随想录算法训练营第二天| 977.有序数组的平方、209.长度最小的子数组、 59.螺旋矩阵II

文章讲述了如何解决有序数组平方问题,利用双指针策略优化内存使用,同时介绍了滑动窗口算法在长度最小子数组问题中的应用,以及螺旋矩阵II的解题过程,强调了代码优化和性能考虑在算法设计中的重要性。
摘要由CSDN通过智能技术生成

977.有序数组的平方

题目链接:977.有序数组的平方

文档讲解:代码随想录/有序数组的平方

视频讲解:视频讲解-有序数组的平方

状态:已完成(2遍)

解题过程 

看到题目的第一想法

 因为涉及到要比较数字的平方,数组里有负数的出现,所以不能死板的按照原来的顺序依次放入新数组,我想到了:一个按非递减顺序排序的数组,那么它平方大的数肯定在两端(y=x^2),那么我将两个指针放在一左一右,将他们指向的数进行各自平方的比较。

/**
 * @param {number[]} nums
 * @return {number[]}
 */
var sortedSquares = function(nums) {
    let leftIndex = 0;//左指针从数组左边开始往右走
    let rightIndex = nums.length - 1;//右指针从数组右边开始往左走
    let ans = [];
        while(leftIndex<=rightIndex){//两者往中间夹
            if(nums[leftIndex]*nums[leftIndex]<nums[rightIndex]*nums[rightIndex]){//如果右指针指向数字的平方更大
                ans.unshift(nums[rightIndex]*nums[rightIndex]);//往数组前方添加此数平方
                rightIndex--;//右指针减1
            }else{//如果左指针指向数字平方更大或者相等
                ans.unshift(nums[leftIndex]*nums[leftIndex]);//往数组前方添加此数平方
                leftIndex++;//左指针加1
            }
        }
    return ans;
};

while的条件我犹豫了一下,要不要带等于号呢?思考了一下如果不带等于号的话,那肯定到最后会漏掉某一个数字,故加上。 

运行、提交都成功,那么看看代码随想录。

看完代码随想录之后的想法 

讲解中首先提到了一个暴力解法,确实简单的暴力解法被我忽视了。比较了一下代码,确实在while循环开始的时候可以定义一个平方变量让代码看上去更简短;数组用了unshift方法不知道是不是比再定义一个变量k来的简单一点。

GPT了一下:

当处理大型数据集时,内存的使用是一个重要的考虑因素。下面细说一下两种不同的内存使用方式可能对性能和内存占用产生的影响:

  1. 预先分配固定大小的数组:第二个代码使用了 new Array(n).fill(0) 创建了一个固定大小为 n 的数组,用于存储结果。这种方式在开始时就为结果数组分配了固定大小的内存空间,确保了数组的容量足够存储所有的平方值。这种方法的好处是可以避免在后续的循环中动态调整数组大小的开销,因为数组的大小已经确定。这可能会在某些情况下提高性能,特别是当处理的数据集很大时,避免了频繁的内存分配和拷贝操作。

  2. 直接使用空数组:第二个代码直接使用空数组 ans,并通过 ans.unshift() 方法将平方值添加到数组的开头。这种方式不需要预先分配固定大小的内存空间,而是根据需要动态调整数组的大小。每次添加元素时,需要将现有的元素依次向后移动,以腾出空间添加新的元素。这种方式的好处是可以节省内存,因为只有实际需要的空间才会被占用。然而,频繁的数组大小调整和元素移动操作可能会导致性能下降,特别是在处理大型数据集时。

综上所述,预先分配固定大小的数组可以提供一定的性能优势,特别是在处理大型数据集时。它避免了频繁的内存分配和拷贝操作,提高了代码的效率。然而,这也意味着需要提前确定数组的大小,并且可能占用更多的内存空间。而直接使用空数组可以节省内存,但可能会带来性能上的损失,特别是在频繁调整数组大小和元素移动的情况下。

/**
 * @param {number[]} nums
 * @return {number[]}
 */
var sortedSquares = function(nums) {
    let n = nums.length;
    let res = new Array(n).fill(0);
    let i = 0, j = n - 1, k = n - 1;
    while (i <= j) {
        let left = nums[i] * nums[i],
            right = nums[j] * nums[j];
        if (left < right) {
            res[k--] = right;
            j--;
        } else {
            res[k--] = left;
            i++;
        }
    }
    return res;
};

总结

这道题总体思路和讲解基本一致,就是学到了定义空数组的更优解,还复习了以下知识点:

从数组的前面添加数字,可以使用以下方法:

1.使用 unshift() 方法:unshift() 方法可向数组的开头添加一个或多个元素,并返回新的数组长度。

const numbers = [2, 3, 4, 5];
numbers.unshift(1); // 在数组的开头添加数字1
console.log(numbers); // 输出: [1, 2, 3, 4, 5]

2.使用扩展运算符(Spread Operator):扩展运算符 ... 可以将一个数组展开,将其元素添加到新的数组中。 

const numbers = [2, 3, 4, 5];
const newNumbers = [1, ...numbers]; // 在开头添加数字1
console.log(newNumbers); // 输出: [1, 2, 3, 4, 5]

3.使用数组的 concat() 方法:concat() 方法用于连接多个数组,并返回一个新的数组。可以将要添加的数字作为一个单元素数组与原数组进行连接。 

const numbers = [2, 3, 4, 5];
const newNumbers = [1].concat(numbers); // 在开头添加数字1
console.log(newNumbers); // 输出: [1, 2, 3, 4, 5]


 209.长度最小的子数组

题目链接:209.长度最小的子数组

文档讲解:代码随想录/长度最小的子数组

视频讲解:视频讲解-长度最小的子数组

状态:已完成(2遍)

解题过程  

看到题目的第一想法

说是用滑动窗口,这窗口怎么设置呢,怎么滑动呢,没有想法。故用两层for循环,果不其然超过时间限制。

/**
 * @param {number} target
 * @param {number[]} nums
 * @return {number}
 */
var minSubArrayLen = function(target, nums) {
    let ans = [];
    for(let i = 0;i<nums.length;i++){
        for(let j = nums.length;j>i;j--){
            let arr = nums.slice(i,j);
            if(arr.reduce((a, b) => a + b, 0)>=target){
                ans.push(arr.length);
            }
        }
    }
    return ans.length?ans.sort((a,b)=>a-b)[0]:0;
};

 看完代码随想录之后的想法 

了解了滑动窗口与双指针的联系,此时的for循环应该表示滑动窗口的终止位置以免落入暴力解法的怪圈。可以发现滑动窗口的精妙之处在于根据当前子序列和大小的情况,不断调节子序列的起始位置。从而将O(n^2)暴力解法降为O(n)。

看了视频讲解之后手搓一版:

/**
 * @param {number} target
 * @param {number[]} nums
 * @return {number}
 */
var minSubArrayLen = function(target, nums) {
    let i = 0,sum = 0;//i代表子数组的左端,sum代表当前子数组的和
    let ans = Infinity;
    for(let j = 0;j<nums.length;j++){//j代表子数组的右端
        sum+=nums[j]//sum每次加上右移的新数
        while(sum>=target){//当和大于target的时候
            ans = Math.min(ans,j-i+1)//记录一下当前长度
            sum-=nums[i];//sum把最左端的数给去掉
            i++;//左端指针加一
        }
    }
    return ans == Infinity?0:ans;
};

 发现和文字讲解版区别不大,for和while循环的区别

文字讲解版代码如下:

var minSubArrayLen = function(target, nums) {
    let start, end
    start = end = 0
    let sum = 0
    let len = nums.length
    let ans = Infinity
    
    while(end < len){
        sum += nums[end];
        while (sum >= target) {
            ans = Math.min(ans, end - start + 1);
            sum -= nums[start];
            start++;
        }
        end++;
    }
    return ans === Infinity ? 0 : ans
};

总结

通过这道题认识了滑动窗口的操作步骤,理解了单个循环遍历的指针为什么在子数组的右端。


 59.螺旋矩阵II

题目链接:59.螺旋矩阵II

文档讲解:代码随想录/螺旋矩阵

视频讲解:视频讲解-螺旋矩阵

状态:已完成(2遍)

解题过程  

看到题目的第一想法

哥们没有想法,哈哈。

 看完代码随想录之后的想法 

这里的左闭右开和之前的二分查找联动起来了,一定要坚持一个循环不变量,过程还是很复杂的,最好画个图对应理解一下。

看了视频讲解之后手搓一版:

/**
 * @param {number} n
 * @return {number[][]}
 */
var generateMatrix = function(n) {
    let startX = 0,startY = 0,offset = 1;//开始时的横纵坐标,以及左闭右开每一条边最后那个不包含在内的元素
    let loop = Math.floor(n/2);//转的圈数
    let mid = Math.floor(n/2);//如果n为奇数中间有个单独的数字
    let count = 1;//计数
    let ans = new Array(n).fill(0).map(()=>new Array(n).fill(0));
    while(loop>0){
        let x= startX,y=startY;
        for(;y<n-offset;y++){//上
            ans[x][y]=count++;
        }
        for(;x<n-offset;x++){//右
            ans[x][y] = count++;
        }
        for(;y>startY;y--){//下
            ans[x][y] = count++;
        }
        for(;x>startX;x--){//左
            ans[x][y] = count++;
        }
        startX++;
        startY++;
        offset++;
        loop--;
    }
    if(n%2===1){
        ans[mid][mid]=count;
    }
    return ans;
};

总结

初次接触螺旋矩阵,看上去很可怕,实操起来也还是很可怕,掌握了方法之后希望下次二刷能够做出来。

二刷发现一拿到题还是没有想法。。确实只能多看多刷多思考

  • 15
    点赞
  • 9
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值