目录
2.2 Leetcode27 移除元素,要求空间自由度O(1)
以前只刷过几十道题,断断续续迷迷糊糊的。
这次工作之余,参与了代码随想录的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 数组总结
感觉双指针还是用的很频繁的,快慢指针/对象指针/滑动窗口等等,基本都是用了两个指针进行不同操作,水挺深,要注意总结思路。
滑动窗口也有些一看就会,一做就废,感觉逻辑有时理不清楚,容易一条错路越走越远。另外要注意滑动窗口求最大连续子数组和最小子连续数组的区别。