目录
接下来看第二个要求: 如果数组中不存在目标值,就把目标值按顺序应该在数组中的插入的位置作为答案输出。
1.力扣-35搜索插入位置
嗯。读完题之后,我们发现这是一个查找目标值的题,和之前写过的力扣704这道题不同的就是,多增加了一个要求:就是如果数组中不存在目标值,就把目标值按顺序应该在数组中的插入的位置作为答案输出。
第一个要求查找目标值:
那么既然要实现的是查找一个数据。那么我们就可以采用704那种直接的二分方法。
1.把while循环条件设置为,数组不为空时一直循环(l<=r)。
2.循环内部,判断语句分三种情况。如果mid所对元素大于目标值,说明应该向左寻找,更新右区间(r=mid-1)。注意既然说了大于了,说明mid的值就不在我们的查找范围内;如果小于目标值,更新左区间(l=mid+1);倘如正好等于,说明mid即为所求,返回即可。
看题中要求。该函数返回一个int类型的数据,所以如上所说,当符合条件时直接renturn即可。
接下来看第二个要求: 如果数组中不存在目标值,就把目标值按顺序应该在数组中的插入的位置作为答案输出。
想实现该操作,就要考虑如何判断不同情况下的插入位置。在这里,我们所采用的这种简单的二分方法的优势就体现出来了。因为当我们循环结束,没有找到目标值,但这个二分仍然是有一个结果的也就是mid。
这里我们要想清楚一件事。
二分的过程要么直接找到了target,要么就是找到了离mid最近的那个元素
因为我们每次二分区间,都是根据mid与目标元素的大小关系来更改区间的。
那这样的话,关于插入位置下标的功能就很好实现了。
l>r是循环出口,那么在l>r的上一步:
l=r继续循环,倘若nums[mid]>target,(这时说明我们所需要的下标应该是mid) r=mid-1导致了l>r,那么l是=mid的。
倘若是因为nums[mid]<target,(这时说明我们所需要的下标应该是mid+1) l=mid+1,导致了l>r,那么l=mid+1的。
所以在while循环结束后,倘若没有return,我们直接return l即可。
我感觉这个是非常巧妙的,我在写的时候,没有想到这个,还一直在分情况啊,判断大小啊啥的。比较蠢hhh。
另一个注意点是注意题中的要求:我们并不需要将他真的插入进去,只是想知道插入的位置。所以我的那种复杂的(应该还实现不了的)写法就或许更没必要(??)了。
代码如下:
// 注意点在函数插入的位置
// 二分的过程要么直接找到了target,要么就是找到了离mid最近的那个元素
#include <iostream>
#include <vector>
using namespace std;
class Solution
{
public:
int searchInsert(vector<int> &nums, int target)
{
int n = nums.size();
if (n == 0)
return 1;
int l = 0, r = n - 1;
while (l <= r)
{
int mid = (l + r) / 2;
if (nums[mid] == target)
return mid;
else if (nums[mid] > target)
r = mid - 1;
else
l = mid + 1;
} // 循环终止的时候 l>r
// 在l>r的上一步,l=r继续循环,倘若nums[mid]>target,这时说明我们所需要的下标应该插到mid处,r=mid-1导致了l>r,那么l是=mid的
// 倘若是因为nums[mid]<target,这时说明我们所需要的下标应该插到mid+1处,l=mid+1,导致了l>r,那么l=mid+1的
return l;
// 数组中不存在这个数,将其插入 我们并不需要将他插入,只是想知道插入的位置
/*蠢蛋emm
l=0,r=n-1;
while(l<r)
{
int mid=l+r>>1;
if(nums[mid]>=target)r=mid;
else l=mid+1;
}
if(l=n-1) return l+1;//末尾插入
else if(l<n-1) return l;//数组中插入
else
*/
}
};
int main()
{
int n;
cin >> n;
vector<int> nums(n);
for (int i = 0; i < n; i++)
{
cin >> nums[i];
}
int target;
cin >> target;
Solution s;
int ans = s.searchInsert(nums, target);
cout << ans << endl;
return 0;
}
(还把那个蠢得留着,是为了提醒我上面的写法是多么巧妙doge)
ok这道题就到这里了。
2.力扣-69:x的平方根
额,感觉这道题题设比较含蓄,不像前面写的,能直接想到二分的(当然可能是我没记住二分的核心)。
如何想到二分呢?我们看题中说已知非负整数x,求根号x。也就是在【0,x】范围内找到一个目标值(根号x)。嗯,想一会理解一下。
那么接下来,我们开始考虑使用哪一个模板。
首先要先确定本题是要查找一个元素(力扣704) 还是 查找满足某一性质的区域(两个模板)?。
我感觉是比较容易误解为找一个数的。那么为什么不是找一个数的呢?因为这道题存在:x并不是完全平方根的 情况,就不可能会有(mid== x/mid)的时候,所以会导致程序出错,也就是你的二分居然没有一个结果??。
排除一个,然后我们就要再看题中的注意点:它说返回类型只保留整数,也就是说像5/2得出结果是2这种意思,也就是应该尽量取小的值。所以我们想一下。我们应该找的是最后一个使得 该数平方之后<=x 的数。即使用模板二。
当然我们可以举个例子:如果x=8,由于开方之后不是整数,取整之后,按照这道题的意思,应该是取2作为答案,这里我们描述一下2这个答案的性质:也就是最后一个满足平方之后<=8的数。现在理解了哈。
你可能会问为什么不能表述成模板一的说法来求解:如果换成求第一个平方之后>=8的数,那么得到的结果应该是3啊,与我们想要的不同,所以不能使用模板一。
好啦,这里理解了就好写了。毕竟模板我们是很熟悉的嘛(应该是把??)。(模板二的注意事项大家不要写错哦)
代码如下
#include <iostream>
using namespace std;
int x;
int main()
{
cin >> x;
int l = 0, r = x;
while (l < r)
{
long long mid = (l + r + 1) / 2;
if (mid <= x / mid) //避免在 mid 很大时发生整数溢出的问题
l = mid;
else
r = mid - 1;
}
cout << l << endl;
return 0;
}
ps:在这段代码中,我们使用了表达式 (l+r+1)/2 来计算区间的中点。由于 l 和 r 的初始值都小于等于x,且x<=2^31-1,因此 l+r+1 的最大值为2^31 - 1 + 2^31 - 1 + 1 = 2^32 - 1,应该用long long 类型定义。
当然按照力扣的答题要求,应该按下面这样写:
#include <iostream>
using namespace std;
class Solution
{
public:
int mySqrt(int x)
{
if (x < 2)
return x;
long long l = 0, r = x; // 避免溢出当l r需要做加法的时候
while (l < r)
{
long long mid = l + (r - l + 1) / 2;
// int mid = (l + r + 1ll) / 2;
// 这里 1ll 的写法是直接把将l+r整个表达式的类型提升为 long long 避免了溢出 倘若这样写,那么前面l r也就不需要定义为longlong了
if (mid <= x / mid)
l = mid;
else
r = mid - 1;
}
return l;
}
};
int main()
{
int x;
Solution s;
int ans = s.mySqrt(x);
cout << ans << endl;
return 0;
}
其实是有个疑问的,为什么 long long mid = l + (r - l + 1) / 2; 这里不定义为 long long 类型会一直报错,显示溢出, l + (r - l + 1) / 2这种写法不是已经避免了+法么?为什么还会溢出呢?求大佬指点
好啦,这道题就到这里啦。
有问题欢迎指出,非常感谢!!!
也欢迎交流建议奥。