Day2 数组:977.有序数组的平方 209.长度最小的子数组 59.螺旋矩阵Ⅱ

780 阅读6分钟

977.有序数组的平方

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

暴力解法:

时间复杂度:O(nlogn) (取决于快排)

将所有的元素都进行平方,然后再排序(可以用快排),这样就可以得到一个所有元素经过平方之后的有序数组。

AC代码: (核心代码模式)

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

具体的时间复杂度:

for循环:O(n) ✖️ 快排:O(nlogn)

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

⚠️排序使用了库函数sort

双指针解法:

时间复杂度:O(n)

数组其实是有序的,暴力解法并没有利用到这一特点。

由于负数的存在,负数平方之后可能就会成为最大数,因此数组平方后的最大值就应该在数组的两端,不是最左边就是最右边,不可能是中间。

此时可以考虑双指针法,i指向起始位置,j指向终止位置。

定义一个新的数组result来装进行平方之后的数组元素。

定义一个索引下标,k = nums.size() - 1; 下标的作用:新的数组要从大到小来更新。

02.01.png

因为每次取的时候都是取了一个最大值(两头向中间取,先取最大,再取次大)。

这样的话,更新result数组也要下标由大到小来更新,最后得到的这个result数组才是一个按非递减顺序排序(元素从小到大)的集合。

通过for循环定义2个下标 ij

循环终止的条件:只要 i <= j 就继续执行该循环

Q:若只写i < j 会怎样?

A:那我们就把i和j相等的时候指向的这个元素给落下了。

⚠️注意:for循环的条件里面不能写i++,j--

eg: for (int i = 0, j = 0; j = nums.size(); i++, j--) ❌

因为i++ 和 j--都是有条件的,取决于两头的元素谁比较大,取了那个大的元素,对应的下标再做 + + or - -

Carl 板书: 02.02.png

动画:

02.03.gif

AC代码: (核心代码模式)

 class Solution {
 public:
     vector<int> sortedSquares(vector<int>& nums) {
         vector<int> result(nums.size(), 0);
         int k = nums.size() - 1;
 ​
         for (int i = 0, j = nums.size() - 1; i <= j; ) {
             if (pow(nums[i], 2) > pow(nums[j], 2)) {
                 result[k--] = pow(nums[i], 2);
                 i++;
             }
             else {
                 result[k--] = pow(nums[j], 2);
                 j--;
             }
         }
         return result;
     }
 };

总结:代码很简单,但是很能体现出对这个双指针操作的细节。

209.长度最小的子数组

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

暴力解法:

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

最直接的想法:2层for循环进行遍历:第一层for循环控制区间的起始位置,第二层for循环控制区间的终止位置。

把数组的所有区间情况都遍历出来,然后找到 >= target 的最小区间长度。
在这个区间里面不断搜索,把所有区间情况都枚举出来,然后判断 >= target 的最小长度是多少,最后返回这个最小长度。

AC代码: (核心代码模式)

 class Solution {
 public:
     int minSubArrayLen(int target, vector<int>& nums) {
         
         int res = 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 >= target) {
                     subLength = j - i + 1;
                     res = res < subLength ? res : subLength;
                     break;
                 }
             }
         }  
 ​
         return res == INT32_MAX ? 0 : res;
     }
 };

力扣更新了数据,暴力解法会超时。

这题说了是连续最小数组,暴力法穷举了所有组合,所以多了很多不必要的计算。

滑动窗口:

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

用一个for循环来做2个for循环所做的事情。

思考:这个索引下标 j 表示的究竟是滑动窗口里面的终止位置还是起始位置

可以先假设 j 表示的是起始位置,

j 表示的是起始位置,这个for循环一次一次把索引下标向后移动,这个终止位置要把后面所有的元素遍历一遍,才能返回所有以这个起始 i 为起始位置的集合,然后我们再去判断这个集合里面的所有元素:

如果 >= target,去搜集所有 >= target这些集合里面的所有的长度,再取一个最小的。 如果终止位置是一个一个向后移动的话,那么和这个暴力的解法又存在什么区别呢?

此时你会发现:如果这一个for循环里面的这个j表示的是起始位置的话,那终止位置依然要把所有位置都遍历一遍,那么它的思路就和暴力是一样的。

因此这一个for循环里面的j一定指向的是终止位置,而起始位置需要我们用动态移动的策略来移动起始位置,这样才能用一个for循环的思路来解决这道题。

🪟滑动窗口解题的关键:窗口的起始位置如何移动?

终止位置随着for循环一个一个向后移动,那什么时候移动起始位置?

如果我们的终止位置指向这里,这个集合里面的元素之和如果 >= target 的话,说明这是符合条件的一个集合,我们收集它的长度之后,起始位置就可以向后移动了。

从而缩小当前这个集合,看下一个集合是否符合我们的条件。

就是当我们发现集合里面所有的元素和 >= target,我们再去移动起始位置,这样就实现了动态调整起始位置,来去收集不同长度区间里面的和。

sum收集滑动窗口里面的和

我们需要有一个集合,通过这个集合里面的元素之和来和target做比较,

res是最终取的最小的长度。

⚠️应该写if(sum >= target) 还是 while(sum >= target)

如果你写的是if,看看这个样例: 输入:target = 100, nums = [1,1,1,1,1,100]

动画:

02.04.gif

AC代码: (核心代码模式)

 class Solution {
 public:
     int minSubArrayLen(int target, vector<int>& nums) {
         int res = INT32_MAX;  //最终取的最小的长度
         int sum = 0;  //滑动窗口内数值之和
         int i = 0; //滑动窗口的起始位置i
         int subLength = 0;  //滑动窗口的长度
 ​
         for (int j = 0; j < nums.size(); j++) {
             sum += nums[j];
 ​
             while (sum >= target) {
                 subLength = (j - i + 1);
                 res = res < subLength ? res : subLength;
                 sum -= nums[i++];
             }
         }
 ​
         return res == INT32_MAX ? 0 : res;
 ​
     }
 };

一些录友会疑惑为什么时间复杂度是O(n)

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

59.螺旋矩阵Ⅱ

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

这是一道模拟题,没有涉及到什么算法,就是一个模拟转圈的过程。

不过转圈这个过程需要处理的边界条件很多。

左闭右开,处理第一个节点,最后一个节点留给下一条边遍历的时候再做处理,这样4条边的遍历规则就统一了。

还在写……