代码随想录60天刷题之数组(Day1、Day2)

目录

一、数组

1、数组理论知识:

2 Day1:

2.1 Leetcode 704二分查找法

2.2 Leetcode27 移除元素,要求空间自由度O(1)

3 Day2

3.1 有序数组的平方Leetcode977

3.2 长度最小的连续子数组Leetcode209

3.3 螺旋矩阵II  Leetcode59

4 数组总结

以前只刷过几十道题,断断续续迷迷糊糊的。

这次工作之余,参与了代码随想录的60天刷题训练营,希望能坚持下去,博客作为记录,也作为以后复习的参考。

由于时间有限,博客略微粗糙,以后有空了再好好整理。

一、数组

1、数组理论知识:

数组是存放在连续内存空间上的相同类型数据的集合。

数组可以方便的通过下标索引的方式获取到下标下对应的数据。

正是因为数组在内存空间的地址是连续的,所以我们在删除或者增添元素的时候,就难免要移动其他元素的地址。

特别注意:数组的元素是不能删的,只能覆盖。

不同编程语言的内存管理是不一样的,以C++为例,在C++中二维数组是连续分布的。

2 Day1:

2.1 Leetcode 704二分查找法

力扣

前提:这道题目的前提是数组为有序数组,同时题目还强调数组中无重复元素,因为一旦有重复元素,使用二分查找法返回的元素下标可能不是唯一的,这些都是使用二分法的前提条件,当大家看到题目描述满足如上条件的时候,可要想一想是不是可以用二分法了。

写二分法,区间的定义一般为两种,左闭右闭即[left, right],或者左闭右开即[left, right)。

选定了一种区间方法,全程要保持统一,切记!!

建议目前还是用左闭右闭吧。

方法1:左闭右闭
   //建议用左闭右闭,好理解一些
   //方法1:左闭右闭写法,while里的若是左闭右闭,那么if else里的也必须是左闭右闭,要保持统一
      int search(vector<int>& nums, int target) {
        int left=0;
        int right=nums.size()-1;
        while(left<=right) 
        //如果没有等号,会把最后一步left=right情况漏掉,也就是少比一个值,如示例中target为5时,没有等号时输出为-1
        {
          int mid=left+(right-left)/2;//不直接写成(left+right)/2是怕分子溢出整型数据
          if(nums[mid]>target)
          {
            right=mid-1;
          }
        else if(nums[mid]<target)
          {
            left=mid+1;
          }
        else
          {
            return mid;
           }   
        }
        return -1;//在right+1=left时退出循环
    }


方法2:左闭右开
 //方法2:左闭右开写法while(left<right)
int search(vector<int>& nums, int target) {
    int left=0;
    int right=nums.size();//左闭右开
    while(left<right)
    {
       int mid=left+(right-left)/2;
       if(nums[mid]>target)
       {
           right=mid; //注意区别
       }
       else if(nums[mid]<target)
       {
           left=mid+1;
       }
       else
       {
           return mid;
       }
    }
    return -1;
    }

2.2 Leetcode27 移除元素,要求空间自由度O(1)

力扣

注意点:

要知道数组的元素在内存地址中是连续的,不能单独删除数组中的某个元素,只能覆盖。

重点掌握双指针的快慢指针法!即方法3。

//(重点)方法3:快慢指针:快指针查找数组中不等于val的元素,慢指针拿到这些值形成新数组,只是要注意两个指针都是在一个数组操作(重点掌握)
    int removeElement(vector<int>& nums, int val) {
    int slow=0;
    int fast=0;
    for(;fast<nums.size();fast++)
    {
      if(nums[fast]!=val)
      {
          nums[slow]=nums[fast];
          slow++; //slow指向 获取数组元素的下一个位置,所以等于新数组长度
      }
    }
    return slow;
  }

//方法1:暴力解法,两个for循环
int removeElement(vector<int>& nums, int val) {
    int n=nums.size();//移动后,size也变了
    for(int i=0;i<n;i++)
    {
        if(nums[i]==val)
        {
            for(int j=i+1;j<n;j++) //用j=i+1不用考虑越界,如果i是最后一个元素,此时j=n,不执行循环
             {
             nums[j-1]=nums[j];// 
            }
        n--;   
        i--;//有可能有连续重复的,这时通过i--要重新查一遍i下标出的值是否等于val 
        }
    }
      return n;
    }*/


