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

今天巩固了双指针的思想,特别是理解了滑动窗口的方法,加深了对循环不变量的理解。

一、977.有序数组的平方

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

该题需要考虑到,若一个有序数组,如[-4,-1,0,3,10],平方后的[16,1,0,9,100]的最小值一定在区间内部。在求解时,定义一个指针i指向数组开始的位置,一个指针j指向数组的末尾,两个指针从两端向中间移动。比较两个指针所指数值的平方大小后,用一个数组result接收新数组的值,用指针k来表示其下标。

由于i、j是从两边向中间移动,result数组必然是先获取到较大的值,后获取到较小的值,为了升序排列,k从result末尾向开头移动。

代码如下:

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相等,这个数就被忽略掉了
            if (nums[i] * nums[i] > nums[j] * nums[j]) {//如果i指向的数的平方大于j指向的数平方,i向后移,j不动
                result[k] = nums[i] * nums[i];
                k--;//这步也可以省略,写进result[k--] = nums[i] * nums[i]
                i++;
            }
            else {//如果j指向的数的平方大于i指向的数平方,j向后移,i不动
                result[k] = nums[j] * nums[j];
                k--;//也步可以省略,写进result[k--] = nums[j] * nums[j]
                j--;
            }
        }
        return result;
    }
};

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

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

该题涉及到数组操作中的一个重要方法:滑动窗口。滑动窗口通过不断调节子数组的起始位置和终止位置, 从而得到想要的结果。

本题中,定义一个指针j作为窗口的终点,指向数组的起始位置;指针i作为窗口的起点,也指向数组的起始位置。在指针j向右不断移动的过程中,i~j窗口中的数字和不断累加,直到和大于等于target,此时记录一下该窗口的长度;由于该窗口不一定是长度最小的(累加的和可能超出target很多),就需要移动窗口的起始位置i,在指针i不断向右移动的过程中,i~j窗口中的数字和不断减小,若大于target则继续记录下此时的窗口长度,并更新最小长度,直到和小于target。在窗口慢慢向右移动的过程中,最终得到最小的窗口长度。

更详细的分析过程可以参考卡哥给出的解答:
https://programmercarl.com/0209.长度最小的子数组.html

代码如下:

class Solution {
public:
    int minSubArrayLen(int target, vector<int>& nums) {
        int i = 0;//窗口的起始位置
        int result = INT32_MAX;//最小子数列的长度,定义result一个32位下的int类型的最大值2147483647
        int sum = 0;//窗口内数字的和
        int subL = 0;//子数列的长度
        for (int j = 0; j < nums.size(); j++){//子数组的终止位置向后移
            sum += nums[j];
            while (sum >= target){//当窗口内的和大于target
                subL = j - i + 1;//取当前子数列的长度
                result = result < subL ? result : subL;//更新最小子数列的长度
                sum -= nums[i];//也可以写成sum -= nums[i++];后面一句i++省略
                i++;//子数组的起始位置向后移一位
            }
        }
        result = result == INT32_MAX ? 0 : result;//如果result没有被赋值(如[1,1,1,1,1],target=10)则返回0
        return result;
    }
};

三、59. 螺旋矩阵

题目链接:https://leetcode.cn/problems/spiral-matrix-ii/

该题不涉及算法,但在面试中出现频率较高,主要考察对代码的掌握能力,过两天需要再次复习。

在打印矩阵的四条边时,每条边都要坚持一直的左闭右开的原则,这样循环才不会乱。从最左侧的元素到最右侧前一个元素填充上行,从最上方的元素到最下方的前一个元素填充右行,从最右侧的元素到最左侧的前一个元素填充下行,从最下方的元素到最上方的前一个元素填充左列。

即:在每个拐角处画一条新的边,这里的左闭右开就是“不变量”。

代码如下:

class Solution {
public:
    vector<vector<int>> generateMatrix(int n) {
        vector<vector<int>> res(n, vector<int> (n, 0));
        int loop = n / 2; //螺旋要跑的圈数
        int count = 1; //矩阵空白处要填的数字
        int offset = 1;//控制每一条边遍历的长度,每循环一圈边的右边界收缩一位
        int startx = 0, starty = 0;//定义每循环一个圈的起始位置
        int mid = n / 2;//矩阵中心的位置
        while (loop--){
            int i = startx;
            int j = starty;
            for (j = starty; j < n - offset; j++ )//填充上行从左到右,左闭右开
                res[i][j] = count++;
            for (i = startx; i < n - offset; i++)//填充右列从上倒下,左闭右开
                res[i][j] = count++;
            for ( ; j > startx; j--)//填充下行从右到左,左闭右开
            						//此时i和j都等于n-offset
                res[i][j] = count++;
            for ( ; i > starty; i--)//填充左列从下到上,左闭右开
                res[i][j] = count++;
            
            //loop--;//跑下一圈
            startx++;//下一圈的起始位置向右移动一位
            starty++;//下一圈的起始位置向下移动一位
            		 //如第一圈起始[0][0],第二圈起始[1][1]
            offset++;//下一圈的右边界收缩一位
		}
        if (n % 2){//如果n为奇数,单独给矩阵最中间的位置赋值,注意这个if是在while之外的
            res[mid][mid] = count;
        }
        return res;
    }
};

要注意以下几个问题:
1)为什么要设置startx和starty,而不是从0开始?
答:以n=4为例,最外圈是从res[0][0]开始,向内一圈后是从res[1][1]开始,起始位置是会改变的;
2)为什么要设置offset来控制右边界的缩进,而不直接是1?
答:和问题1)类似,最外圈的上行为1,2,3,4,下标为0,1,2,3,画上行时只画前三个,因此循环中j<3-1=2,此时offset为1;但是在向内一圈后,仍以上行为例,内圈上行为13,14,下标为1,2,stary=1,循环中j<3-2=1,即只填充res[2][2],此时offset=2。每循环一圈右边界会收缩一位。
3)为什么if在while之外?
答:如果n为奇数,比如3,第一次while循环中loop=1,第二次loop=0,不进入while循环,也就是只画了最外面的一圈,中间的那个值是空的,需要单独赋值。也可以想象n=1时,直接不进入while循环,如果if在while循环内的话,唯一的值就没了。这个错误在我第一次提交的时候发生了,要多注意以下。

总结

今天对双指针、滑动窗口、螺旋矩阵进行了学习,因为写代码的过程中总是出现很弱智的错误,所以把当时一下子没转过来的地方举了例子,以后再绕进去的时候可以看看。

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