704、二分查找
重点是如何选择区间的定义,这关系到我们的更新逻辑及判断条件。
1、定义左闭右闭区间
因为是左右都可以取到取值,那么 l e f t = = r i g h t left == right left==right是有意义的,那么循环的判断条件则为 l e f t < = r i g h t left <= right left<=right
其次,当 n u m s [ m i d d l e ] ! = t a r g e t nums[middle] != target nums[middle]!=target时,要 l e f t = m i d d l e + 1 left =middle + 1 left=middle+1或者是 r i g h t = m i d d l e − 1 right =middle- 1 right=middle−1,因为当前这个 m i d d l e middle middle的取值已经不是 t a r g e t target target,并且我们的区间是左右都闭合,即左右都可以取到的,那么这个 m i d d l e middle middle再取值就没意义了,那么就可以舍弃掉当前这个 m i d d l e middle middle
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;
if (nums[middle] < target){
left = middle + 1;
}else if (nums[middle] > target){
right = middle - 1;
}else{
return middle;
}
}
return -1;
}
};
2、定义为左闭右开区间
定义区间为:
[
l
e
f
t
,
r
i
g
h
t
)
[left,right)
[left,right),那么
r
i
g
h
t
right
right这个我们是一直取不到取值的,因此循环的判断条件为
l
e
f
t
<
r
i
g
h
t
left < right
left<right,等于已无意义;其次在更新时也要按照闭合和开的区间边界来区分开,因此右边界
r
i
g
h
t
right
right一直不会取到,那么不能取
m
i
d
d
l
e
−
1
middle-1
middle−1否则就会忽略掉
m
i
d
d
l
e
−
1
middle-1
middle−1的判断。
因此更新公式为
r
i
g
h
t
=
m
i
d
d
l
e
right = middle
right=middle 以及
l
e
f
t
=
m
i
d
d
l
e
+
1
left = middle + 1
left=middle+1
其次,初始化的时候由于右边界取不到,因此 r i g h t = n u m s . s i z e ( ) − 1 right = nums.size() - 1 right=nums.size()−1的话会将结尾元素忽略掉,因此取 n u m s . s i z e ( ) nums.size() nums.size()
class Solution {
public:
int search(vector<int>& nums, int target) {
int left = 0;
int right = nums.size(); #注意这里不再是size()-1,因此这个边界我们取不到
while(left < right){
int middle = (left + right) / 2;
if (nums[middle] < target){
left = middle + 1;
}else if (nums[middle] > target){
right = middle;
}else{
return middle;
}
}
return -1;
}
};
能不能定义为左开右闭?
不行!因为这样定义的话 在初始化的时候 l e f t left left就不能等于0,因为这样会由于区间左边是开的,取不到左边界的值,那么就忽略掉 n u m s [ 0 ] nums[0] nums[0]的值。而再往左边走就是 − 1 -1 −1了,整个后面的逻辑就乱了,因此不可以。
35、搜索插入位置
本题跟二分查找类似,增加一个找不到如何返回插入的索引即可
class Solution {
public:
int searchInsert(vector<int>& nums, int target) {
int left = 0;
int right = nums.size() - 1;
int middle;
while (left <= right){
middle = ( left + right ) / 2;
if (nums[middle] < target){
left = middle+1;
}else if(nums[middle] > target){
right = middle-1;
}else{
return middle;
}
}
if (nums[middle] > target){
return middle;
}else{
return middle + 1;
}
}
};
在我的代码中找不到后我进行了大小的判断,从而解决了插入位置索引的问题。而代码随想录中的解决方案更是灵活
class Solution {
public:
int searchInsert(vector<int>& nums, int target) {
int left = 0;
int right = nums.size() - 1;
int middle;
while (left <= right){
middle = ( left + right ) / 2;
if (nums[middle] < target){
left = middle+1;
}else if(nums[middle] > target){
right = middle-1;
}else{
return middle;
}
}
return right + 1
}
};
因为插入到其中只可能有四种情况:
- 插入到所有元素的左边:即 t a r g e t target target比所有元素都小,那么在判断的时候就是 r i g h t right right不断向 l e f t left left靠近,直接 r i g h t = = l e f t = = 0 right == left == 0 right==left==0的时候仍然是 n u m s [ m i d d l e ] > t a r g e t nums[middle] > target nums[middle]>target 因此就 r i g h t = m i d d l e − 1 right = middle - 1 right=middle−1 ,那么此时 r i g h t = = − 1 right == -1 right==−1,而要返回的位置为第一个位置即索引0,因此返回 r i g h t + 1 right + 1 right+1
- 插入到所有元素的右边:即 t a r g e t target target比所有元素都大,那么在判断的时候就是 l e f t left left不断向 r i g h t right right靠近,直接 r i g h t = = l e f t = = n u m s . s i z e ( ) − 1 right == left == nums.size()-1 right==left==nums.size()−1的时候仍然是 n u m s [ m i d d l e ] < t a r g e t nums[middle] < target nums[middle]<target 因此就 l e f t = m i d d l e + 1 left = middle + 1 left=middle+1 ,那么此时 l e f t = n u m s . s i z e ( ) , r i g h t = n u m s . s i z e ( ) − 1 left = nums.size(),right = nums.size()-1 left=nums.size(),right=nums.size()−1,而要返回的位置为最后一个元素后面,即索引位置为 n u m s . s i z e ( ) nums.size() nums.size(),因此返回 r i g h t + 1 right + 1 right+1
- 插入到中间元素的左边:即 t a r g e t target target比当前 m i d d l e middle middle对应的元素小,此时已经是 l e f t = = r i g h t left == right left==right,因此更新为 r i g h t = m i d d l e − 1 right=middle-1 right=middle−1,而正确的插入位置就是 m i d d l e middle middle,因此返回 r i g h t + 1 right+1 right+1
- 插入到中间元素的右边:即 t a r g e t target target比当前 m i d d l e middle middle对应的元素大,此时已经是 l e f t = = r i g h t = = m i d d l e left == right ==middle left==right==middle,因此更新为 l e f t = m i d d l e + 1 left=middle+1 left=middle+1,此时位置为 r i g h t = m i d d l e right=middle right=middle,而正确的插入位置就是 m i d d l e + 1 middle+1 middle+1,因此回 r i g h t + 1 right+1 right+1
34、在排序数据中查找元素的第一个和最后一个位置
该题目一开始的思路是想着初始化数组然后左右边界同时判断,但觉得能力有限逻辑不清楚,因此看了代码随想录的提醒,即左右边界分开求解便思路清晰,具体代码如下:
class Solution {
public:
vector<int> searchRange(vector<int>& nums, int target) {
int rightBorder = getRightBorder(nums,target);
int leftBorder = getRLeftBorder(nums,target);
if (rightBorder == -1 || leftBorder == -1){
return {-1,-1};
}else{
return {leftBorder,rightBorder};
}
}
int getRightBorder(vector<int>& nums,int target){
int rightBorder = -1;
int left = 0;
int right = nums.size()-1;
while(left <= right){
int middle = (left + right) / 2;
if( nums[middle] < target ){
left = middle+1;
}else if( nums[middle] > target ){
right = middle-1;
}else{
rightBorder = middle;
left = middle+1;
}
}
return rightBorder;
}
int getRLeftBorder(vector<int>& nums,int target){
int leftBorder = nums.size();
int left = 0;
int right = nums.size()-1;
while(left <= right){
int middle = (left + right) / 2;
if( nums[middle] < target ){
left = middle+1;
}else if( nums[middle] > target ){
right = middle-1;
}else{
leftBorder = middle;
right = middle-1;
}
}
return leftBorder;
}
};
主要思路是初始化边界都为 − 1 -1 −1,只要其中存在与 t a r g e t target target相同的元素那么就一定能够通过二分法查找到,就一定会修改边界,因此只要返回的两个边界不为 − 1 -1 −1就直接输出两个边界即可。返回的若其中一个是 − 1 -1 −1那么就要输出都是 − 1 -1 −1
其次就是判断边界中我的边界更新条件与代码随想录中的不同,但都是可以通过的。
69、x的平方根
同样是用二分查找,主要是longlong数据类型的使用
class Solution {
public:
int mySqrt(int x) {
int left = 0;
int right = x;
int ans = -1;
while(left <= right){
int middle = (left + right) / 2;
if ((long long) middle * middle <= x){
ans = middle;
left = middle + 1;
}else{
right = middle - 1;
}
}
return ans;
}
};
以下代码是不行的:
int middle = (left + right) / 2;
long long middlePow = middle * middle;
这样会显示溢出,在 m i d d l e ∗ m i d d l e middle*middle middle∗middle的时候,因为是 i n t int int类型,在相乘之后也想要返回一个 i n t int int类型,但是太大的数字在这里就发生了溢出,因此就算是一个 l o n g l o n g long \quad long longlong类型来接收也不行。可以这样做:
int middle = (left + right) / 2;
long long middlePow = (long long)middle * middle;
在返回之前就用一个 ( l o n g l o n g ) (long \quad long) (longlong)来将返回值的类型修改,防止溢出。
367、有效的完全平方数
这部分耶可以用二分查找完成,具体逻辑只是在找到的时候返回值不同而已,找到我们立刻返回一个 t r u e true true,如果正常退出循环即说明没有找到,因此返回 f a l s e false false。
class Solution {
public:
bool isPerfectSquare(int num) {
int left = 0;
int right = num;
while(left <= right){
int middle = (left + right) / 2;
long long middlePow = (long long)middle * middle;
if(middlePow < num){
left = middle + 1;
}else if(middlePow > num){
right = middle - 1;
}else{
return true;
}
}
return false;
}
};