算法小白算法刷题记录【一】
文章目录
前言
第一次刷题,第一次发博,知识来源网络,但是用自己理解的话表达。跟着[代码随想录]内容开始 吧。记录自己刷题过程,如果同样有踩坑,提供一种思想吧。【算法小白开始,不高深】
环境:VScode:Leetcode插件。
记录一:二分搜索法
一、什么是二分搜索法?
问题:什么时候要用它?看到什么能想到用二分搜索法?
答:看到“有序数组”、“找特定元素”、“元素不重复”,把第一反应的线性搜索换成二分搜索。
解释:二分:折半的意思,每次划分一半去搜索(字面理解)。因为时间复杂度是O(logn),所以也叫对数搜索算法。
目的:在有序数组中,找一个特定元素A,返回这个元素的位置。
过程:从中间位置开始,和要找的元素A比较。如果A大,去大的那一半找;如果A小,去小的那一半找。(有序作用的体现,不是有序不能用的,要么得先排序)确定好哪一半之后,再找这一半的中间,继续重复过程。最终返回位置。
二、解答过程
1.力扣[704]
给定一个 n 个元素有序的(升序)整型数组 nums 和一个目标值 target ,写一个函数搜索 nums 中的 target,如果目标值存在返回下标,否则返回 -1。
示例 1:
输入: nums = [-1,0,3,5,9,12], target = 9
输出: 4
解释: 9 出现在 nums 中并且下标为 4
示例 2:
输入: nums = [-1,0,3,5,9,12], target = 2
输出: -1
解释: 2 不存在 nums 中因此返回 -1
2.第一动手尝试(不看讲解,没做优化)
全靠自己想,写出来的。没有做优化的代码。先贴算法代码:
class Solution {
public:
int search(vector<int>& nums, int target) {
int size = nums.size();//获得当前元素个数
int middle = floor(size/2);//确定中间索引,左半边的右界,右半边的左界。
int left = 0; //左半边的左界
int right = size-1;//右半边的右界
while((0 <= middle<size) && (right >= left) ){ //最后确定条件
if(nums[middle] == target){ //如果刚好是正中间的元素,直接返回查找
return middle;
}else if(nums[middle] < target){ //进入右半边查找,更新边界
left = middle+1;
}else if(nums[middle] > target){
right = middle -1;
}
middle = left + floor((right-left)/2);
}
return -1;
}
};
写的过程(包含删减的过程,主要体现为什么这样改?):
1.第一步确定左右两半的边界:
一开始:int middle = floor(size/2);
int left = middle;
int right = middle+1;
得到左半边:[0,left]。右半边:[right,size]。(先放着)写到更新边界的时候发现,左半边的0没有办法改,右半边的size没有办法动。所以改成了[left,middle]和[middle,right]。最后如图所示。
2.if条件判断:
else if(nums[middle] < target){ //进入右半边查找,更新边界
left = middle+1;
middle = left + floor((right-left)/2); //注意先写了这行
写到if大于的条件发现middle更新语句一样,所以删去了这里,统一放到了最后。
3.left和right更新
我在这里没有出错。此时我还没有区间是闭区间还是左闭右开区间的概念,但是我认为nums[middle]已经比较过,下一轮不应该再有nums[middle]。
所以right = middle-1;left = middle+1.
4.while条件确定
在最后才确定了while条件,想middle界限应该在vector内;并且right>=left。
为什么有“=”?
答:画图发现当left=right,就剩nums[left]也就是nums[right]没有比较,不能漏掉它,所以得有“=”。
C++运行:
执行代码:
#include <iostream>
#include <vector>
#include <cmath>
using namespace std;
int main(){
vector<int> nums;
int elem;
int target;
Solution search;
cout<<"输入有序数字,空格为间:"<<endl;
do{
cin>>elem;
nums.push_back(elem);
}while(cin.get() != '\n');
cout<<"要查找的目标值:"<<endl;
cin>>target;
int result = search.search(nums,target);
if(result == -1){
cout<<target<<"不存在nums中因此返回"<<result<<endl;
}else{
cout<<target<<"出现在nums中并且下标为"<<result<<endl;
}
return 0;
}
运行结果没有问题。
3.代码随想录学习(改进)
区别:
第一:while()。他没有判断middle,只判断left和right足够。
第二:middle,我先确定每一轮循环的middle,再去while。而他是先进入while,再去求middle。
想一想我要按照他这么改吗?
卡哥讲解记录:
循环不变量:每次while都是找一个区间,也就是说的在左半边还是右半边?
表达:始终坚持区间包含left和right下标的元素(左闭右闭)或者始终坚持区间包含left下标元素但不包含right下标元素(左闭右开)
总结:我在写的时候是左闭右闭,[left,right]。此时有了区间的概念。
第一种[left,right]:
while(left<=right) left=middle+1;right=middle-1;
第二种[left,right):
while(left<right) 初始right=nums.size()。left = middle+1;right=middle;
讲解从区间合不合法确定,更直观可以画图指向元素尝试一下。
总结:不要漏掉元素和target比较,从这个角度看,边界的确定保证每个元素都能被比较到,同时不要重复比较。
改进点:为语句更少,更简洁。
- middle不会越界,所以可以不用放在while中。
- middle只在while内放上面,初始时可以不用写了。
总结
二分搜索记录如上。边界确定好的关键是:不要漏掉元素和target比较,从这个角度看,边界的确定保证每个元素都能被比较到,同时不要重复比较。
(欢迎指正)