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

1. 学习的文章和视频链接

1.1 977有序数组的平方-代码随想录
1.2 977有序数组的平方-卡哥B站视频讲解
1.3 209长度最小的子数组-代码随想录
1.4 209长度最小的子数组-卡哥B站视频讲解
1.5 59螺旋矩阵 II-代码随想录
1.6 59螺旋矩阵 II-卡哥B站视频讲解


2. 看到题目的第一想法

  • 977:平方->排序->输出。直接想到了暴力解法
  • 209:-> 数组中元素是否比target值小?
    -> 小于的检测下一个元素 - 循环内
    ->下一个元素变成当前元素
    -> 要有个记总数的,当前检测的加上后是否大于target
    -> 等于的输出
    ->大于的直接跳 (考虑方向错误先从单个元素去考虑了,没有考虑到滑动窗口,因此没有考虑连续条件)
  • 59:无论是顺序排列,还是螺旋排列,matrix的第一行是1到n,不会改变。螺旋的顺序是上边->右边->下边->左边。
    上边:从左到右;右边:从上到下;下边:从右到左;左边:从下到上。每1次遇到拐角,填充的数字就要-1;2次就是-2,依次类推。。好难。

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

  • 977:双指针在数组中大有用处。一共要设置3个index变量,旧数组左右两头index和新数组最后一个元素的index。左右index用来在旧数组中比乘方大小,新数组最后一个元素index用来从后往前(从大到小)来放置确定的元素乘方。在一个数组中从后往前确定元素可以用nums[k- -]即将nums[k]和k- -连起来使用。
  • 209:" sum -= nums[i++]; " 这一行就让人惊呼神奇!总数减去当前元素,然后将窗口起点位置右移,改变起点位置,缩小窗口。且元素进来while循环肯定是超过了target值的,可谓一进,当前元素要被抛弃来缩小窗口,可谓一出,计算的出while循环的时间复杂度只是O(N)。这一招 “头追尾定”(while) ,之后再 “头定尾进”(for) ,动态演绎出你追我赶的画面,太高明了。
  • 59:逻辑太清楚了。首先,我们得声明一个二维数组,这也是最后要返回的数组。有了数组,每一条边的起点终点也需要。然后是旋转次数即循环次数,对于奇数边的中点,每个位置上的填充数,以及每次循环一轮(4条边)后每边要收缩的数量。然后从外向内按照上左下右边的顺序每格依次填充数字,记住,秉持左闭右开的原则,不要变化。因为一旦变了可能会造成重复或者丢失。接下来,更新下一圈的起始点位置和收缩数目(都+1即可)。最后,填充边的循环结束后,判断整数n是否为奇数,如果是,则填充。只填充奇数时的中点是因为偶数时,无中心点。这样,填充完毕。

4. 实现过程中遇到哪些困难

  • 977:
  1. sort方法一开始写错了
  2. 时间复杂度认为两种解法都是线性复杂度,这是错误的,因为我不清楚sort方法是什么排序,并且在计算是,没有算进暴力解法中
  3. 看了解法后,没理解k- -能放进result[k]的原因
  • 207:
  1. 连续条件不知道怎么写,看下面代码
  2. 没有考虑滑动窗口->双指针的不熟悉
class Solution {
public:
    int minSubArrayLen(int target, vector<int>& nums) {
        int child_len = 0, total = 0; // 声明子数组长度 和 元素相加总值
        for(int i=0; i<nums.size(); i++){  // 遍历
            // 数组中第i个元素小于target
            if(nums[i]<target){
                // 如何判断是连续的?????
                //连续的话
                total += nums[i];    // 计算总值
            }
            // 数组中第i个元素等于target
            else if(nums[i]==target){
                // 直接返回1长度,
                // 不能是child_len++,
                // 考虑数组内有几个与target相等的元素之情况
                return 1;
            }
            // 数组中第i个元素大于target
            else continue;  // 跳过第i个,检查下一个
        }
        // 全部元素加起来小于target,或者全部元素都大于target, 返回0
        return child_len;
    }
};
  • 59:
  1. 忘记如何声明二维数值,其实就是嵌套一下一维的声明。
  2. offset定义一开始不太清楚,实际写的时候理解了很久
  3. 4个边for loop时,j

5. 今日收获

  • 对比了不同解法之间的复杂度
  • 知道了数组sort()排序使用的是快速排序 -> 因此在sort步骤,时间复杂度是nlog n
  • 双指针对于数组删除,查找中都是好的解决方法
  • 复习了C++中数组的声明
  • 双指针的边界问题有了清楚的认识
  • 滑动窗口中头尾位置和窗口的真正含义
  • 循环不变量 - 在螺旋矩阵中是对每条边的处理规则 - 边界规则一定要保持每边一致
  • 学会了在Markdown文件中写代码,之前是截图保存然后插入图片,很麻烦,现在简便了许多

Code1: 977暴力解法
时间复杂度: O(n+nlog n)

class Solution {
public:
    vector<int> sortedSquares(vector<int>& nums) {
        // 1. 数组中每个元素乘方
        for(int i=0; i<nums.size(); i++){
            nums[i] *= nums[i];
        }
        // 2. 排序 - 哪种排序?
        sort(nums.begin(), nums.end());
        // 3. 返回数组
        return nums;
    }
};

Code2: 977双指针解法但未想通k- -为何可以放在result[k]中
时间复杂度:** O(n)

