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

977.有序数组的平方

1、题目

image-20231130210237398

2、题解

思路一:暴力排序

讲每个数平方过后重新排序一下,

 // 时间复杂度 O(nlogn)
 ​
 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;
     }
 };

思路二:双指针法

定义两个指针i和j,指针 i 指向起始位置,j 指向终止位置。

定义一个用来存放答案的新数组 ans。

定义一个变量k用来指向ans的末尾位置

因为这个数组最小的在中间,最大的在两端,所以从两端的数开始平方并进行比较,把大的数存入答案数组中,然后进行下一轮比较,直到所有数都比较完。

当 abs(nums[end]) > abs(nums[front]) 则 result[i] = nums[end] * nums[end];

当 abs(nums[end]) <= abs(nums[front]) 则 result[i] = nums[front] * nums[front];

977.有序数组的平方

 
// 时间复杂度 O(n)
 ​
 class Solution
 {
     public:
     vector<int> sortedSquares(vector<int>& nums)
     {
         int end = nums.size() - 1;
         int front = 0;
         vector<int> result(nums.size());
         for (int i = result.size() - 1; i >= 0; i--)
         {
             if (abs(nums[end]) > abs(nums[front]))
             {
                 result[i] = nums[end] * nums[end];
                 end--;
             }
             else
             {
                 result[i] = nums[front] * nums[front];
                 front++;
             }
         }
         return result;
     }
 };
3、总结

在这题中,使用的是相向双指针,又称对撞指针。双指针法比暴力排序降低了不少时间复杂度。在前面的移除元素题中用到了同向双指针,又称快慢指针。应该多把这两题联合起来看看,理解双指针的思想。

209.长度最小的子数组

1、题目

image-20231130213308746

2、题解

思路一:暴力解法

使用两个for循环来寻找符合条件的子序列

// 时间复杂度 O(n^2)
 ​
 class Solution
 {
     public:
     int minSubArrayLen(int s, vector<int>& nums)
     {
         int result = 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 >= s)
                 {
                     // 一旦发现子序列和超过了s,更新result
                     subLength = j - i + 1; // 取子序列的长度
                     result = result < subLength ? result : subLength;
                     break; // 因为我们是找符合条件最短的子序列,所以一旦符合条件就break
                 }
             }
         }
 ​
         // 如果result没有被赋值的话,就返回0,说明没有符合条件的子序列
         return result == INT32_MAX ? 0 : result;
     }
 };

思路二:滑动窗口

滑动窗口 是一种基于双指针的一种思想,两个指针指向的元素之间形成一个窗口。可以用来解决一些查找满足一定条件的连续区间的性质(长度等)的问题。由于区间连续,因此当区间发生变化时,可以通过旧有的计算结果对搜索空间进行剪枝,这样便减少了重复计算,降低了时间复杂度。往往类似于“请找到满足xx的最x的区间(子串、子数组)的xx”这类问题都可以使用该方法进行解决。

在本题中实现滑动窗口,主要确定如下三点:

  • 窗口内是什么?

  • 如何移动窗口的起始位置?

  • 如何移动窗口的结束位置?

窗口内是满足题目答案的子序列

窗口的起始位置如何移动:当窗口的值大于s了,起始指针就要向前移动了(缩小窗口)

窗口的结束位置如何移动:当窗口的值小于s了,末尾指针就要向后移动了(扩大窗口)

20210312160441942

// 时间复杂度:O(n)
 ​
 class Solution
 {
     public:
     int minSubArrayLen(int target, vector<int>& nums)
     {
         int ans = INT32_MAX;// INT_MAX 和 INT_MIN 是 C++ 的两个宏,代表了整型变量能够存储的最大正整数和最小负整数,分别为 2147483647 和 -2147483648
         int sum = 0; // 滑动窗口数值之和
         int front = 0;
         int end = 0;
         while (end <= nums.size() - 1)
         {
             sum += nums[end];
             while (sum >= target)
             {
                 ans = min(ans, (end - front + 1)); // 更新最小子序列
                 sum -= nums[front++];// 缩小窗口的时候不要忘记把新窗口外的数删除              
             }
             end++;
         }
         return ans == INT32_MAX ? 0 : ans;
     }
 };
3、总结

可以直观的看到,滑动窗口的精妙之处在于根据当前子序列和大小的情况,不断调节子序列的起始位置。从而将O(n^2)暴力解法降为O(n)。滑动窗口法可以看做是一种双指针方法的特例,两个指针都起始于原点,并一前一后向终点前进。

什么情况可以用滑动窗口来解决实际问题呢?

  1. 一般给出的数据结构是数组或者字符串

  2. 求取某个子串或者子序列最长最短等最值问题或者求某个目标值时

  3. 该问题本身可以通过暴力求解

59.螺旋矩阵II

1、题目

image-20231130220721759

2、写法

这题不涉及算法,是一道模拟过程的题目。

做题时需要注意坚持循环不变量原则,也就就是对边界的处理时,对每一条边都要坚持左闭右开或者左闭右闭的原则,不能对每一条边的处理原则都不相同,在下面的解中我坚持的是左闭右开的原则

模拟顺时针画矩阵的过程

  • 填充上行从左到右

  • 填充右列从上到下

  • 填充下行从右到左

  • 填充左列从下到上

螺旋矩阵

// 时间复杂度 O(n^2)
 ​
 class Solution
 {
     public:
     vector<vector<int>> generateMatrix(int n)
     {
         vector<vector<int>> result(n, vector(n, 0));
         int i = 1;
         int row = 0; //行
         int col = 0; //列
 ​
         int top = 0; //上边加
         int buttom = n - 1; //下边界
         int left = 0; //左边界
         int right = n - 1; //右边界
         while (i <= n*n)
         {
 ​
             // 从左到右
             while (col <= right)
             {
                 result[row][col++] = i++;
             }
             col--; 
             row++; //换到下一行
             top++; //缩小上边界
 ​
             // 从上到下
             while (row <= buttom)
             {
                 result[row++][col] = i++;
             }
             row--;
             col--; //换到下一列
             right--; //缩小右边界
 ​
             // 从右到左
             while (col >= left)
             {
                 result[row][col--] = i++;
             }
             col++;
             row--; //换到下一行
             buttom--; //缩小下边界
 ​
             // 从下到上
             while (row >= top)
             {
                 result[row--][col] = i++;
             }
             row++;
             col++; //换到下一列
             left++; //缩小下边界
         }
         
         //如果n为奇数单独填充中间的数
         if (n % 2 == 1)
             result[n / 2][n / 2] = n * n;
         return result;
     }
 };
3、总结

注意循环不变量原则,其他的照着模拟过程敲出来就ok

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值