算法Day2|977.有序数组的平方、209.长度最小的子数组、59.螺旋矩阵II

刷题第二天。继续加油!


977.有序数组的平方

题目链接:https://leetcode.cn/problems/squares-of-a-sorted-array/description/

文章讲解:https://www.programmercarl.com/0977.%E6%9C%89%E5%BA%8F%E6%95%B0%E7%BB%84%E7%9A%84%E5%B9%B3%E6%96%B9.html

视频讲解:https://www.bilibili.com/video/BV1QB4y1D7ep/?vd_source=4db246e029eadef34f78b45063edc459

题目: 

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

思想及代码:

1.暴力解法:

每个数平方之后,排个序。

代码如下:

class Solution {
public:
    vector<int> sortedSquares(vector<int>& A) {
        for (int i = 0; i < A.size(); i++) {
            A[i] *= A[i];
        }
        sort(A.begin(), A.end()); // 快速排序
        return A;
    }
};

时间复杂度:O(n + nlogn),可以说是O(nlogn)的时间复杂度。

2.双指针:

题目强调 非递减顺序数组(数组是有序的),所以,最大平方值的元素必定在数组的两端,不是在左端就是在右端,不可能在中间。

利用双指针,两个指针从两端逐渐向中间靠拢,从而得到一个从大到小的数组。

之后将得到的从大到小的新数组,其下标由大到小 依次从右向左逐渐更新,从而得到符合题目的数组。

代码如下:

class Solution {
public:
    vector<int> sortedSquares(vector<int>& nums) {
        int k = nums.size() - 1;
        vector<int> result(nums.size(),0); //定义一个新数组去接收数据
        for(int i = 0,j = k;i <= j; ) //i<=j;如果i<j的话,若出现i=j时,此时循环退出,这样就丢失了一个数据元素
        {
            if(nums[i] * nums[i] < nums[j] * nums[j])
            {
                result[k] = nums[j] * nums[j];
                k--;
                j--;
            }
            else
            {
                result[k] = nums[i] * nums[i];
                k--;
                i++;
            }
        }
        return result;
    }
};

 时间复杂度:O(n)


209.长度最小的子数组

 题目链接:https://leetcode.cn/problems/minimum-size-subarray-sum/submissions/

文章讲解:https://www.programmercarl.com/0209.%E9%95%BF%E5%BA%A6%E6%9C%80%E5%B0%8F%E7%9A%84%E5%AD%90%E6%95%B0%E7%BB%84.html#%E7%AE%97%E6%B3%95%E5%85%AC%E5%BC%80%E8%AF%BE

视频讲解:https://www.bilibili.com/video/BV1tZ4y1q7XE/?spm_id_from=333.788&vd_source=4db246e029eadef34f78b45063edc459

题目: 

给定一个含有 n 个正整数的数组和一个正整数 target 。

找出该数组中满足其总和 大于等于 target 长度最小的 连续子数组[numsl, numsl+1, ..., numsr-1, numsr] ,并返回其长度如果不存在符合条件的子数组,返回 0 。

思路及代码: 

1.暴力解法:

暴力解法是两层嵌套for循环,不断寻找符合条件的子序列。

第一层循环从 i = 0 开始遍历至数组末尾,第二层循环从 j = i 开始遍历至找到总和大于等于 target 的连续子数组,并将该连续子数组的长度与之前找到的子数组长度相比较,若这个子数组长度更短,则更新结果。

将初始长度设置为 INT32_MAX 或 nums.size() + 1,用于判断是否不存在符合条件的子数组,通过判断结果是否被赋值,若未被赋值就返回0,说明没有符合条件的子序列。

代码如下:

class Solution {
public:
    int minSubArrayLen(int s, vector<int>& nums) {
        int result = INT32_MAX; // 最终的结果
        int sum = 0; // 子序列的数值之和
        int subLength = 0; // 子序列的长度
        for (int i = 0; i < nums.size(); i++) { // 设置子序列起点为i
            sum = 0;
            for (int j = i; j < nums.size(); j++) { // 设置子序列终止位置为j
                sum += nums[j];
                if (sum >= s) { // 一旦发现子序列和超过了s,更新result
                    subLength = j - i + 1; // 取子序列的长度
                    result = result < subLength ? result : subLength;
                    break; // 因为我们是找符合条件最短的子序列,所以一旦符合条件就break
                }
            }
        }
        // 如果result没有被赋值的话,就返回0,说明没有符合条件的子序列
        return result == INT32_MAX ? 0 : result;
    }
};

时间复杂度:O(n)

空间复杂度:O(1)

2.滑动窗口(思想类似于双指针思想)

精华:如何移动起始位置的指针。

所谓滑动窗口,就是不断的调节子序列的起始位置和终止位置,从而得出我们要想的结果

滑动窗口只利用了一个for循环,这个循环的索引,一定是表示 滑动窗口的终止位置

窗口的结束位置 j 就是遍历数组的指针,也就是for循环里的索引。i 则代表窗口的起始位置。

