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

文章讲述了如何利用双指针法和滑动窗口法解决两个编程问题:有序数组的平方计算和长度最小的连续子数组。作者通过实例和解题思路分析了如何在有序数组中高效地求平方并保持非递减顺序,以及如何找到满足特定条件的连续子数组的最小子数组长度。
摘要由CSDN通过智能技术生成

997.有序数组的平方

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

示例一:

输入:nums = [-4,-1,0,3,10]
输出:[0,1,9,16,100]
解释:平方后,数组变为 [16,1,0,9,100]
排序后,数组变为 [0,1,9,16,100]

示例二:

输入:nums = [-7,-3,2,3,11]
输出:[4,9,9,49,121]

提示:

1 <= nums.length <= 104
-104 <= nums[i] <= 104
nums 已按 非递减顺序 排序

解题思路:

  1. 关键词提取:整数数组、数组有序、元素乘积
  2. 暴力解法:两次遍历,一次遍历取乘积,一次遍历排序,时间复杂度是o(n + nlogn)
  3. 双指针法:一次遍历,取乘积时同步做排序,时间复杂度是o(n)
    1. 数组有序,由负数->正数依次排列,因此两端的元素的乘积是最大的,才可以采用双指针
    2. 左右指针的取值,左指针取下标0,右指针取下标size-1
    3. 为了不影响原数组的顺序,新建一个数组result存储元素乘积
    4. 从数组两端开始比较,每次都比较两端元素,比较之后,将较大元素乘积存放到数组中
    5. 移动已存放元素对应的指针,缩短数组范围,重复第4点,直至数组的长度为0(左右指针相遇)

双指针法代码如下:

class Solution {
public:
    vector<int> sortedSquares(vector<int>& nums) {
        // 双指针法,数组是有序的
        // 申请一个新的数组,大小与nums一致,存放nums元素乘积
        vector<int> result(nums.size(), 0);
        int len = nums.size() - 1;
        // 定义左指针与右指针,分别从nums两边向内移动
        int left = 0;
        int right = nums.size() - 1;
        // 两个指针相遇之后,说明元素检索完成
        while(left <= right)
        {
            // 记录左指针的元素乘积与右指针的元素乘积
            int leftSum = nums[left]* nums[left];
            int rightSum = nums[right] * nums[right];
            // 将大的元素放到result数组中,由大到小依次摆放,也就是从后往前摆放
            if(leftSum > rightSum)
            {
                result[len--] = leftSum;
                left++;
            }
            else if(leftSum <= rightSum)
            {
                result[len--] = rightSum;
                right--;
            }
        }
        return result;
    }
};

总结:

  1. 第一次做的时候,仅仅会暴力解法,但是会有一个想法是靠近双指针法的,只是比较模糊。
    1)按照提示,思路很自然,就是先乘积,后排序。
    2)提交之后,再来思考,会发现,以0为界限,左侧的负数乘积是先大后小,右侧的正数乘积是先小后大。可以先找到正数和负数交界的位置,由中间向两边取数,由小到大排列。但是这个很难去实现,搁置了。
  2. 跟着代码随想录刷题的时候,才发现可以用双指针法。
    1)与第二点的想法类似,但是是从两边开始,由大到小,这个符合双指针法的普遍使用方式。
    2)做过移除元素的题目之后,就更容易理解新建一个数组,依次存放有序的元素乘积的做法。

209.长度最小的子数组

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

示例一:

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

示例二:

输入:target = 11, nums = [1,1,1,1,1,1,1,1]
输出:0

提示:

1 <= target <= 109
1 <= nums.length <= 105
1 <= nums[i] <= 105

解题思路:

  1. 关键词提取:正整数数组、目标值、长度最小、连续子数组
  2. 暴力法:两次遍历,第一次遍历每一个元素,第二次遍历由当前元素往后的符合要求的最短长度,时间复杂度o(n^2)
  3. 滑动窗口法:一次遍历,双指针,一个指针负责窗口的起始位置,一个指针负责窗口的结束位置
    1. 窗口的含义是:不符合要求的、连续的子数组
    2. 一次遍历,以结束位置的指针为主,当该指针指向数组最后一个元素时,遍历结束
    3. 在遍历的过程中,假如窗口的值符合条件,记录当前的最短长度
    4. 记录长度后,缩小窗口,移动起始位置的指针,直到窗口中的值不符合条件为止
    5. 假如遍历结束,最短长度为初值,返回0

滑动窗口法代码如下:

class Solution {
public:
    int minSubArrayLen(int target, vector<int>& nums) {
        // 滑动窗口法
        // 双指针实现,窗口中的元素,相加后,值大于等于目标值,窗口由起始位置与终止位置框选出来
        int left = 0;
        int right = 0;
        int sum = 0;
        int result = INT32_MAX;
        // 终止指针已经指向数组最后一个元素
        while(right < nums.size())
        {
            // 将下一个元素纳进窗口
            sum = sum + nums[right];
            // 窗口中的值符合条件,需要缩小窗口,移动起始位置,将窗口缩小至不符合条件,并且记录窗口的最小长度
            while(sum >= target)
            {
                int subSum = right - left + 1;
                result = result > subSum ? subSum : result;
                sum = sum - nums[left];
                left++;
            }
            // 终止指针往右移动
            right++;
        }
        // 若找不到符合条件的窗口
        if(result == INT32_MAX)
        {
            result = 0;
        }
        return result;
    }
};

