第一天💪💪💪,编码语言:c++
数据理论基础:
文档讲解:代码随想录数组理论基础
一句话总结:数组是存放在连续空间上的相同类型数据的集合。
特点:· 由于在内存上是连续存放的,地址存在连续性,因此可以通过下标直接读取每个元素。
· 数组的下标都是从0开始的。
注意:
- 数组在进行中间元素插入和删除操作的时候,需要注意移动后面所有的元素。 且插入元素实际上是通过把后面元素后移,再将插入元素覆盖插入下标原有元素,如果数组大小不够,则无法进行插入。删除元素是将后面的元素前移覆盖,数组大小不变,因此最后一个元素实际上和前一个元素相同,如果是自己设计的类,可以通过一个int类型数据size,记录当前数组内有效元素个数。
- c++中的vector类底层实现是数组,但严格讲vector是个容器,它的一系列实现是通过数组来实现的。因此vector的尾部元素插入(push_back())、删除(pop_back())的时间复杂度为O(1),但可以进行中间元素的插入(insert())、删除(erase())的时间复杂度为O(n).
二维数组:增加了一个维度的一维数组,包含两个索引下标。
注意:在c++中二位数组在内存的空间地址上也是连续的。地址从行开始连续,即a[0][0]->a[0][1].
704.二分查找:
文档讲解:代码随想录二分查找
视频讲解:手撕二分查找
题目:注意无重复元素,否则二分法得到的答案可能不唯一
初看:由于是有序序列,可以通过从第一个元素逐个往后比较查找的方式,找到目标元素。也可以通过从中间元素往左右查找的方式,找到目标元素。同时需要处理目标元素不存在的情况。
代码:
//时间复杂度O(logn)
//空间复杂度O(1)
class Solution {
public:
int search(vector<int>& nums, int target) {
int left = 0;
int right = nums.size() - 1;
while (left <= right) {
int mid = left + (right - left)/2; //与直接(left+right)/2相比可防止数值过大越界;
if (nums[mid] == target) return mid;
else if (nums[mid] < target) left = mid + 1; //中间值小于目标值,目标值可能区间在中间值右边且不包含中间值;
else right = mid - 1;//中间值大于目标值,目标值可能区间在中间值左边且不包含中间值;
}
return -1; //当right < left时以为这此时目标值不存在,它可能在当前nums[right]和nums[left]之间
}
};
困难:最初写的时候对于while内循环是写"<"还是写"<=“存在疑问,看完卡哥讲解视频后,理解了不同符号的区间含义。
收获:
- 二分法的比较区间一般有两种分别是左闭右闭[left,right],左闭右开[left,right)。(左不能开,因为左边开的话初始值为-1,可能会存在mid为负数下标的情况,例如left=-1,right=0)
- 确定哪个比较符号符合哪个区间就看最后一次比较。[left,right]区间中,在left=right的情况下,区间仍然成立,因此此时应该再次进入循环,直到right<left才退出循环,因此此时while的比较判断应该是(left <= right);[left,right)区间中,在left = right的情况下,区间已经不成立,此时就需要退出循环,所以while的比较判断应该是(left < right),且由于right不取,因此在第三个判断中应该right = mid。
- 判断循环的条件,其实就是看不变量,在这道题目中区间的定义就是不变量,循环中坚持根据区间的定义来做边界的处理。
27.移除元素:
文档讲解:代码随想录移除元素
视频讲解:手撕移除元素
题目:
初看:两种想法1.遍历数组,找到val值则让后面的元素都往前覆盖(即删除该元素),时间复杂度为O(n^2);2.快慢指针,慢指针保存正确数组元素,快指针遍历数组,当快指针指向val值时跳过,不为val值时则赋给慢指针。
代码:
// 时间复杂度:O(n)
// 空间复杂度:O(1)
class Solution {
public:
int removeElement(vector<int>& nums, int val) {
int slowIndex = 0;
for (int fastIndex = 0; fastIndex < nums.size(); fastIndex++) {
//当nums[fastIndex]不等于val时进行赋值
if (val != nums[fastIndex]) nums[slowIndex++] = nums[fastIndex];
}
return slowIndex;
}
};
另一种方式:左右指针,减少多余赋值操作,时间复杂度,空间复杂度不变
时间复杂度O(n)
空间复杂度O(1)
class Solution {
public:
int removeElement(vector<int>& nums, int val) {
int n = nums.size();
int left = 0;
int right = n - 1;
while (left <= right) {
//找到从左边开始第一个等于val的值
while (left <= right && nums[left] != val) {
left++;
}
//找到右边开始一个不等于val的值
while (left <= right && nums[right] == val) {
right--;
}
//把左边的val值删掉,覆盖为右边的不等于val的值
if (left < right) {
nums[left++] = nums[right--];
}
}
return left;
}
};
收获:
- 学习到了快慢指针的使用方式,重点在于快指针是寻找新数组需要的元素,在此题目中,要寻找的元素就是不等于val的元素;慢指针是更新新数组的下标,使其成为新的数组。
- 再次了解,数组的删除不是直接把元素删掉,而是元素的覆盖,数组的大小本身是不变的,此题目中,用慢指针来指代新数组的大小。
总结
第一天总结:今天的题目是此前刷过的,入门的时候还是很难的,现在看来依旧有所收获,再接再厉💪💪