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

目录

一、977.有序数组的平方

二、209.长度最小的子数组

三、59.螺旋矩阵II

四、总结


一、977.有序数组的平方

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

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

示例 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]

提示:

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

第一眼看到题目的思路:暴力,是肯定可以解决的,每个数平方之后,排个序。

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

这个时间复杂度是 O(n + nlogn), 可以说是O(nlogn)的时间复杂度,但为了和下面双指针法算法时间复杂度有鲜明对比,我记为 O(n + nlog n)。

双指针法

仔细一想,昨天写过的双指针法也是可以解决的。

数组是有序的,只不过因为有负数的存在,平方之后最小的数可能变为最大的数,但最大的数只可能出现在数组两端,不可能出现在数组中间。

所以用leftIndex指向数组左端,rightIndex指向数组右端,然后定义一个与原数组相同长度的数组ans,让flag指向ans数组终止位置。

如果 nums[leftIndex] * nums[leftIndex] > nums[rightIndex] * nums[rightIndex]   那么 ans[flag--] = nums[leftIndex] * nums[leftIndex]; leftIndex++;

如果 nums[leftIndex] * nums[leftIndex] <= nums[rightIndex] * nums[rightIndex]   那么 ans[flag--] = nums[rightIndex] * nums[rightIndex]; rightIndex++;

代码如下:

class Solution {
public:
    vector<int> sortedSquares(vector<int>& nums) {
        int size = nums.size();
        vector<int> ans(size);
        int flag = size - 1;
        int leftIndex = 0, rightIndex = size -1;
        while (leftIndex <= rightIndex) {
            if (nums[leftIndex] * nums[leftIndex] > nums[rightIndex] * nums[rightIndex]) {
                ans[flag--] = nums[leftIndex] * nums[leftIndex];
                leftIndex++;
            } else {
                ans[flag--] = nums[rightIndex] * nums[rightIndex];
                rightIndex--;
            }
        }
        return ans;
    }
};

此时的时间复杂度为O(n),相对于暴力排序的解法O(n + nlog n)还是提升不少的。

由于最开始没考虑将平方后的数逆序存储在ans中,导致得到的ans数组需要反转操作,所以去查了一下reverse函数的用法。

C++ reverse函数的用法:

reverse函数功能是逆序(或反转),多用于字符串、数组、容器。reverse函数用于反转在[first,last)范围内的顺序(包括first指向的元素,不包括last指向的元素),reverse函数无返回值

eg.

string str = "hello world , hi";
reverse(str.begin(), str.end());  //str结果为 ih , dlrow olleh
vector v = {5,4,3,2,1};
reverse(v.begin(), v.end());//容器v的值变为1,2,3,4,5

二、209.长度最小的子数组

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

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

示例 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 <= target <= 109
  • 1 <= nums.length <= 105
  • 1 <= nums[i] <= 105

第一眼看到题目的思路:用昨天双指针法的思路可以解决。定义fastIndex和slowIndex两个指针,子数组的和sum = 0。

当sum < target时,sum += nums[fastIndex++];

当sum >= target时,sum -= nums[leftIndex++]; 同时更新子数组的长度。这一步需要一直进行到sum < target为止,所以sum >= target要用while循环。

代码如下:

class Solution {
public:
    int minSubArrayLen(int target, vector<int>& nums) {
        int size = nums.size();
        int slowIndex = 0;
        int num = size;
        int sum = 0, ans = size + 1;
        for (int fastIndex = 0; fastIndex < size; fastIndex++) {
            sum += nums[fastIndex];
            while (sum >= target) {
                if ((fastIndex - slowIndex + 1) < ans) {
                    ans = fastIndex - slowIndex + 1;
                }
                sum -= nums[slowIndex++];
            }
        }
        if (ans == size + 1) {
            return 0;
        } else {
            return ans;
        }
    }
};
  • 时间复杂度:O(n)
  • 空间复杂度:O(1)
                if ((fastIndex - slowIndex + 1) < ans) {
                    ans = fastIndex - slowIndex + 1;
                }
                sum -= nums[slowIndex++];
        if (ans == size + 1) {
            return 0;
        } else {
            return ans;
        }

上面这两段代码可以用三目运算符简化,当时没考虑这么多,以后需要注意。

相关题目推荐

三、59.螺旋矩阵II

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

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

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

示例 2:

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

提示:

  • 1 <= n <= 20

这题没有简单方法,只能模拟。

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

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

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

59. 螺旋矩阵 II

这里每一种颜色,代表一条边,我们遍历的长度,可以看出每一个拐角处的处理规则,拐角处让给新的一条边来继续画。

这也是坚持了每条边左闭右开的原则,坚持循环不变量原则。

代码如下:

class Solution {
public:
    vector<vector<int>> generateMatrix(int n) {
        vector<vector<int>> res(n, vector<int>(n, 0)); // 使用vector定义一个二维数组
        int start_x = 0, start_y = 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 = start_x;
            j = start_y;
            // 下面开始的四个for就是模拟转了一圈
            // 模拟填充上行从左到右(左闭右开)
            for (j = start_y; j < n - offset; j++) {
                res[start_x][j] = count++;
            }
            // 模拟填充右列从上到下(左闭右开)
            for (i = start_x; i < n - offset; i++) {
                res[i][j] = count++;
            }
            // 模拟填充下行从右到左(左闭右开)
            for (; j > start_y; j--) {
                res[i][j] = count++;
            }
            // 模拟填充左列从下到上(左闭右开)
            for (; i > start_x; i--) {
                res[i][j] = count++;
            }
            // 第二圈开始的时候,起始位置要各自加1, 例如:第一圈起始位置是(0, 0),第二圈起始位置是(1, 1)
            start_x++;
            start_y++;
            // offset 控制每一圈里每一条边遍历的长度
            offset++;
        }
        // 如果n为奇数的话,需要单独给矩阵最中间的位置赋值
        if (n % 2) {
            res[mid][mid] = count;
        }
        return res;
    }
};

  相关题目推荐

四、总结

1. 看到前两道题目的时候,能想到用双指针法去做,但掌握的还不够熟练,需要多刷题强化训练。(滑动窗口)

2. 循环不变量原则的重要性,在明确这一原则后,题目的思路就很清晰了。

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值