二分查找的妙用

问题描述
    上周在一个外文学习网站上看到一个很不错的程序设计题目,描述比较冗长,整整一页全英文描述,对于英文理解能力要求较高,在此就不赘述了。读完本文需要一定的耐心,但是读懂会跟我一样受益匪浅的,题目大概意思是这样的:
    1、一名刚毕业于麻省的学生获得一份工作,年薪为annual_salary,他希望3年之内能买房,房子首付target=250000。
    2、此时他身无分文,他打算将每个月工资的固定比例拿出来存银行,这个比例用rate表示(10进制表 示,保留小数点后4位,比如0.0704就是7.04%,忽视7.041%与7.039%之间的这种误差)银行存款年利率为4%,附加解释:即每个月的钱将增加(4%)/12。
    3、他的工资不是一成不变的,每过6个月,他的工资将会增加原来工资的7%
    4、要求程序设计求出这个最低rate,题目给出了两组输入输出案例作为格式参考要求。
    之所以说这个题目不错,首先在题目理解上相信就已经能给大部分人压力了(但其实外文网站上的题目都这样,有时光是一个赋值语句能搞定的事就扯了一大段专业术语,心理战术)其次一开始会比较无头绪,但是当看完我的解题后,相信你也会觉得这个题目很不错,因为它活跃了我们的思维,很多算法别人叫我们去写,我们或许能够写出来,但是遇到具体问题的时候未必能想到用正确而有效的算法,这道题就是一个不错的例子。

解题思路
    step1:假设我们已知这个rate,我们求员工3年的存款,我们会怎么求?
    step2:将step1中的已知rate,用个自变量x替代,那么我们就可以得到一个求他3年存款的函数,函数参数是自变量x,假设函数为func(x),我们只需要求使func(x)>250000成立的最小值,也就是func(x)-target>0的最小值。
    step3:题目解读到step2相信很多人应该会开始想到用二分查找求函数零点的方式,因为很显然,rate越大,存款越多,func(x)绝对是一个连续的单调递增函数,而二分查找适用于有序的序列中查找。那么问题来了,rate肯定是一个(0,1】之间的小数,如何将一个连续的单调递增函数转化成一个离散的有序序列呢?如何用二分查找呢?
    step4:要用二分查找,自变量的取值必然是整数,将(0,1】区间放大为原来的10000倍,为什么是10000倍呢,因为题目要求保留小数点后4位,并且忽视0.07041和0.07039之间的这种误差,加入我们放大100000倍的话,7041和7039之间必然还可以进行二分,所以此处我们放大10000倍,在区间【1,10000】之间进行二分查找,区间虽然看起来有点大,但是以二分查找log2(n)的效率,问题是不大的。
    下面贴上整个解题代码(python3),已经过详加注释:

annual_salary=int(input("Enter the starting salary: "))
semi_annual_raise=0.07                 #涨薪比例
target=250000                          #存款目标金额

r=0.04                                  #年利率
inc=1+r/12                              #本金增加倍数
s=1+semi_annual_raise                       #每月工资增加倍数  
def func(x):                                #x表示工资中用于存款的比例
    money=annual_salary*(x/120000)   #年薪/12即为月薪,再乘以比例,注意x放大了10000倍
    current_saving=money                     #一个月后存款
    i=2
    while i<37:
        temp=current_saving*inc
        if i%6==1:                          #每过6个月,工资增加
            money*=s
        current_saving=temp+money           #i月后拥有存款
        i=i+1
    return current_saving-target

left=1
right=10000

if func(right)<0:
    print("It is not possible to pay the down payment in three years.")
else:                                     #二分查找求最低rate
    counter=0
    while left<right:
            mid=int((left+right)/2)
            counter+=1                  #计算二分次数
            if func(mid)<0:
                left=mid+1
            if func(mid)>0:
                right=mid-1
    rate=left/10000                    #求的是rate的最小值,取左值
    print("Best savings rate: ",rate)
    print("Steps in bisection search: ",counter)

年薪150000时的运行结果
年薪10000是不可能3年存够首付的
    进行二分查找前,先进行了最值判断,因为如果比例为1还不能存够的话就没必要二分了,直接输出提示信息,当然这也是题目案例要求的。然后加了个counter计数器,来计算二分的次数,来证明一下它的效率还是很高的。

    整个题目最大的收获就是一种思维的灵活跳跃,一步步的推理引导,灵活运用所学方法,妙用二分查找,用程序解决实际问题,测试案例有限,如有任何错误或者不合理之处,请读者随时指出,我是python的初学者,我将及时更正并万分感谢!

  • 3
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值