由一道题所想到的

        前一篇文章写得其实很匆忙,为了尽可能保证不贻笑大方,要查阅许多资料把许多细节问题搞清楚才敢写出来,没有把握的东西一般不敢写。因此那篇文章很多地方没有展开,比如traits。对于一个严谨认真的人来说,其实现在应该回过头去把前篇文章再修改修改、润色润色,但我的习惯是做的时候尽量严谨,做完就扔到一边,不想回头了。而且现在我的脑子里积了一些东西,迫切地想要把它们写出来,甚至这篇文章刚开头,下篇文章的题材我已经在构思了。

       我从小到大都有个习惯,喜欢预习,不喜欢复习。我很少做笔记,因为记了我也不会看。我学某种知识时,一般会忽略细节,而试图理解其思想或方法,一旦理解了,它们就会留在我的记忆里,也就不太有必要记一大堆笔记了。我的认识是,该理解的该学会的都会记在脑子里,记不住的东西要么是你没有理解,要么是一些死记硬背的东西。我很喜欢爱因斯坦说的:声音的速度是多少?那恐怕要查一查物理辞典。人的大脑不是用来记忆那些已经被发现的知识,而更多地是要去发现新的知识。

        当然,以上全都是我对懒惰的借口,我无意与伟人相提并论,我连人家的一根腿毛都比不上呢。只是说,我有意或无意地养成了这种思维习惯,并且目前来看,我也没准备改变这种习惯,因而想要解释一下,以免使不了解我的人觉得莫名其妙。由于多了这些琐碎的东西,我所写的已经不能算是纯技术或学习方面的文章了,不过这也是我的本意:我不在意形式,只是想要尽可能多地学到一些东西。我没有太多的时间和精力来对文章的结构、内容进行编排和润色(因为还要抽出时间健身、睡觉、玩游戏、吹牛,哈哈),这些文字只是一些简单的随笔性质的东西,将就一下把。好了,现在开始正题。

       那道题叫作Climbing Stairs,本科的时候见到的,前段时间在leetcode上又看到了。这类题并不需要太多复杂的算法,更多地考察的是编程的基本功、思维的清晰度和灵活度等等。不少题目是那种即使做不出来但一看答案就恍然大悟的,不是难到看了答案还要花很大精力甚至看不懂,有时技巧性会强一些,有点类似脑筋急转弯。比如这个题目

You are climbing a stair case. It takes n steps to reach to the top.

Each time you can either climb 1 or 2 steps. In how many distinct ways can you climb to the top?


这道题我的第一反应似乎是递归。大家都知道,递归相对来说,实现简单,只要把递归公式和边界条件设置好就行了,语言本身的栈机制是比较容易实现递归的,然而性能上是不能令人满意的,这是非多项式复杂度算法,无论是时间还是空间。这道题的关键是,如果假设c[i]为爬到第i级台阶的方法数量,则c[i] = c[i-1] + c[i-2] (i>2); 而爬上第一级台阶明显只有一种方法,即c[1] = 1,爬上第二级台阶有两种方法:要么一级一级地爬,要么一次爬两级,因此c[2] = 2.因此这道题可以用迭代或动态规划(Dynamic Programming)来做,最根本的思想是自底向上计算,先考虑c[i-1]和c[i-2],再计算出c[i];而递归先考虑c[i],再向下递归计算c[i-1]和c[i-2],一直递归直到满足边界条件,然后返回,由子问题的解合并成上一级问题的解,这是自顶向下的思想。

       顺便再说一说分治法(Divide and Conquer),DP和DC都是算法思想,而递归既可以说是算法,也可以说是实现手段。DC可以用递归来实现,归并排序(Merge Sort)就是DC的典型应用。爬楼梯这道题之所以用递归做性能会非常差,是因为递归中存在重叠子问题。例如,计算c[10],向下递归计算c[9]和c[8],c[9]又会向下递归计算c[8]和c[7],c[8]向下计算c[7]和[c6];由于递归的每一层调用都是独立的,其参数会作为局部变量独立入栈,即使计算c[8]时c[7]会被计算出来,计算c[9]时仍然要重新计算c[7],它们在递归树上的路径不同,在栈中也是独立分配空间计算(C/C++语言是传值调用(Call by value),传入实参时会在栈上构造局部变量拷贝实参的值,函数返回或退出后局部变量被销毁释放,需要通过全局变量、指针或return语句来操作外部 数据),这就造成了时间和空间上的浪费。而自底向上计算时,子问题已经按序计算出,即计算c[i]时,c[i-1]和c[i-2]都已经计算完毕(其实所有序号小于i的值都已计算出来,c[1]、c[2]为初始值,按照公式依序计算c[3]、c[4]、c[5]...等),子问题只计算一遍,然后保存起来,需要的时候只需直接调用即可而不需重新计算,这就将指数级的复杂度降为线性复杂度。这与备忘录法类似,但备忘录法是自顶向下的。当然,本题其实比较简单,不太需要完整的动态规划算法,直接迭代计算即可。再说白了,忽略问题的具体场景,抽象出其数学本质后,这题其实就是计算斐波那契(Fibonacci)数列,最简单的方法是用矩阵计算,时间复杂度为O(logN)。

       这道题本身并没有什么难的,可能关键代码就一行。我自己也不是专门研究学习算法,没怎么刷过算法题,水平很低,所以关键不在于用了多高深的算法和技巧解出了这道题。我想表达的是,通过这道题,可以联系到很多计算机编程方面的知识,DP、DC、递归、斐波那契数列、函数、自底向上方法等等。很多东西都是相通的,理解它们然后才可以灵活运用。我的记忆力不算坏,但我曾经几乎花了两个月才记住自己的手机号,我想更多的是因为思维习惯的问题吧。自顶向下的学习方法一般来说更自然一些,符合人类认知的过程,大多数人看到电视机都是先了解其外观、功能、使用等,如果有兴趣,才会研究它的构造和原理。我个人的学习方法应该不是纯粹的自顶向下或自底向上,我喜欢先把某样知识或技术的大致框架或原理搞清楚,然后根据需要或兴趣选择某个知识模块深入学习。说起来,似乎偏向自底向上更多些?

       先写到这吧,还是很粗糙,大家多包涵,等把脑子里那些迫切想要“溢“出来的东西写完再看看有没有时间整理修改润色吧。:)

         

 

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值