class Solution {
public:
    vector<int> sortedSquares(vector<int>& nums) {
        int k = nums.size() - 1;  // k是最终数组的最后一个index
        vector<int> result(nums.size(), 0); // 声明最终数组
        // i是左边界index,j是右边界index,左边不能超过右边
        for(int i=0, j=nums.size()-1; i<=j;){
            // 最左边元素的乘方大于最右边元素的乘方
            if(nums[i]*nums[i]>nums[j]*nums[j]){
                // 最终数组的最后一个元素是大的一方(左)
                result[k] = nums[i] * nums[i];
                i++;  // 左边界index右移(往后)一位
                k--;  // 最终数组的最后一个元素已有,index往前一位
            }
            else {  // 最左边元素的乘方小于等于最右边元素的乘方
                // 最终数组的最后一个元素是大的一方(右)
                result[k] = nums[j] * nums[j];
                j--;  // 右边界index左移(往前)一位
                k--;  // 最终数组的最后一个元素已有,index往前一位
            }
        }
        return result;
    }
};

Code3: 977双指针解法
时间复杂度:O(n)

class Solution {
public:
    vector<int> sortedSquares(vector<int>& nums) {
        int k = nums.size() - 1;  // k是最终数组的最后一个index
        vector<int> result(nums.size(), 0); // 声明最终数组
        // i是左边界index,j是右边界index,左边不能超过右边
        for(int i=0, j=nums.size()-1; i<=j;){
            // 最左边元素的乘方大于最右边元素的乘方
            if(nums[i]*nums[i]>nums[j]*nums[j]){
                // 最终数组的最后一个元素是大的一方(左)
                // 先获得最后一位元素的值,然后做-1,所以用k--
                result[k--] = nums[i] * nums[i];
                i++;  // 左边界index右移(往后)一位
            }
            else {  // 最左边元素的乘方小于等于最右边元素的乘方
                // 最终数组的最后一个元素是大的一方(右)
                result[k--] = nums[j] * nums[j];
                j--;  // 右边界index左移(往前)一位
            }
        }
        return result;
    }
};

Code4: 209暴力解法-2个loop
时间复杂度: O( N 2 N^2 N2)

class Solution {
public:
    int minSubArrayLen(int target, vector<int>& nums) {
        int result = INT32_MAX;   // 最终连续子数组的长度
        int total = 0;            // 连续子数组元素相加总和
        int subLen = 0;           // 当前连续子数组的长度
        for(int i=0; i<nums.size(); i++){  // 子数组的头index
            total = 0;   // 新一轮连续子数组,得重置总和
            for(int j=i; j<nums.size(); j++){  // 子数组的尾index
                total += nums[j];          // 子数组中元素加进总和
                if(total>=target){         // 如果总和大于等于目标,
                    subLen = j - i + 1;    // 计算当前子数组长度
                    // 比较当前子数组长度与之前存进result符合条件的子数组的长度
                    // 小的存进result
                    result = subLen < result ? subLen : result;
                    break;   // 已经存进了最短长度,此子序列结束,跳出j-loop
                }
            }
        }
        // 如果result未发生变化,说明没有满足条件的数组,结果为0;有变化,则输出
        return result == INT32_MAX ? 0 : result;
    }
};

Code5: 209滑动窗口-双指针
时间复杂度: O(N)

class Solution {
public:
    int minSubArrayLen(int target, vector<int>& nums) {
        int result = INT32_MAX;  // 最短连续子数组的长度
        int i = 0;       // 滑动窗口起点index
        int total = 0;   // 滑动窗口内元素总和
        int subLen = 0;  // 当前滑动窗口的长度
        for(int j=0; j<nums.size(); j++){  // 滑动窗口终点index
            total += nums[j];      // 更新总数,将当前元素加进来
            while(total>=target){     // 当子数组元素总和大于等于target
                subLen = j - i + 1;   // 计算当前滑动窗口的长度
                // 之前的长度和当前的长度,哪个小取哪个
                // 注意,这里滑动窗口的终点index不会改变
                result = result < subLen ? result : subLen;
                // 精髓。总和减去当前元素后,窗口起点index右移,窗口长度自动缩小
                total -= nums[i++];
            }
        }
        // result未赋值,则没有符合要求的连续子数组,返回长度0;result赋值了,则返回
        return result == INT32_MAX ? 0 : result;
    }
};

Code6: 59循环不变量原则-左闭右开
时间复杂度: O( N 2 N^2 N2)

class Solution {
public:
    vector<vector<int>> generateMatrix(int n) {
        vector<vector<int>> mat(n, vector<int>(n, 0)); // 声明二维数组
        int startx = 0, starty = 0;   // 声明起始点位置
        int loop = n / 2;    // 一共经历几次循环,相当于有多少圈
        int mid = n / 2;     // 奇数n时,矩阵中点没填充到,需要声明中点位置,最后填充
        int count = 1;       // 填充的数字
        int offset = 1;      // 每边收缩的数目,要在4边loop后更新
        int i, j;
        while(loop--){    // 每一轮4边填充后,循环数(圈数)减少,直至0不再填充(退出)
            // 更新每次i,j
            //i = startx;
            //j = starty;

            // 一轮填充4条边 -> 4个for loop ,左闭右开原则

            // 上边,j变(从左往右递增)i不变
            for(j=starty; j<n-offset; j++)
                mat[startx][j] = count++;

            // 右边,i变(从上往下递增)j不变
            for(i=startx; i<n-offset; i++)
                mat[i][j] = count++;
            
            // 下边,j变(从右往左递减)i不变
            for(; j>starty; j--)
                mat[i][j] = count++;

            // 左边,i变(从下往上递减)j不变
            for(; i>startx; i--)
                mat[i][j] = count++;

            // 更新起始点位置,准备下一此循环
            startx++;
            starty++;

            // 更新收缩数目,增加1位要收缩的
            offset++;
        }
        // 循环结束后,判断n是否为奇数,是则填充中间位置
        if(n%2)
            mat[mid][mid] = count;

        return mat;
    }
};

你可能感兴趣的:(算法,矩阵,python)