整数二分习题:力扣-35,69(c++代码 含思路详解)

文章详细解析了力扣两道关于二分查找的题目:35题要求在有序数组中搜索目标值并给出插入位置,69题要求求非负整数的平方根。对于35题,重点在于循环结束后如何确定插入位置;对于69题,关键在于理解应找到使平方小于等于原数的最接近的整数。两题都强调了二分查找的应用和细节处理。
摘要由CSDN通过智能技术生成

 目录

1.力扣-35搜索插入位置

第一个要求查找目标值:

接下来看第二个要求: 如果数组中不存在目标值,就把目标值按顺序应该在数组中的插入的位置作为答案输出。

2.力扣-69:x的平方根


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这种写法不是已经避免了+法么?为什么还会溢出呢?求大佬指点

好啦,这道题就到这里啦。


 有问题欢迎指出,非常感谢!!!

也欢迎交流建议奥。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值