LeetCode-二分查找面试题目总结

今天这篇随笔主要是解决前几天在朋友圈分享的《从一道面试题谈谈一线码农应该具备的基本素质》里面的题目,作为一个三线码农,从思考到最后写成,Debug完毕大概用了一个小时。可见自己对基础算法的掌握程度还不够熟练,内功比较薄弱。
题目如下:
实现一个函数, 完成 开根号 的操作, 方法签名如下.
double sqrt(int v, double t)
要求:
1. 不能调用系统库函数, 诸如 Math.sqrt(v) 之类的;
2. 假设计算出的结果为 r, 要求满足如下条件, , 其中 是真实的值, t 为给定的一个误差范围, 例如0.1等, 即你计算出的值要在给定的误差范围内. (哭, 公众号文章里不支持 mathjax)
3. 实现语言不限, 你条件可以比上述更加苛刻, 但不能宽松, 举例而言, 我调用你的接口 sqrt(9, 0.21) 返回值属于 [2.79, 3.21]这个区间的任意一个都满足条件.看到这里, 其实你可以 拿出笔和纸, 尝试解答一下, 强调一下, 一定要注意给定的误差条件, 欢迎沟通交流. 其实这个题目是就是 leetcode 上原题稍加变化得到.
既然不能调用系统库函数,那么给的误差就是最终解决问题的一个关键点。并且最重要的是要摒弃那些你“已经”知道的常识,比如说100开根号就是10,9开根号就是3之类的。因为我们不能调用sqrt,只能尽可能的夹逼到满足误差的情况。
误差的计算公式在题目要求中已经说明,这里我们由于无法提前知道dInput的平方根,所以题目中的结束条件我们用另外一个公式,也就是在下面提到的有可能错误的结束条件 ,毕竟比较好实现。这个结束条件是否正确和t的取值有关,如果t大于1,那么正确,因为t比t的平方要小,反之,不正确。一般情况下,我们输入的误差要求都会是小于1的,那么要将不等式的右边精度进一步提升,因为小数平方后更小,精度更高,设为t的平方。这样一来,不管是什么情况,新的判断条件的精准程度要高于原设精准程度。
至于高多少?一个数量级是没问题的。具体证明略。
现在开始写二分前的第一次first和last的取值。这里采用二分之一的dInput的平方和原值做比较,根据大小决定first和last定位区间。代码如下:

double dFirst = 0;
double dLast = 0;
double dBuf = 0;
double dCompDelt = dDelta * dDelta; //存储当前误差
//首先,判断二分的第一次界限
dBuf = (dInput/2)*(dInput/2);
if (dBuf > dInput)
{
dFirst = 0;
dLast = dInput/2;
}
if (dBuf < dInput)
{
dFirst = dInput/2;
dLast = dInput;
}
//下面这个写法只是为了去掉warning:不是所有的路径都有返回值
if (dBuf == dInput)
{
goto END_PROCESS_FINAL_ANSWER;
}

在第一次写这个部分时,相等的情况直接写了return,但是会发现编译器报警,warning:函数不是所有路径都有返回值。因为函数最外层大括号没有return语句,所以使用了goto语句,把这种情况消除。这也是体现了函数只能有唯一出口的原则。Warning和error并没有什么区别,都是要消除的对象。但这里需要强调的是,VS2008中,调用scanf基本输入函数会报警说这个函数不安全,建议使用scanf_s。如果只是在Windows平台下,可以改,因为scanf_s函数微软自己做的一个安全的输入函数,要求用户显示表明输入数据的长度,防止越界。但是在Linux平台下,没有这个函数。
好,开始最难的地方,通过不停地判断中间值是否符合判断条件,进而对边界值进行修改,这也是这个题目的难点和重点。我在这里就犯傻了,不知道脑子在想什么,把+写成了-,中间值一直算错了……大概是今晚刷盘子刷疯了。

//而后开始二分查找
for (;dFirst!=dLast;)
{
dBuf = ((dLast + dFirst)/2 )* ((dLast + dFirst)/2);
//二分查找关键在于新的左右边界的确定,一定是加号,加号!!
if (dBuf>dInput)
{
if ((dBuf-dInput) > dCompDelt)
{
dLast = (dLast + dFirst)/2;
}
else
{
return (dLast + dFirst)/2;
}
}

    if (dBuf<dInput)
    {
        if ((dInput-dBuf) > dCompDelt)
        {
            dFirst = (dLast + dFirst)/2;
        }
        else
        {
            return (dLast + dFirst)/2;
        }
}

}

其实有心的同志读到这里会发现我的判断写的有点冗长了,尤其是for循环的结束条件,基本没什么用。所以更加精简的写法如下:

//而后开始二分查找
for (;((dBuf-dInput) > dCompDelt)||((dInput-dBuf) > dCompDelt);)
{
dBuf = ((dLast + dFirst)/2 )* ((dLast + dFirst)/2);
//二分查找关键在于新的左右边界的确定,一定是加号,加号!!
if (dBuf>dInput)
{
dLast = (dLast + dFirst)/2;
}

    if (dBuf<dInput)
    {
        dFirst = (dLast + dFirst)/2;
    }

}
return (dLast + dFirst)/2;

这里的判断要是能够调用math.h,直接用fabs()函数即可。所以,代码写完了有机会一定要CodingReview,能够发现很多自己当初没有看到的问题和改进的方法。这往往就是提升性能和写出优美代码的前进之路。
P.S double型变量,双精度浮点型变量对应的格式化输入输出为%lf,这些小知识点自己已经有点忘记了。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值