1.窗口的结束位置 j ,首先不断右移并执行 sum += nums[j] ,计算当前从指针 i 到 j 的子数组之和。
2.当sum >= target时,此时得到一个总和大于等于 target 的连续子数组,其长度为subL = j - i + 1,此时需判断该长度是否比已记录的最短长度要小,若小于则更新最短长度。
3.随后,窗口的起始指针 i 开始右移,缩小窗口长度,注意可能存在右移后其子数组总和仍大于等于 target 的情况,所以此处判断应该是 while 而不是 for,还需要将 i 原来指向的数值在 sum 中减掉。
3.窗口的起始指针 i 右移至窗口中的子数组不满足条件后,此时需要结束指针 j 开始右移,直至窗口中的子数组再次满足条件,即跳转至第1步,当 j == nums.size() 时,表示数组内全部可能的子数组遍历完成,返回结果。
4.最后同样通过将初始长度设置为 INT32_MAX 或 nums.size() + 1,判断是否不存在符合条件的子数组,通过判断结果是否被赋值,若未被赋值就返回0,说明没有符合条件的子序列。shijia

class Solution {
public:
    int minSubArrayLen(int target, vector<int>& nums) {
        int i = 0;
        int j = 0;
        int sum = 0;
        int subL = 0;
        int result = INT32_MAX;

        for(j; j < nums.size(); j ++)
        {
            sum += nums[j];

            while(sum >= target) //要用while,让i不断向后走,不可以用if
            {
                subL = j - i + 1;
                
                //这里体现出滑动窗口的精髓之处,不断变更i(子序列的起始位置)
                sum -= nums[i];
                i++;
                result = result < subL ? result : subL;
            }
        }
        return result == INT32_MAX ? 0 : result;
    }
};

时间复杂度:O(n)

空间复杂度:O(1)

关于时间复杂度,不要以为for里放一个while就以为是O(n^2), 主要是看 每一个元素被操作的次数,每个元素在滑动窗后进来操作一次,出去操作一次,每个元素都是被操作两次,所以时间复杂度是 2 × n 也就是O(n)。 


 59.螺旋矩阵II

题目:

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

思路及代码: 

这道题目可以说在面试中出现频率较高的题目,本题并不涉及到什么算法,就是模拟过程,但却十分考察对代码的掌控能力。

一定要坚持 循环不变量原则。即:对每条边的处理规则是一致的,坚持同一条处理原则。

模拟顺时针画矩阵的过程:

  • 填充上行从左到右
  • 填充右列从上到下
  • 填充下行从右到左
  • 填充左列从下到上

由外向内一圈一圈这么画下去。

每条边坚持 左闭右开 或者 左开右闭 原则。

本次解法采用 左闭右开。

class Solution {
public:
    vector<vector<int>> generateMatrix(int n) {
        vector<vector<int>> res(n, vector<int>(n, 0)); // 使用vector定义一个二维数组
        int startx = 0, starty = 0; // 定义每循环一个圈的起始位置
        int loop = n / 2; // 每个圈循环几次,例如n为奇数3,那么loop = 1 只是循环一圈,矩阵中间的值需要单独处理
        int mid = n / 2; // 矩阵中间的位置,例如:n为3, 中间的位置就是(1,1),n为5,中间位置为(2, 2)
        int count = 1; // 用来给矩阵中每一个空格赋值
        int offset = 1; // 需要控制每一条边遍历的长度,每次循环右边界收缩一位
        int i,j;
        while (loop --) {
            i = startx;
            j = starty;

            // 下面开始的四个for就是模拟转了一圈
            // 模拟填充上行从左到右(左闭右开)
            for (j = starty; j < n - offset; j++) {
                res[startx][j] = count++;
            }
            // 模拟填充右列从上到下(左闭右开)
            for (i = startx; i < n - offset; i++) {
                res[i][j] = count++;
            }
            // 模拟填充下行从右到左(左闭右开)
            for (; j > starty; j--) {
                res[i][j] = count++;
            }
            // 模拟填充左列从下到上(左闭右开)
            for (; i > startx; i--) {
                res[i][j] = count++;
            }

            // 第二圈开始的时候,起始位置要各自加1, 例如:第一圈起始位置是(0, 0),第二圈起始位置是(1, 1)
            startx++;
            starty++;

            // offset 控制每一圈里每一条边遍历的长度
            offset += 1;
        }

        // 如果n为奇数的话,需要单独给矩阵最中间的位置赋值
        if (n % 2) {
            res[mid][mid] = count;
        }
        return res;
    }
};

时间复杂度:O(n^2)

if (n % 2) {
            res[mid][mid] = count;
        }

不能写为

if (n % 2) {
            res[i][j] = count;
        }

        n为奇数时,因为写为 res[i][j]的话,当前面的循环结束后,ij 的值会指向矩阵的边界之外,因为它们在循环中的终止条件会使它们递增或递减到超出矩阵的范围。ij 的值已经超出了矩阵的边界,因此将会导致数组越界错误。

补充:

vector<vector<int>> res(n, vector<int>(n));//首先创建一个空的二维数组,n行n列

利用vector 创建二维数组。

 vector 创建二维数组相关学习:

https://www.cnblogs.com/yccy/p/17411775.html

  • 45
    点赞
  • 36
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值