算法打卡|Day2 数组part02

发布时间 2023-09-22 18:51:56作者: RickRuan

Day1 数组part01


今日任务:数组理论基础,704. 二分查找,27. 移除元素



Problem: 977. 有序数组的平方

思路

首先分析这道题,它是一道有序数组平方后排序的题目。观察平方后数组的特性我们发现,平方后最大的数永远是在数组的前后两端。例如【-1,-2,-3,0,1,2】,平方后去掉符号所以最大的数总是在两侧。
所以本题考虑运用前后双指针算法,一前一后两个指针,比较谁的值大就更新到新的数组中去,直到它们相遇(本题要求新数组从小到大,所以我们从后往前放大数)。

解题方法

双指针算法

复杂度

  • 时间复杂度:

$O(n)$
(补充:快排的时间复杂度:for循环:O(n) ✖️ 快排:O(nlogn))

Code


class Solution {
public:
  vector<int> sortedSquares(vector<int>& nums) {
      int k = nums.size() - 1;
      vector<int> newnums(nums.size(),0);//记住这种初始化操作,可以设置vector长度

      for(int j = nums.size()-1,  i = 0; i<=j;){
          if(nums[i]*nums[i] < nums[j]*nums[j]){
              newnums[k] = nums[j]*nums[j];
              k--;
              j--;
          } else{
              newnums[k] = nums[i]*nums[i];
              k--;
              i++;
          }
      }

      return newnums;

  }
};

Problem: 209. 长度最小的子数组

思路

1.前缀和+二分思路:观察到是求连续子数组的和的问题,我们可以用前缀和。首先我们对整个数组求前缀和数组,然后依次对前缀和数组每一项的值加上目标值去二分搜索不超过这个和的最小值那一项,这样得到的项便对应了连续子数组和的最后一项,我们用这一项减去遍历到的起始坐标,就得到了长度。然后依次遍历,依次比较最小长度即可。
2.双指针思路:其实写滑动窗口不需要背步骤,只需要写的时候问自己四个问题,而这四个问题也正是滑动窗口真正核心的部分。解决了这四个问题,代码也写出来了。

1.增大窗口时,要更新窗口内的什么数据?
要计算窗口内数字的和。

2.什么时候窗口要缩小(shrink)?
当窗口内的和大于等于目标长度时缩小。

3.窗口缩小时,更新窗口内的什么数据?
更新窗口长度,并与之前更新的长度比较得出最小长度。另外由于缩小窗口,在缩小窗口之前要先减去当前left指针所在的数值。

4.什么时候更新结果?
在循环缩小窗口的适合更新最小长度。

链接:https://zhuanlan.zhihu.com/p/142048740

解题方法

双指针算法|前缀和+二分

复杂度

  • 时间复杂度:

滑动窗口:$O(n)$
滑动窗口算法的时间复杂度通常是O(n) 的,其中 n 表示字符串或数组的长度。这是因为滑动窗口算法只需要对每个元素至多遍历一遍,同时也只需要在窗口的左右边界上移动,因此总的操作次数不会超过 2n 次。

前缀和+二分:$O(n+lgn)$

  • 空间复杂度:

$O(1)$

Code1:双指针


class Solution {
public:
  int minSubArrayLen(int target, vector<int>& nums) {
      int n = nums.size();
      int l=0,r=0;
      int minSubArrayLen = INT_MAX;
      int sum=0;
      while(r<n){
          sum += nums[r];
          while(sum>=target){
              int len = r-l+1;
              minSubArrayLen = min(minSubArrayLen,len);
              sum-=nums[l];
              l++;
          }
          r++;
      }
      return (minSubArrayLen == INT_MAX)?0:minSubArrayLen;
  }
};

Code2:前缀和+二分


class Solution {
public:
  int minSubArrayLen(int target, vector<int>& nums) {
      int n = nums.size();
      vector<int> preSum(n + 1, 0);
      for(int i = 1; i <= n; i++) preSum[i] = preSum[i - 1] + nums[i - 1];

      int minSubArrayLen = INT_MAX;

      //每一项遍历搜索
      for(int i = 0; i< n; i++){
          int tar =target+preSum[i];
          int l = i-1;//区间范围搜索
          int r = n+1;//区间范围搜索
          while(l+1 != r){
              int mid =(l+r)/2;
              if(preSum[mid] < tar){
                  l = mid;
              } 
              else r = mid;
          }

          //输出答案时候注意过滤特殊情况
          if(l!=i-1 && r!=n+1 ) minSubArrayLen = min(minSubArrayLen, r - i);
      }
      return minSubArrayLen == INT_MAX ? 0 : minSubArrayLen;
  }
};


Problem: 59. 螺旋矩阵 II

思路

蛇形矩阵问题,我们可以用偏移量控制蛇的姿态,撞墙(越界)或重复走路代表着换方向。另外要注意边界条件,我们判断的是如果沿着当前方向走,下一步走出去了才换方向。

解题方法

利用 int dx[4] = {0, 1, 0, -1}和int dy[4] = {1, 0, -1, 0}去模拟四个方向的偏移量。

复杂度

  • 时间复杂度:

$O(n^2)$,取决于这个数组的大小

  • 空间复杂度:

$O(1)$

Code


class Solution {
public:
  vector<vector<int>> generateMatrix(int n) {
      vector<vector<int>> nums(n, vector<int>(n, 0));
      int x = 0, y = 0;

      //竖着看这是在模拟每个偏移量,分别是向上,向右,向下,向左
      int dx[4] = {0, 1, 0, -1};
      int dy[4] = {1, 0, -1, 0};
      
      //这是在记录方向,0。1。2。3代表取数组不同的方向
      int d = 0;

      for (int i = 1; i <= n * n; i++) {
          nums[x][y] = i;

          //这几步骤可以合并在一起,定义两个新的变量的目的是判断按着原来的方向继续走是否会撞墙或走了重复的路。
          int new_x = x + dx[d];
          int new_y = y + dy[d];
          if (new_x < 0 || new_x >= n || new_y < 0 || new_y >= n || nums[new_x][new_y] != 0) {
              d = (d + 1) % 4;//取模运算是一个周期函数,要记住,换方向就用取模运算,模几代表在几个数之间循环。
          }

          x = x + dx[d];
          y = y + dy[d];
      }
      return nums;
  }
};