第一天开始刷题,先看了视频,然后默写,有很多细节的地方出错。之后看题解前,重点关注变量和符号。
数组基础
C++中,数组的地址是连续的,包括二维数组,首地址之间差一个定义长度,比如int差4。地址是十六进制显示。(像Java是没有指针的,同时也不对程序员暴露其元素的地址,寻址操作完全交给虚拟机。所以看不到每个元素的地址情况)
数组的元素不能删除,只能覆盖。
C++中,要注意vector 和 array的区别,vector的底层实现是array,严格来讲vector是容器,不是数组。
704.二分查找:
通过对半缩小区间来实现目标的快速查找。
思路:
先对边界值之外的进行排除,然后对之内的进行筛选
看完代随想录之后的思路:
循环通过左右区间判别来实现。
查找target通过值的大小判断来实现,判断后立即缩小区间。
class Solution {
public:
int search(vector<int>& nums, int target) {
int left=0;
int right=nums.size()-1;
while(left<=right)
{
int middle=left+(right-left)/2;
if(target<nums[middle])
right=middle-1;
else if(target>nums[middle])
left=middle+1;
else
return middle;
}
return -1;
}
};
遇到的困难:
- 语法不熟,size方法的括号没加上,数组的序号用成了方法点上去了
- 变量的变化不敏感,将middle放在了循环外,这样区间就没法变化了;middle应该是左区间加上一半的区间长度,但是却定义成了左加右除以2,这样不变化
- left只是下标值,不是地址,不能写nums.0
- 查找时是用target和middle比较,不是区间边界和middle比较
收获:
第一次刷题,做完、找到错误、执行正确,有了开头,继续坚持下去。
最原始办法的用时和资源消耗不是最顶级,二刷时再看看别的题解。
middle的初始值不能写成(left+right)/2,因为如果right是int最大值,加起来直接就溢出了,所以一定是用left+(right-left)/2。也可以写成((r - l) >> 1) + l
迭代时保证不重不漏。如果left=middle,不是middle+1,那么当target在范围之外时,循环永远出不去。因为到最后right=left+1,根据计算机除2必舍的原则,middle永远等于left,之后一直在这一步卡死。所以迭代时一定保证不重不漏,搜过的一定不能再在下一次的范围中。
重点:
区间开闭的影响
变量变化的影响(是否在循环内、谁去比较)
27.移除元素:
在一个数组中找到目标元素,并将元素剔除出这个数组。
思路
直接删除
看完随想录之后的思路
暴力解法:两个循环,第一个循环查找与target相同的元素,第二个循环将该元素后的所有元素向前移位
class Solution {
public:
int removeElement(vector<int>& nums, int val) {
int size=nums.size();
for(int i=0;i<size;i++)
{
if(nums[i]==val)
{
for(int j=i+1;j<size;j++)
{
nums[j-1]=nums[j];
}
i--;
size--;
}
}
return size;
}
};
双指针解法:只用一个循环,当遇到不同的元素,快指针和慢指针同步更新地址;当遇到相同的元素时,快指针更新,慢指针不变。最后慢指针的数据即为更新数据
class Solution {
public:
int removeElement(vector<int>& nums, int val) {
int slowIndex=0;
for(int fastIndex=0;fastIndex<nums.size();fastIndex++)
{
if(val!=nums[fastIndex])
{
nums[slowIndex++]=nums[fastIndex];
}
}
return slowIndex;
}
};
左右双指针解法:两层循环,外层循环保证内层覆盖一次结束后,继续下一次覆盖任务。内层左循环查找与target相同的元素,内层右循环查找不等于target的元素,然后将右边非剔除值塞到左边剔除值的空位上。最后输出左边序列的下标值就是新数组的长度。
class Solution {
public:
int removeElement(vector<int>& nums, int val) {
int leftIndex = 0;
int rightIndex = nums.size() - 1;
while (leftIndex <= rightIndex) {
// 找左边等于val的元素
while (leftIndex <= rightIndex && nums[leftIndex] != val){
++leftIndex;
}
// 找右边不等于val的元素
while (leftIndex <= rightIndex && nums[rightIndex] == val) {
-- rightIndex;
}
// 将右边不等于val的元素覆盖左边等于val的元素
if (leftIndex < rightIndex) {
nums[leftIndex++] = nums[rightIndex--];
}
}
return leftIndex; // leftIndex一定指向了最终数组末尾的下一个元素
}
};
遇到的困难
1. for循环的第一个变量需要定义:
for(int i=0;;)
2. 数组中下标的自增运算:
第一种:正常定义的数组,其数组名存储的是数组第一个元素的地址,是一个常量指针,赋值或者自增自减运算不合法;
第二种,把一个定义好的数组作为函数的形参时,此时数组名在编译时就会被当作指针变量来处理(形参肯定是一个指针变量,只有指针变量才能存放地址),此时这个形参的赋值或者自增自减运算是合法的;
3. 自增代表的意义模糊不清:
p++:运算规则:先取 p 的值作为表达式的值,然后将 p 后移指向下一个内存单元。
++p:运算规则:将 p 后移指向下一个内存单元,然后取 p 的值作为表达式的值。
这里可能看不出来有什么区别,因为是单独作为语句,放在一起输出对比一下先后++的顺序就知道了
收获
for循环的定义
变量前后自增的效果
重点
数组不能删,只能移位
双指针减少时间复杂度的巧妙思路
指针的自增运算,输出长度得到数组
额外拓展
1. 移位
二分的中间界限写法:
1> mid = l + (r - l) / 2
避免溢出
mid=(l+r)/2中,(l+r)可能会溢出
确保正负数取整方向一致,正负数都是向下取整而不是向零取整
- 区间
[2, 5]
的中点求下界是mid = (2 + 5) / 2 = 3;mid = 2 + (5 - 2) / 2 = 3
- 区间
[-5, 2]
的中点求下界是mid = (-5 + 2) / 2 = -1(有问题,正负数取整方向不一致);mid = -5+(2 -(-5)) / 2 = -2
2> mid = (l + r) >> 1
向右移位约等于除以二
-5 / 2 = -(int)2.5 = -2
,这里是把绝对值变小了,加个负号,结果就变大了。-5 >> 1 = (1011) >> 1 = (1101) = -3
,假设用4-bit
表示一个整数,补码表示。发现结果变小了。
-5 / 2 = -2
,5 / 2 = 2
。这表明除二是向零取整-5 >> 1 = -3
,5 >> 1 = 2
。这表明右移一位是向下取整
2. C++函数:输出数组长度,得到数组
- 数组不用返回:函数的形参使用了引用,类似于指针的效果,直接在内存上对变量进行修改。外部调用变量时,直接从内存拿值,拿到的值就是修改过的。
- 调用该函数的语句类似于(从右上角playground调试中查看):len=removeElement(nums, val); cout<<nums<<endl; 所以输出数组长度,得到的是数组
3. 引用:
给变量起别名:
//数据类型 &别名 = 原名
int a = 10;
int &b = a;
引用定义时,必须使用一个变量来初始化
初始化后不能再重新用其他变量来定义
int a = 10;
int b = 20;
//int &c; //错误,引用必须初始化
int &c = a; //一旦初始化后,就不可以更改
//int &c=b; //错误,初始化后不能用其他变量来定义
c = b; //这是赋值操作,不是更改引用
引用在函数的形参中作用与指针相同,而且语法更加简单
//1. 值传递
void mySwap01(int a, int b) {
int temp = a;
a = b;
b = temp;
}
//2. 地址传递
void mySwap02(int* a, int* b) {
int temp = *a;
*a = *b;
*b = temp;
}
//3. 引用传递
void mySwap03(int& a, int& b) {
int temp = a;
a = b;
b = temp;
}
int main() {
int a = 10;
int b = 20;
mySwap01(a, b);
cout << "a:" << a << " b:" << b << endl;
mySwap02(&a, &b);
cout << "a:" << a << " b:" << b << endl;
mySwap03(a, b);
cout << "a:" << a << " b:" << b << endl;
system("pause");
return 0;
}