704.二分查找
题目链接: link
方法一:[left,right] 左闭右闭区间
class Solution {
public:
int search(vector<int>& nums, int target) {//限定target落在数组里
int left=0;
int right=nums.size()-1;
int middle;
while(left<=right){//right可以取到
middle=(left+right)/2;//等价于 left + ((right - left) >> 1),(left + right)可能会溢出
if(nums[middle]>target)//middle在target的右边,所以改变右边界的值
right=middle-1;//已知middle>target,无需再把middle放在新的区间内
else if(nums[middle]<target)//middle在target的左边,所以改变左边界的值
left=middle+1;//已知middle<target,无需再把middle放在新的区间内
else
return middle;//middle==target
}
return -1;
}
};
方法二:[left,right) 左闭右开区间
class Solution {
public:
int search(vector<int>& nums, int target) {//限定target落在数组里
int left=0;
int right=nums.size();
int middle;
while(left<right){//right取不到,left=right无意义
middle=(left+right-1)/2;
if(nums[middle]<target)//已知middle<target,无需再把middle放在新的区间内
left=middle+1;
else if(nums[middle]>target)
right=middle;//已知middle<target,无需把middle放在新的区间内,但是right本来就取不到,所以直接等于right即可
else
return middle;
}
return -1;
}
};
遇到困难:
1.未完全理解两个方法中right的定义方式
方法一:在 [left,right] 中,right=nums.size()-1
方法二:在 [left,right) 中,right=nums.size()
2.未完全理解两个方法中middle的定义方式
方法一:在 [left,right] 中,middle=(left+right)/2
方法二:在 [left,right) 中,middle=(left+right-1)/2
3.在if语句中直接写了middle>target / middle<target
答疑:
1 & 2.首先left与right代表的是数组下标,数组下标从0开始,而nums代表的仅仅是数组数据中的位数,从1开始;如果数组A中有五个数,则nums.size为5,而数组下标为4
-
对于[left,right] 该区间可以取到right的值,right=nums.size() -1 (eg:5-1=4) ;minddle=(left+right)/2 (eg:[0+4]/2=2,而下标为2在数组中代表第三个数字,即中间值)
-
对于[left,right)该区间取不到right的值,right=nums.size() 即可(eg:[left,right) =[0,5)=[0,4] )相当于默认right取得是nums.size() -1 的值 ; middle=(left+right-1)/2 (right本身就取不到 eg:(0+5-1)/2=2,而下标为2在数组中代表第三个数字,即中间值)
3 .需要比较的是数组中的middle值,所以需要写成nums[middle]>target / nums.[middle]<target
补充(进位运算符):
避免因编译器的不同,造成溢出现象
**右移运算符:**各二进制位全部右移若干位,正数高位补0,负数高位补1,低位丢弃
正数的位运算:12>>2=1100>>2,即1100右移两位,得0011=3
右移>>1相当于一个除以2的十进制运算
(left+right)/2 = left/2+right/2 = left+right/2-left/2 = left+((right-left)/2) = left+((right-left)>>1)
eg:10>>1=1010>>1=0101=5
8>>1=1000>>1=0100=4
11>>1=1011>>1=0101=5
总结(题目思想):
区间的定义就是不变量。要在二分查找的过程中,保持不变量,就是在while寻找中每一次边界的处理都要坚持根据区间的定义来操作,这就是循环不变量规则。
27.移除元素
题目链接: link
方法一:暴力解法
class Solution {
public:
int removeElement(vector<int>& nums, int val) {
int i,j;
int size=nums.size();
for(i=0;i<size;i++){//遍历数组
if(nums[i]==val){//查找数组中等于val的值
for(j=i+1;j<size;j++){//遍历数组
nums[j-1]=nums[j];//进行覆盖操作,用目标元素的后一位元素去覆盖目标元素,不可写成nums[j]=nums[j-1],否则数组溢出
}
i--;//i以后的数值都向前移动了一位,所以i也需要向前移动
size--;//每次循环,数组都前移一次,数组大小减一
}
}
return size;
}
};
遇到困难:
1.只大致了解用两个for循环,一层用来遍历数组,找到等于Val的值,另一层用来实现覆盖操作
2.把if内条件,误写到for循环里
答疑:
一个for循环遍历数组元素 ,第二个for循环更新数组
方法二:双指针法(快慢指针法)
class Solution {
public:
int removeElement(vector<int>& nums, int val) {
int slowIndex=0;//更新原有数组,即创建一个新的数组,指向新数组下标的位置
int fastIndex;//不包含目标元素的数组,即指向新数组所需要的元素
for(fastIndex=0;fastIndex<nums.size();fastIndex++){
if(nums[fastIndex]!=val){
nums[slowIndex++]=nums[fastIndex];//等价于nums[slowIndex] = nums[fastIndex]; slowIndex++;
}
}
return slowIndex;
}
};
遇到困难:
1.未能完全理解fastIndex与slowIndex快慢指针的含义
2.老忘记遍历数组中间的条件
3.把快指针的值赋值给慢指针的时候,忘记递加
答疑:
1.这里的快慢指针并不是真正的指针,可以理解为地址索引,只是fastIndex与slowIndex两个变量用以表示指向地址的位置
2.遍历数组 for(int i=0; inums.size(); i++)
3.nums[slowIndex++]=nums[fastIndex]等价于nums[slowIndex]=nums[fastIndex];slowIndex++;
总结(题目思想):
移除元素的核心在于覆盖,而不是删除。因为数组的元素在内存地址中是连续的,不能单独删除数组中的某个元素,只能覆盖。
2022.12.28
反思:
感觉对于C++很多东西都不熟练,有时候即使知道算法思想,但也写不出代码,对于很多定义比较模糊,比如迭代和递归 / 时间复杂度(目前只会一重循环)和空间复杂度求解 ,想比较C语言与C++的不同,以及java,还未实现,写题目时间过于分散,希望明天能集中注意力,加油!