赞
踩
目录
接下来看第二个要求: 如果数组中不存在目标值,就把目标值按顺序应该在数组中的插入的位置作为答案输出。
嗯。读完题之后,我们发现这是一个查找目标值的题,和之前写过的力扣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这道题就到这里了。
额,感觉这道题题设比较含蓄,不像前面写的,能直接想到二分的(当然可能是我没记住二分的核心)。
如何想到二分呢?我们看题中说已知非负整数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这种写法不是已经避免了+法么?为什么还会溢出呢?求大佬指点
好啦,这道题就到这里啦。
有问题欢迎指出,非常感谢!!!
也欢迎交流建议奥。
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。