目录
Day01
题目1
给定一个
n
个元素有序的(升序)整型数组nums
和一个目标值target
,写一个函数搜索nums
中的target
,如果目标值存在返回下标,否则返回-1
。
class Solution {
public int search(int[] nums, int target) {
int low = 0;
int high = nums.length - 1;
while(low <= high)
{
int mid = low + ((high - low) >> 1);
if(nums[mid] > target){
high = mid - 1;
}else if(nums[mid] < target){
low = mid + 1;
}else{
return mid;
}
}
return -1;
}
};
class Solution {
public:
int search(vector<int>& nums, int target)
{
int right=nums.size();
int left=-1;//请注意此种情况我们需要查找的区间是(left,right);
int mid= left + ((right - left) >> 1);//这样写是为了防止int型数据过大导致溢出
while(left+1<right) //当left=right-1时易知区间(left,right)为空,需要在此时跳出循环
{
if(nums[mid]==target) return mid;
else if(nums[mid]>target)//证明答案只可能存在于区间(left,mid)中
right=mid;//将要查找的区间改为(left,mid)
else left=mid;//证明答案只可能存在于区间(mid,right)中,将要查找的区间改为(mid,right)
mid=(right-left)/2+left;
}
return -1;
}
};
总结:C++中string的size与length的区别
在C++的string类中,有两种函数:length和size。他们的作用都是返回字符串的长度
那么,问题来了,他们两者有什么区别?
为了钻研,我们要先找到他们两者的源代码
让我们先找到length的源代码
首先,我们随便定义一个字符串,并调用length
#include <iostream>
#include <string>
using namespace std;
int main(){
string s;
s.length();
return 0;
}
然后,重点来了!按住Ctrl,点击length,就会跳到C++的库文件(如果看不懂,就选中length,右键到实现)
我们就可以看到length的源代码
length() const _GLIBCXX_NOEXCEPT
{ return _M_rep()->_M_length; }
然后,按照同样的步骤找到size的源代码
size() const _GLIBCXX_NOEXCEPT
{ return _M_rep()->_M_length; }
可以看到两者的源代码一摸一样,所以这两者其实没有区别
但是为什么要搞两个呢?
其实string一开始只有length,这是C语言延续下来的习惯
而size则是在出现STL之后,为了兼容加入了和STL容器求长度的函数一个名字的size,这样就可以方便的使用于STL的算法
模板总结
用C++实现二分查找的算法:
模版三步走
1.设置判别函数check
2.根据check的结果来查看区间更新方式
3.查看区间更新方式来决定是否要+1
算法思路:
假设目标值在闭区间[l, r]中, 每次将区间长度缩小一半,当l = r时,我们就找到了目标值。
版本1
当我们将区间[l, r]划分成[l, mid]和[mid + 1, r]时,其更新操作是r = mid或者l = mid + 1;,计算mid时不需要加1。
int bsearch_1(int l, int r)
{
while (l < r)
{
int mid = l + r >> 1;
if (check(mid)) r = mid;
else l = mid + 1;
}
return l;
}
版本2
当我们将区间[l, r]划分成[l, mid - 1]和[mid, r]时,其更新操作是r = mid - 1或者l = mid;,此时为了防止死循环,计算mid时需要加1。
int bsearch_2(int l, int r)
{
while (l < r)
{
int mid = l + r + 1 >> 1;
if (check(mid)) l = mid;
else r = mid - 1;
}
return l;
}
假设有一个总区间,经由我们的 check 函数判断后,可分成两部分,
这边以o作 true,.....作 false 示意较好识别
如果我们的目标是下面这个v,那麽就必须使用模板 1
................vooooooooo
假设经由 check 划分后,整个区间的属性与目标v如下,则我们必须使用模板 2
oooooooov...................
所以下次可以观察 check 属性再与模板1 or 2 互相搭配就不会写错啦
模板1和模板2本质上是根据代码来区分的,而不是应用场景。如果写完之后发现是l = mid,那么在计算mid时需要加上1,否则如果写完之后发现是r = mid,那么在计算mid时不能加1。
需不需要“+1”和check没关系,是根据check结束后所得的结果来判定到底是取上整还是取下整,如果取上整则该段程序都取上整,取下整则都取下整。其最终本质都是为了不重复的划分区间以二分找到最后的边界。详见链接
边界 [left, right] 的二分查找模板
int bSearch(vector<int>& arr, int target) {
int left = 0, right = arr.size() - 1;
while (left <= right) {
// 使用下面代码代替 (left + right) >> 1 防止相加后整型数据溢出
// >> 运算符的优先级比较小,要加括号
int mid = left + ((right - left) >> 1);
if (arr[mid] > target) {
right = mid - 1;
}else if (arr[mid] < target) {
left = mid + 1;
}else {
return mid;
}
}
return -1;
}
边界[left, right)的二分查找模板
注意边界条件,[left, right] 和 [left, right) 在处理 while(left <= right) 和 if (arr[mid] > target) 的时候代码有所不同。
模板返回的是找到满足某个条件下的数的下标,如果没找到返回 -1,可以根据题目适当做改变。
变体一:查找第一个值等于给定值的元素
public int bsearch(int[] a, int n, int value) {
int low = 0;
int high = n - 1;
while (low <= high) {
int mid = low + ((high - low) >> 1);
if (a[mid] > value) {
high = mid - 1;
} else if (a[mid] < value) {
low = mid + 1;
} else {
if ((mid == 0) || (a[mid - 1] != value)) return mid;
else high = mid - 1;
}
}
return -1;
}
变体二:查找最后一个值等于给定值的元素
public int bsearch(int[] a, int n, int value) {
int low = 0;
int high = n - 1;
while (low <= high) {
int mid = low + ((high - low) >> 1);
if (a[mid] > value) {
high = mid - 1;
} else if (a[mid] < value) {
low = mid + 1;
} else {
if ((mid == n - 1) || (a[mid + 1] != value)) return mid;
else low = mid + 1;
}
}
return -1;
}
重点还是第11行代码,如果 a[mid] 这个元素已经是数组中的最后一个元素了,那它肯定是我们要找的;如果或者后一个值不等于给定值,那也说明 a[mid] 就是我们要找的最后一个值等于给定值的元素。
如果我们经过检查之后,发现 a[mid] 后面的一个元素 a[mid+1] 也等于 value,那说明当前的这个 a[mid] 并不是最后一个值等于给定值的元素。我们就更新 low=mid+1,因为要找的元素肯定出现在 [mid+1, high] 之间。
变体三:查找第一个大于等于给定值的元素
public int bsearch(int[] a, int n, int value) {
int low = 0;
int high = n - 1;
while (low <= high) {
int mid = low + ((high - low) >> 1);
if (a[mid] >= value) {
if ((mid == 0) || (a[mid - 1] < value)) return mid;
else high = mid - 1;
} else {
low = mid + 1;
}
}
return -1;
}
第10行,如果 a[mid] 小于要查找的值 value,那要查找的值肯定在 [mid+1, high] 之间,所以,我们更新 low=mid+1。
对于 a[mid] 大于等于给定值 value 的情况,我们要先看下这个 a[mid] 是不是我们要找的第一个值大于等于给定值的元素。如果 a[mid] 前面已经没有元素,或者前面一个元素小于要查找的值 value,那 a[mid] 就是我们要找的元素。这段逻辑对应的代码是第 7 行。
如果 a[mid-1] 也大于等于要查找的值 value,那说明要查找的元素在 [low, mid-1] 之间,所以,我们将 high 更新为 mid-1。
变体四: