随想录Day2|977. 有序数组的平方、209. 长度最小的子数组、59. 螺旋矩阵Ⅱ

发布时间 2023-09-22 01:10:48作者: Alouette29

随想录Day2|977. 有序数组的平方、209. 长度最小的子数组、59. 螺旋矩阵Ⅱ

 

977. 有序数组的平方

LeetCode题目

文章讲解

视频讲解

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

  • 1 <= nums.length <= 104
  • -104 <= nums[i] <= 104

 

第一想法

不能原地操作了,因为负数的平方可能会更大,而排序算法可能会超时,所以需要在过程中就按照顺序填写。

哦,然后看到了说进阶是设计\(O(n)\)的算法,排序好一点一般也是\(O(n\log n)\),而我已经看到了卡尔的建议,所以肯定是双指针这个方向,看我能不能自己想出来……

如果我可以定位绝对值最小的元素作为开始,然后双指针往两边移动?首先扫一遍数组,从右往左,如果最右边是负数则直接以它为起点做平方,如果最右边是正数则……

woc等等啊,那为什么不从两边往中间啊,这样就不用找绝对值最小的元素位置了,只要最后两个指针重合了就好!因为两边一定是绝对值最大的。填入一个更大的平方值,移动那边的指针。

还需要注意一下细节,数组长度为偶数的时候不需要特别处理,长度为奇数的时候会遇到两个指针重叠的情况,应该在while (pleft < pright)外面特判一下这种情况,把最后一个元素填入。

噫,好,我过了!

用时25min~

vector<int> sortedSquares(vector<int> &nums)
{
    int len = nums.size();
    int pleft = 0;
    int pright = len - 1;

    vector<int> result_nums(len, 0);
    int pres = len;

    while (pleft < pright)
    {
        int numl = nums[pleft];
        numl *= numl;
        int numr = nums[pright];
        numr *= numr;
        // 填入更大的那个,并向中间移动对应的指针
        if (numl > numr)
        {
            result_nums[--pres] = numl;
            pleft++;
        }
        else
        {
            result_nums[--pres] = numr;
            pright--;
        }
    }
    if (pleft == pright)
        result_nums[--pres] = nums[pleft] * nums[pleft];

    return result_nums;
}

 

209. 长度最小的子数组

LeetCode题目

文章讲解

视频讲解

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

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

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

 

第一想法和纠正

没错,又是看了卡尔的题目建议,大概知道方向,滑动窗口法感性地体会一下,如果一段数中有很多大数字,但是夹杂了很多负数,就可能会很长;不怎么含有负数,但是都是小的正数,也可能会很长。(哪里有负数了看看数据范围!你到底审不审题的啊啊啊 发出尖锐爆鸣声*)

盲猜需要两个指针,指向这个滑动窗口的左右两边。接下来就要确定这个窗口的规则了(啊这时候我的思维飘到了动态规划),左指针起始在0位置,右指针往右走并且计算当前窗口内的和。应该会有一个全局的min_len记录能满足条件的子数组的最短长度,每当当前的子数组和大于target,就右移左指针,直到恰好不满足条件,记录那个临界长度。然后右指针继续往右走……

啊不过,如果左指针再右移一点又符合了,这种可能性有吗?那也就是说,子数组和先降后升,左边先滑走了一个正数,然后又滑走了一个负数,而且负数的绝对值更大。对,所以要是说起负数在的情况,滑动窗口这种想法就不适用了吧!

不确定上面想法的严谨性,不过还是想想怎么判断不存在符合条件的子数组吧?那么就是min_len的起始值设置为n+1,这样如果结束循环之后还是这个值,就返回0。循环的条件应该是右指针到达最右边。

好,不管怎么样,先试试吧。

好的果然过不了case!

 

看完随想录题解的想法

右移左指针时,需要在for循环内部引入一个while循环(我写成if了难怪错了)!不断比较target和当前子数组和的大小关系,(更新cur_len),更新min_len,然后把左指针右移一位。