总结:

  1. 第一次做的时候,仅仅会暴力解法。
    1)最短的、连续的子数组,想到的就是以每个元素为子数组的第一个元素,遍历一遍,找到符合条件的子数组。

    2)在所有的子数组中,找到最短的长度。

    3)很难想到滑动窗口法,而且也跟双指针没有什么关系。

  2. 跟着代码随想录刷题的时候,发现还有滑动窗口法,而且对于窗口、起始指针、终止指针都给了很明确的解释。
    1)知道要用滑动窗口法的时候,会在想,双指针的作用分别是什么,该怎么移动。
    2)但是,最重要的一点,就是窗口的定义没有思考清楚,导致起始位置的移动是错误的。我只考虑到满足条件后,起始位置往右移动一位,删除掉窗口左侧的一个元素,然后,就继续往窗口右侧添加新的元素。仔细看代码随想录的解释才明白原因。

    3)现在做题是有动力的,原因是代码随想录的视频讲解和文字讲解都特别清晰,而且是系统性地学习。相比于之前做不出来看题解,透过官方题解以及别人的题解去学习,属实太过痛苦,很难掌握原理,并且很难去应用这个原理。即使是相同原理的题目,也会夹杂着新的知识,这让一个新手很难适应,并且很难找到正反馈。感谢carl哥,让我找到了算法的乐趣,可以触碰新的领域。

59.螺旋矩阵II

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

示例一:

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

示例二:

输入:n = 1
输出:[[1]]

提示:

1 <= n <= 20

解题思路:

  1. 关键词提取:正整数n、1-n^2、顺时针旋转、n*n矩阵
  2. 模拟法:
    1)需要参数去做累加,加到n*n为止
    2)一共4条边,每条边都用一个参数去记录起始位置
    3)明确循环不变量,就是每条边的起始位置与结束位置,都是由第一个元素开始,倒数第二个元素结束
    4)针对每条边,采用一次遍历,将对应的值放进去二维数组中
    5)一共循环的次数是n/2,左右、上下,两条边分别往中间靠拢
    6)针对于奇数与偶数下的矩阵中心值不一致,需要单独再做填充

模拟法代码如下:

class Solution {
public:
    vector<vector<int>> generateMatrix(int n) {
        vector<vector<int>> res(n, vector<int>(n, 0));
        // 一共n*n个元素,需要一个数字记录
        int count = 1;
        // 一共四条边,需要由一个参数记录
        int firstRow = 0;
        int endCol = n - 1;
        int endRow = n - 1;
        int firstCol = 0;
        // 转多少圈
        int loop = n / 2;
        // 将所有的值放置完
        // 共4条边,每条边都要放n!的数
        // 顺时针旋转
        // 循环不变量,每条边的长度一定,并且相等,都是n-2
        while(loop--)
        {
            // 第一条边,也是第0行,从第0个位置开始放,到倒数第2个位置
            for(int i = firstRow; i < endCol; i++)
            {
                res[firstRow][i] = count;
                count++;
            }

            // 第二条边,也是第n-1列,从第0个位置开始放,到倒数第2个位置
            for(int j = firstRow; j < endRow; j++)
            {
                res[j][endCol] = count;
                count++;
            }

            // 第三条边,也是第n-1行,从倒数第1个位置开始放,到第1个位置
            for(int k = endRow; k > firstCol; k--)
            {
                res[endRow][k] = count;
                count++;
            }

            // 第四条边,也是第0列,从倒数第1个位置开始放,到第1个位置
            for(int m = endRow; m > firstRow; m--)
            {
                res[m][firstCol] = count;
                count++;
            }

            // 各条边的起始位置均移位1
            firstRow++;
            endRow--;
            endCol--;
            firstCol++;
        }
        // n为奇数与n为偶数,矩阵中心会有区别
        if(n % 2 == 1)
        {
            res[n/2][n/2] = count;
        }
        return res;
    }
};

总结:

  1. 第一次做的时候,一眼看过去,就是要放弃的题目,没有继续下去的勇气。
    1)逻辑性不够强,压根不理解题目的意思,找不到解题的突破口。

    2)在看了一遍代码随想录的讲解后,开始尝试自己做,每个循环下,都将整个二维数组打印出来,看是否正确。

    3)不断修改边界与判断条件,调试了整整5个小时,才勉强将这个题目提交通过。

  2. 跟着代码随想录刷题的时候,会让自己的逻辑清晰很多,但容易陷入典型的一听就会,一做就不会。
    1)第一个点就是,听着carl哥讲解的时候,是那种很有信心可以解决这道题目的状态,而且这道题很简单,不难,这点对于一个初学者来说,特别重要,即使第一次做不出来,后面多做几次,类似的,能做出来就是学会了,所以,不用担心。
    2)第二个点就是,有个核心的点叫循环不变量,这个题目与二分法会有相关性。题目自身没有相关性,但是carl哥找到了这个相关性,并且以此为示例,让初学者对这个概念有很深的印象。
    3)在我二刷的时候,会发现,这道题目的思路是水到渠成的,尽管在最后的一个循环中,起始位置和结束位置写错了,但是通过打印二维数组以及打印m的值,很快就能找到问题点。我的解答是4条边各自采用一个参数记录起始位置,这个还能再做优化,因为发现在循环中,会有重复使用的参数,最终可以优化为三个参数。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值