//方法2:双指针法(我的思路,一开始少写了个下面的else条件,超出时间限制,原因是程序卡住了,一开始num[left]!=val的情况)
int removeElement(vector<int>& nums, int val) {
    int left=0;
    int right=nums.size()-1;
    int n=nums.size();
    while(left<=right)
    {
       if(nums[left]==val)//相等时
       {
           if(nums[right]!=val)
           {
            nums[left]=nums[right];//左右交换,右值赋给左边
            left++;
            right--;
            n--;
           }
           else
           {
           right--;
           n--;
           }
       }
       else//不相等时
       {
           left++;
       }
    }
       return n;
    }

    
    
 //方法2.1:对向指针,代码随想录解法,用右边界不是val的值来代替左边界是val的值
  int removeElement(vector<int>& nums, int val) {
  int left=0;
  int right=nums.size()-1;
  while(left<=right)
//如果没有等于号,那么会漏掉值检查,比如[1,2,2,3]中的第二个2
  {
      //看左右有没有是不是等于val,确定左右边界
     while(left<=right&&nums[left]!=val)//前面又写了left<=right是为了防止数组越界
     {
         left++; //这个循环结束,left指向左边界第一个是val的值
     }
     while(left<=right&&nums[right]==val)
     {
         right--;
     }
     if(left<right)//这里可以<=,但等于没必要,因为,若果left=right必然满足上面两个while条件的一个,要么等于要么不等于
     //执行完这个if后,left和right是新的了,所以要再次检查,确定左右边界,即执行上面的两个while
      {
         nums[left]=nums[right];//右边界不是val的值代替左边界是val的值
         left++;
         right--;
      }
  }
  return left;//left指向最后一个元素的下一位 
}

3 Day2

3.1 有序数组的平方Leetcode977

力扣

方法1:一开始的思路:遍历数组,平方,可以放到另一个数组,然后sort排序。属于暴力法。

//后来想想,可以双指针slow和fast,在原数组上进行操作,再sort,但时间复杂度不变,空间复杂度为O(1) 

 //看完题解,暴力法直接遍历成原地平方nums[i]*=nums[i]、然后平方即可,不需要指针了。


方法2:对向双指针,需新建数组。
//原则:最大的平方数一定出现在原数组的两侧之一,比较,然后把最大数取出来,移动边界。

//方法1:暴力法
 vector<int> sortedSquares(vector<int>& nums) {
         int slow=0;
         int fast=0;
         for(;fast<nums.size();fast++)
         {
            nums[slow]=nums[fast]*nums[fast];
            slow++;
         }
         sort(nums.begin(),nums.end());//时间复杂度(nlogn)
         return nums;
}


//方法2:对向双指针,需新建数组。
//原则:最大的平方数一定出现在原数组的两侧之一,比较,然后把最大数取出来,移动边界。

  vector<int> sortedSquares(vector<int>& nums) {
        vector<int> v_ret(nums.size(),0);//构造函数初始化,一开始没初始化,报runtime error
        int i=v_ret.size()-1;//新数组最后一个下标位置,从后往前建数组,因为要求从小到大
        int left=0;
        int right=nums.size()-1;
        while(left<=right)//比较到最后剩一个值left==right时就不用比较了,但需要循环结束后手动平方(left<right条件)
        //如果是left=right,发现也可以用循环里的else那个命令给出平方数,此时不用循环结束后手动平方了
        //也可以for(int left=0,right=nums.size()-1;left<=right;) left++和 right--不写,因为不是每一次循环这俩都执行
        {
           if(nums[left]*nums[left]>nums[right]*nums[right])//左侧平方数大
           {
             v_ret[i]=nums[left]*nums[left];
             i--;
             left++;//收缩左边界
           }
           else//右侧大
           {
               v_ret[i]=nums[right]*nums[right];
               i--;
               right--;//收缩右边界
           }
        }
        return v_ret;
  }

3.2 长度最小的连续子数组Leetcode209