还有一个小错误是我之前在右移左指针时更新的是min_len,但是正确的流程应该是记录cur_len,然后每次cur_len可能变小的时候(所以只在左指针右移的时候判断,因为右指针右移肯定cur_len变大)判断cur_len如果比min_len小,再去更新min_len

最后整理一下直观感受,这个窗口始终是“紧”的,右移右指针是扩展窗口,右移左指针是缩紧窗口。只有在不满足target条件的时候才扩展,一旦满足了马上试试能不能缩紧(while保证了缩到最紧),并且一直更新全部历史记录中的最小窗口长度。这样就可以保证我们找到全局最小子数组长度。

 

用时40min~

int minSubArrayLen(int target, vector<int> &nums)
{
    int n = nums.size();
    int min_len = n + 1;
    int cur_len = 0;
    int pleft = 0;
    int pright = 0;
    int cur_sum = 0;

    for (; pright < n; pright++)
    {
        cur_sum += nums[pright];
        while (cur_sum >= target)
        {
            cur_len = pright - pleft + 1;
            min_len = min_len < cur_len ? min_len : cur_len;
            // pleft begin to move right
            cur_sum -= nums[pleft++];
        }
    }
    if (min_len == n + 1)
        min_len = 0;
    return min_len;
}

 

59. 螺旋矩阵Ⅱ

LeetCode题目

文章讲解

视频讲解

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

 

第一想法

按照 → ↓ ← ↑ 的顺序循环,主要是计算清楚每次走多少步。把每一行的管辖范围都控制为左开右闭,也就是说,对一个矩形框,如果是\(5\times 5\)的,那就右4步,下4步,左4步,上4步,而不是一行走5步,那样会比较难保持一个规律去循环。

矩阵是\(n\times n\),所以最外圈四个方向都是\(n-1\)步,次外圈是\(n-3\)步……很自然地联想到肯定要考虑一下奇偶。如果\(n\)是奇数,比如想象\(5\times 5\)的矩阵……就是最中间肯定有点区别,先直接开始写吧!

 

思路详解

真不错,我和卡尔的思路还不一样,不过我觉得自己这个真的挺简洁的!

cur_len代表当前四个方向走的边长,如果\(n\)是奇数,到中心\(3\times 3\)矩阵的时候会有cur_len = 2,下一轮再减2就会为0,实际上就是最中心那个数。它的行列号都相等,(n - 1) / 2,在最后判断一下如果cur_len == 0,手动填入\(n^2\)即可。如果\(n\)是偶数,到中心\(2\times 2\)矩阵的时候会有cur_len = 1,可以正常填写,下一轮再减2就会为-1,则while循环的条件应该是cur_len > 0,这样奇偶都能正常停止循环。

每次向右走之前,由于新的一轮是走靠里一点的小一圈的框,起始位置也要调整,所以需要i++; j++;。应该在进入循环之前把初始值都设为-1,以便从(0, 0)开始,第二轮就是从(1, 1)开始,以此类推。

 

用时40min~

vector<vector<int>> generateMatrix(int n)
{
    vector<vector<int>> nums(n, vector<int>(n, 0));
    int n_square = n * n;
    int cur_len = n - 1;
    int cur_num = 1;
    int i = -1, j = -1;
    while (cur_len > 0)
    {
        int steps;
        i++;
        j++;
        for (steps = 0; steps < cur_len; steps++, j++)
            nums[i][j] = cur_num++;
        for (steps = 0; steps < cur_len; steps++, i++)
            nums[i][j] = cur_num++;
        for (steps = 0; steps < cur_len; steps++, j--)
            nums[i][j] = cur_num++;
        for (steps = 0; steps < cur_len; steps++, i--)
            nums[i][j] = cur_num++;
        cur_len -= 2;
    }
    if (cur_len == 0)
    {
        int mid = (n - 1) / 2;
        nums[mid][mid] = n_square;
    }
    return nums;
}

 

碎碎念

今天的状态真的不错,满课一天所以拖到晚上才写,但是还是很在状态的!计时来自插件自带,是我打开题目到AC的总用时,做题中途我也在写这个文档的内容,是我整理思路的一种方式。

成就感真的很管饱!明天也要继续加油!