算法纪实|Day1
数组01
理解数组
数组是存放在连续内存空间上的相同类型数据的集合,不难看出数组的一些特点,数组内的元素,数据类型相同;数组存放在连续的存储空间,可以实现随机存取;也是因为存放在连续的存储空间,导致数组元素不能删除只能覆盖。
由此,不难理解,当我们对数组进行删除、增加,就会涉及到其它元素的移动,这也是我们对数组进行运用的一个关键所在。
LeetCode 704-二分查找
二分查找
思考
二分查找也称折半查找,作用于去重的有序序列,一种较为快速的查找方式,主要思路为,每次查找,都与序列中位数mid进行比较,然后舍去不满足条件的一半,在满足条件的半个序列中重复述操作,直至中位数mid等于目标值或者left>right没有目标值。
算法实现
区间为左闭右闭的写法:
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) / 2;//中位数,这样计算也是满足题目要求的
// 如果元素的数值再扩大,为避免溢出,处理如下
int middle = left + ((right - left)>>2);
if(nums[middle] > target){
right = middle -1;//目标值小于中位数,在左侧,[left,middle - 1]
}else if(nums[middle] < target){
left = middle + 1;//目标值大于中位数,在右侧,[middle + 1,right]
}else{
return middle;//目标值等于中位数,返回中位数
}
}
return -1;//没找到目标值
}
};
区间为左闭右开的写法:
class Solution {
public:
int search(vector<int>& nums, int target) {//左闭右开的情况
int left = 0;
int right = nums.size();
while(left < right){
int middle = left + ((right - left)>>2);
if(nums[middle] > target){
right = middle;//目标值小于中位数,在左侧,[left,middle)
}else if(nums[middle] < target){
left = middle + 1;//目标值大于中位数,在右侧,[middle + 1,right)
}else{
return middle;
}
}
return -1;
};
};
时间复杂度:假设总数组长度为n,那么第k次循环过后长度变为n/2(k),不难看出n/2(k)>=1,那么k<=logn,时间复杂度为O(logn)
空间复杂度:创建了三个变量,粗略的认为,空间复杂度为O(1)
总结
中间值的计算,按照题目的范围,正常计算也是满足的,考虑到范围扩大到int的最大值,换成左端点加偏移量的形式可避免溢出;位运算代替除法,可以提高代码的运行效率。
二分查找的就业场景,查找某个数据,且数据列表为去重有序序列。
学习对于数组边界的处理,理解循环不变量规则,初始为左闭右开后续循环内就都是按照左闭右开进行处理。
LeetCode 27-移除元素
思考
不使用额外的数组空间,也就是要求原地删除,放弃拷贝数组删除的想法,那么不妨暴力直接删除,那么难点就是数组理论中说明的,不可删除,只可覆盖,遍历数组找到目标元素,把后续的元素向前移动(此时又是一遍遍历,就是不是从头遍历而已),然后继续遍历。
我们来继续思考一下,可不可以只遍历一遍,假设我们设置两个指针,不是目标元素且两指针指向同一元素时,两指针同时向下遍历,当遇到目标元素时,快指针继续向下遍历,慢指针暂停一轮,如果此时快指针指向的元素不是目标元素,快慢指针元素交换,并同时继续向下遍历;如过快指针指向的是目标元素,慢指针继续暂停一轮,快指针向下遍历,重复上述操作,当快指针遍历完整个数组时,慢指针指向的长度就是删除目标数据后的长度。
算法实现
暴力解
class Solution {//暴力解
public:
int removeElement(vector<int>& nums, int val) {
int len = nums.size();
for(int i = 0; i < len; i++){//第一层循环,寻找需要删除的元素
if(nums[i] == val){
for(int j = i; j < len - 1; j++){//把需要删除的元素都往前移
nums[j] = nums[j + 1];
}
i--;//把nums[i]删除后,需要重新遍历第i个元素
len --;//删除一个元素,长度减一
}
}
return len;
}
};
时间复杂度,不难看出,算法存在两次层嵌套的for循环,第一个循环为n轮,第二个循环为n-i-1轮,时间复杂度为O(n²)
快慢指针法
class Solution {//双指针
public:
int removeElement(vector<int>& nums, int val) {
int fast = 0;
int slow = 0;
for(fast; fast < nums.size(); fast++){//快指针会遍历完整个数组
if(fast == slow && nums[fast] != val){//当快慢指针相等时且指向的元素不是目标元素时,快慢指针同时向后移动
slow++;
}else if(nums[fast] != val){//当快慢指针不相等,且快指针指向不是目标元素时,快慢指针指向元素替换,并同时向后移动
int a = nums[slow];
nums[slow] = nums[fast];
nums[fast] = a;
slow++;
}//当快指针指向元素为目标元素时,快指针向后移动,慢指针不变
}
return slow;
}
};
时间复杂度,只进行了一轮for循环,时间复杂度为O(n)
总结
更深入的理解数组的特性,不能删除,只能覆盖。
算法调优的一种思路,双指针,简化算法的循环轮数,从而提高算法效率。
上述两种算法都保证了元素相对位置不变。
浙公网安备 33010602011771号