力扣

注意滑动窗口的用法。

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

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

  • 窗口内是什么?
  • 如何移动窗口的起始位置?
  • 如何移动窗口的结束位置?

窗口就是 满足其和 ≥ target 的长度最小的连续子数组。

窗口的起始位置如何移动:如果当前窗口的值大于target了,窗口就要向前移动了(也就是该缩小了)。

窗口的结束位置如何移动:窗口的结束位置就是遍历数组的指针,也就是for循环里的索引

//方法1:暴力法,两个for循环,超时
int minSubArrayLen(int target, vector<int>& nums) {
    int ret=INT32_MAX; //设置一个极大值(宏定义),用来比较长度,也是最终要返回的值
    int len=0;//子序列的长度
    for(int i=0;i<nums.size();i++)
    {
       int sum=0;
       for(int j=i;j<nums.size();j++) 
       {
           sum+=nums[j];
           if(sum>=target)//找到了符合条件的子序列,然后统计
           {
               len=j-i+1;
               ret=min(ret,len);//有满足条件的才改变ret的值
               break;//跳出内层循环
           }
       }
    }
    if(ret==INT32_MAX)
    {
        return 0;
    }
    else
    {
        return ret;
    }
   }



//方法2:求最短的子序列长度,滑动窗口:所有元素进一次滑动窗口,再出去一次

    int minSubArrayLen(int target, vector<int>& nums) {

        int ret=INT32_MAX;
        int sum=0;//滑动窗口内的数值之和
        int len=0;//子序列的长度
        int i=0;//窗口的起始位置

        for(int j=0;j<nums.size();j++)//j代表滑动窗口的终点位置
        {
            sum+=nums[j];
            while(sum>=target)//不是if,是while,看刚找到的这个子序列长度是否还能再短;或者说更新了i,先内部检查是否符合
            {
                len=j-i+1; 
                ret=min(ret,len);//更新找到的子序列长度
                sum=sum-nums[i];//收缩左侧边界,先把sum值减去起始位置的值
                i++;
            }
            //跳出while循环时,子序列长度变短,且已经不满足条件了,需要继续向右加num[j]
        }
        // 如果ret没有被赋值的话,就返回0,说明没有符合条件的子序列,其实只有一种情况:所有和加起来都不够,for循环一次到底
        return ret==INT32_MAX?0:ret;
    }

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

不知道怎么用∑∑1计算for+while的时间复杂度??待解决

3.3 螺旋矩阵II  Leetcode59

力扣

 这道题看了会视频反而晕,就没看,直接用以前做过的矩阵顺时针遍历打印方法做的, 应该属于左闭右闭的方法。

二维数组初始化最后老写不对!!!

//模拟方法同顺时针遍历打印数组,上下左右4个边界的处理,算是左闭右闭区间处理
 vector<vector<int>> generateMatrix(int n) {
        vector<vector<int>> v_ret(n,vector<int>(n,0));//这一步的初始化老写不对,注意写法
        int left=0;
        int right=n-1;
        int up=0;
        int down=n-1;
        int k=1;
        while(left<=right&&up<=down)//看到群友发的,写成while(k<=n*n)也行
        {
           for(int i=left;i<=right;i++ )//第一行,或者说上边界,取1 2 3
            {
                v_ret[up][i]=k++;
            }
           up++;//上边界下移
           for(int i=up;i<=down;i++) //取4 5
           {
               v_ret[i][right]=k++;
           }
           right--;//右边界左移
           for(int i=right;i>=left;i--) //取 6 7
           {
               v_ret[down][i]=k++;
           }
           down--;//下边界上移 
           for(int i=down;i>=up;i--) //取8
           {
               v_ret[i][left]=k++;
           }
           left++;
        }
        return v_ret;
}


4 数组总结

感觉双指针还是用的很频繁的,快慢指针/对象指针/滑动窗口等等,基本都是用了两个指针进行不同操作,水挺深,要注意总结思路。

滑动窗口也有些一看就会,一做就废,感觉逻辑有时理不清楚,容易一条错路越走越远。另外要注意滑动窗口求最大连续子数组和最小子连续数组的区别。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值