编程斐波那契数列

Lately I have been studying algorithms and data structures while trying to prepare for technical interviews. Some of it comes easier than the rest, but I always enjoy a good challenge. I love the whiteboard problems that feel like a puzzle you’re so close to putting together. At some point it just clicks in your brain and you know exactly what you need to do. For example, I really enjoyed my first problem using the Fibonacci sequence. If you are not familiar, a general Fibonacci problem could look something like this:

最近,我在尝试准备技术面试时一直在研究算法和数据结构。 其中有些比其他的要容易一些,但我始终面临着很好的挑战。 我喜欢白板上的问题,感觉就像是一个难题,您很难将它们放在一起。 在某个时候,它只是在您的大脑中发出咔嗒声,您便确切地知道需要做什么。 例如,我真的很喜欢使用斐波那契数列的第一个问题。 如果您不熟悉,一般的斐波那契问题可能看起来像这样:

You have an array with the following values: array = [0,1,1,2,3,5,8,13,21,34]Given any index, n, determine what the value at that index in the array would be, array[n].

At the time, I had not been exposed to the sequence so the problem was very challenging for me to solve. After the challenge was over, I wanted to better understand the solution to the sequence. After some practice, I worked it out to the following:

当时,我还没有接触过这个序列,所以这个问题对我来说很难解决。 挑战结束后,我想更好地了解序列的解决方案。 经过一些练习,我将其解决如下:

The relationship between values in the array is:array[n] = array[n-1] + array[n-2]function fibonacci(n){   if(n <= 2) return 1   return fibonacci(n-1) + fibonacci(n-2)}

I was satisfied with this solution. It made sense to me, passed my tests, and utilized recursion, which I had just learned (if you want a refresher on recursion, check out my blog post here). Later on, I realized there was a problem with my solution. If I ran my solution with a number above 35, it would take a while to return a value, and if I ran it with a number above 50, it would freeze up my computer or crash the application. What was happening? Well, the time complexity for this solution is very bad. Its O(2^n). So as my input grew, the amount of work my computer had to do grew exponentially. I had to reduce the time complexity somehow. This is when I learned about Dynamic Programming.

我对此解决方案感到满意。 这对我来说很有意义,通过了我的测试,并利用了我刚刚学到的递归(如果您想对递归进行复习,请在此处查看我的博客文章)。 后来,我意识到我的解决方案存在问题。 如果我使用大于35的数字运行解决方案,则需要一段时间才能返回值;如果我使用大于50的数字运行解决方案,则会冻结计算机或使应用程序崩溃。 发生了什么事? 嗯,此解决方案的时间复杂度非常差。 它的O(2 ^ n)。 因此,随着输入的增加,计算机必须完成的工作量成倍增加。 我不得不以某种方式降低时间复杂度。 这是我了解动态编程的时候。

Dynamic Programming is a problem solving method that takes advantage of overlapping subproblems and optimal structure in order to reduce the amount of time or work it takes in order to solve a problem. It optimises the solution. How does this apply to Fibonacci? Well, the fibonacci problem has optimal structure and overlapping subproblems, because the same input should always give the same result, and there are repeated subproblems. For example, fibonacci(6) will always return 8. But look how this is calculated through my recursive function:

动态编程是一种解决问题的方法,它利用重叠的子问题和最佳结构来减少解决问题所需的时间或工作量。 它优化了解决方案。 这如何适用于斐波那契? 好吧,斐波那契问题具有最优的结构和重叠的子问题,因为相同的输入应始终给出相同的结果,并且存在重复的子问题。 例如,fibonacci(6)将始终返回8。但是请看一下如何通过我的递归函数计算得出:

fib(6) =fib(5) + fib(4) = fib(4) + fib(3) + fib(3) + fib(2) = fib(3) + fib(2) + fib(2) + fib(1) + fib(2) + fib(1) + 1 =fib(2) + fib(1) + 1 + 1 + 1 + 1 + 1 + 1 =1 + 1 + 1 + 1 + 1 + 1 + 1 + 1 =8

There’s an issue here. The sequence will always return the same number given the same input, so why do I make my computer run fib(4) twice, fib(3) three times, and fib(2) four times? 6 was a very small input, can you imagine how many time I would make my computer run fib(3) if my original input was fib(30). We’re programmers, we don’t like repeating ourselves in our code and we don’t like repeating work either.

这里有个问题。 给定相同的输入,该序列将始终返回相同的数字,那么为什么要让我的计算机运行两次fib(4),fib(3)三次和fib(2)四次? 6是非常小的输入,您能想象如果我的原始输入是fib(30)时,我可以让计算机运行fib(3)多少次。 我们是程序员,我们不喜欢在代码中重复自己,也不喜欢重复工作。

Enter Memoization. Not ‘memorization’. That’s what I initially assumed it was, but that’s not a terrible way to think of it. You are kind of ‘memorizing’ the return value for inputs you’ve already ran, that way, if your computer tries to calculate fib(4) for a second time, like it does when you input fib(6), you stop it from recursively breaking it down all the way again, and just say “Hey you’ve already done this! The answer is 3 remember!?”.

输入备忘。 不是“背诵”。 那就是我最初的想法,但这并不是想到它的糟糕方法。 您有点“记住”已经运行的输入的返回值,这样,如果您的计算机尝试第二次计算fib(4),就像您输入fib(6)一样,就停止它再次递归地将其分解,然后说“嘿,您已经做到了! 答案是3记住!?”。

You have to keep in mind that your computer isn’t magic. It’s computing the answer to a fib(n) call one at a time. So, unlike the code snippet above, where we are reading it left to right in rows, your computer is working more so in columns. It doesn’t look to the next fib(n) call until it has a definitive answer for what the first one is. Let me try to visualize it. If you call fib(6), here’s what your computer will run in order of its call stack, with the bold function being the next step your computer runs on the next line:

您必须记住,您的计算机不是魔术。 它的计算回答一次一个 FIB(N)调用之一 。 因此,与上面的代码段不同,我们在行中从左到右读取它,而在列中,您的计算机工作得更多。 在找到第一个是什么的明确答案之前,它不会寻找下一个fib(n)调用。 让我尝试形象化它。 如果调用fib(6),则计算机将按照调用堆栈的顺序运行, 粗体函数是下一步,计算机将在下一行运行:

fib(6) = fib(5) + fib(4)fib(5) = fib(4) + fib(3)fib(4) = fib(3) + fib(2)fib(3) = fib(2) + fib(1)fib(2) = 1 //YAY we removed a fib call from the stack and bubble upfib(3) = 1 + fib(1) //we now get to run the 2nd fib call from herefib(1) = 1 //another fib call done, we bubble up again!fib(3) = 1 + 1 = 2 //Now those first 2 calls let us finish this callfib(4) = 2 + fib(2) //We solved fib(2) before but are doing it againfib(2) = 1 //same answer we got last time we did fib(2)fib(4) = 2 + 1 = 3 //and now we bubble up again to finish this callfib(5) = 3 + fib(3) //we already solved fib(3) once! Why again?!fib(3) = fib(1) + fib(2) //See how repetitive this is?fib(1) = 1 //same solution we got the first timefib(3) = 1 + fib(2) //have I made my point yet?fib(2) = 1 //This isn't new informationfib(3) = 1 + 1 = 2 //yep, fib(3) still equals 2fib(5) = 3 + 2 = 5 //Finally finish 1/2 of the original problemfib(6) = 5 + fib(4) //you've got to be kidding me...fib(4) = fib(3) + fib(2) //we could've finished this a long time agofib(3) = fib(1) + fib(2) //all of this is not fun to write outfib(1) = 1 //We meet again fib(1)fib(3) = 1 + fib(2) //this is the 4th time we are doing fib(2)fib(2) = 1 //who would've thought!? (sarcasm)fib(3) = 1 + 1 = 2 //dejavufib(4) = 2 + fib(2) //Yeah sure, a 5th time for good measurefib(2) = 1 //I genuinely hope all this is helpful to youfib(4) = 2 + 1 = 3fib(6) = 5 + 3 = 8 //FINALLY WE HAVE AN ANSWER

As you can see, our fib(6) call made our computer run fib(n) 15 times, and only 6 of those fib(n) calls were unique! If we could us memoization (memorization) we could drastically cut down the work and skip almost every repeated fib(n) call:

如您所见,我们的fib(6)调用使我们的计算机运行fib(n)15次,而这些fib(n)调用中只有6个是唯一的! 如果我们可以进行记忆(记忆),则可以大大减少工作量,并跳过几乎所有重复的fib(n)调用:

fib(6) = fib(5) + fib(4)fib(5) = fib(4) + fib(3)fib(4) = fib(3) + fib(2)fib(3) = fib(2) + fib(1)fib(2) = 1 //lets remember thatfib(3) = 1 + fib(1)fib(1) = 1 //lets remember that toofib(3) = 1 + 1 = 2 //Cool, lets remember that as wellfib(4) = 2 + fib(2) //oh yeah! fib(2) = 1fib(4) = 2 + 1 = 3 // sweet lets remember thatfib(5) = 3 + fib(3) //oh yeah! fib(3) = 2fib(5) = 3 + 2 = 5fib(6) = 5 + fib(4) //oh yeah! fib(4) = 3fib(6) = 5 + 3 = 8 //DONE

As you can see, we are drastically cutting down the amount of times we are calling fib(n) by remembering our return values. How do we do something like this? How do we use memoization? We do this by persisting an array of return values through our recursive calls, and checking that array every time in order to see if we have already solved that fib(n) before. How do we know where in the array to store and retrieve our values? We simply store the answer to fib(n) at array[n]. Heres what that looks like:

如您所见,我们通过记住返回值来大幅度减少调用fib(n)的时间。 我们如何做这样的事情? 我们如何使用记忆化? 为此,我们通过递归调用保留一个返回值数组,并每次都检查该数组,以查看以前是否已经解决过fib(n)。 我们如何知道数组中存储和检索值的位置? 我们只需将对fib(n)的答案存储在array [n]处。 如下所示:

function fib(n, memo=[]){  if(memo[n] !== undefined) return memo[n];  if(n <= 2) return 1  let num = fib(n-1, memo) + fib(n-2, memo)  memo[n] = num  return num}

Here’s whats happening. We pass in our array that stores our return values (called ‘memo’ for ‘memoization’) and default it for only the first call as an empty array. Then, we check our memo array to see if we already have an answer stored in its nth spot; if so, we return it. We then return 1 if n is either 1 or 2 just like we originally did. Then, we recursively call fib(n-1) and fib(n-2) like we use to, but this time, passing our persistent memo array along with it, and we set the sum to a variable. We then place that sum in our memo array at the nth spot, and return the sum.

这是怎么回事。 我们传入存储了我们的返回值的数组(对于“ memoization”来说称为“ memo”),并且仅在第一次调用时将其默认为空数组。 然后,检查备忘录数组以查看是否已经在第n个位置存储了答案。 如果是这样,我们将其退回。 然后,如果n等于1或2,则返回1,就像我们最初所做的那样。 然后,我们像以前一样递归调用fib(n-1)和fib(n-2),但是这次,将持久性备忘录数组与其一起传递,并将和设置为变量。 然后,我们将该总和放在备忘录数组的第n个位置,并返回总和。

Photo by Keith Luke on Unsplash
Keith Luke在 Unsplash上 拍摄的照片

So with this solution, any time fib(n) is called where n has already been solved once, memo[n] will already hold the answer, and return it, instead of continuing to recursively call fib(n-1) + fib(n-2). This brings the time complexity from O(2^n) to O(n) which is a lot better. This means where fib(39) use to take my computer over a minute to solve, it now takes less than a second!

因此,使用此解决方案,只要在n已经被解决一次的任何时间调用fib(n),memo [n]就已经保存了答案并返回了答案,而不是继续递归调用fib(n-1)+ fib( n-2)。 这使时间复杂度从O(2 ^ n)到O(n)更好。 这意味着在使用fib(39)花费一分钟时间来解决我的计算机问题时,现在只需不到一秒钟的时间!

Don’t celebrate too soon…. Go ahead and try to run fib(8349) on repl.it. It will return infinity in less that a second. So the time is pretty good, and the returned number is not ideal but hey we’re using javascript and thats the way it is. The real problem is when you add 1 to n…run fib(8350) and you get:

不要过早庆祝…。 继续尝试在repl.it上运行fib(8349)。 它会在不到一秒钟的时间内返回无穷大。 所以时间非常好,返回的数字也不理想,但是嘿,我们正在使用javascript,那就是它的样子。 真正的问题是,当您将n加1时…运行fib(8350)并得到:

RangeError: Maximum call stack size exceeded
Photo by Kristopher Roller on Unsplash
由 Kristopher Roller在 Unsplash上 拍摄的照片

Classic stack overflow. We have way too many recursive calls waiting on our stack and its too much to handle. We’ve been betrayed by memoization. Classic Revenge of the Sith — ‘You were suppose to be the chosen one’.

经典堆栈溢出。 我们在堆栈上等待的递归调用太多,无法处理。 我们被回忆背叛了。 西斯经典复仇-“您原本是被选中的人”。

It’s okay, here comes ‘Tabulation’ to save the day! Tabulation is a process in which you store the results in a table (usually an array) while iterating instead of recursively calling. This saves on space complexity and prevents a stack overflow. Here’s what it looks like:

没关系,这里有“制表”来节省时间! 制表是在迭代过程中而不是递归调用时将结果存储在表(通常是数组)中的过程。 这节省了空间复杂性并防止了堆栈溢出。 看起来是这样的:

function fib(n){  if(n <= 2) return 1  const fibNums = [0,1,1]  for(let i = 3; i <= n; i++){    fibNums[i] = fibNums[i-1] + fibNums[i - 2]  }  return fibNums[n]}

This time, instead of passing an array as an argument, it is assigned in the function just after the edge case. It is initialized with [0,1,1] because we know those values and we need to give our loop something to calculate off of. In our loop, we start i = 3 since we already filled in the array indices 0–2. We then run the loop while i is less than or equal to n. This is key for reaching the nth value correctly. We are now iterating up in the loop, incrementing i each time, instead of recursively breaking down. This ensures we are not recomputing any value, and allows us to return the current value in O(n) time, without causing a stack overflow. So we can now run fib(8350) without issue.

这次,不是在传递数组作为参数的情况下,而是在边缘情况之后在函数中分配了它。 它使用[0,1,1]进行初始化,因为我们知道这些值,并且需要给我们的循环一些计算的依据。 在我们的循环中,由于我们已经填写了数组索引0–2,因此开始i = 3。 然后在i小于 等于 n时运行循环。 这是正确达到第n个值的关键。 现在,我们在循环中迭代,每次递增i,而不是递归分解。 这确保了我们不重新计算任何值,并允许我们以O(n)时间返回当前值,而不会引起堆栈溢出。 因此,我们现在可以毫无问题地运行fib(8350)。

As we get better at problem solving, we are able to identify patterns, and come up with solutions on our own with ease. We may be able to write a solution that passes every test case, but as programmers, thats not where our job ends. We need to care about time and space complexity…which is complex. A great way to try to improve a solution’s time complexity, is to take a dynamic programming approach. Look for smaller problems within your problem. Look for repeated calculations, loops, and function calls. Try to implement memoization when working recursively or tabulation when looping and attempt to improve your BigO. Balance the trade offs between time complexity and space complexity, as one can often aid the other. I hope you found these concepts as interesting as I did, and I hope it sparks another lightbulb next time you’re facing off against another algorithm. Thanks for reading!

随着我们在解决问题方面的能力越来越强,我们能够识别模式,并轻松地自己提出解决方案。 我们也许可以编写一个通过每个测试用例的解决方案,但是作为程序员,那不是我们工作的终点。 我们需要关心时间和空间的复杂性……这是复杂的。 尝试提高解决方案时间复杂度的一种好方法是采用动态编程方法。 在您的问题中寻找较小的问题。 寻找重复的计算,循环和函数调用。 递归工作时尝试实现备忘录,或在循环时尝试列表化,并尝试改进BigO。 在时间复杂度和空间复杂度之间权衡取舍,因为一个通常可以帮助另一个。 我希望您能像我一样发现这些概念很有趣,并且希望您下次遇到另一种算法时,它会激发另一个灯泡。 谢谢阅读!

翻译自: https://medium.com/dev-genius/an-introduction-to-dynamic-programming-through-the-fibonacci-sequence-memoization-and-tabulation-67a8624be61a

编程斐波那契数列


http://www.taodudu.cc/news/show-6329127.html

相关文章:

  • 正四面体公式
  • android usb gadget分析
  • 利用python下载哨兵1号轨道数据
  • Android Qcom USB Driver学习(四)
  • linux kernel X-tranx-Y : Ethernet-to-Gadget
  • Am335x 平台上GSM 3G/4G modem的一些硬件和软件的杂事
  • linux/android系统的USB gadget configfs用户空间配置USB HID U盘 adb dcd等模式的使用
  • matlab基本操作指令总结
  • MATLAB学习一:基本程序结构、控制语句以及 常用命令
  • 通过命令行运行matlab代码
  • 【MATLAB】命令技巧
  • MATLAB常用命令总结
  • Matlab学习-常用命令技巧
  • 为什么matlab的程序都加了分号,还会在命令行窗口不断输出
  • 通过CMD启动MATLAB的同时运行M脚本
  • Matlab中不为人知的强大命令
  • matlab一些指令
  • matlab之常用命令整理(持续更新中...)
  • matlab中dos命令的应用
  • MATLAB计算与常用命令
  • Matlab入门-01命令行操作
  • MATLAB中的常用命令
  • Matlab Tips: 高效实用的快捷命令
  • MATLAB常用指令及解释(持续更新中)
  • matlab命令(应该很全了,欢迎补充!)
  • matlab脚本命令汇总
  • TI官方代码中的任务状态机
  • 编程入门笔记:状态机模式在工控机中的体现
  • GaussDB高斯数据库(SQL语法分类)
  • GaussDB表设计最佳实践

编程斐波那契数列_通过斐波那契序列记忆和制表法进行动态编程的简介相关推荐

  1. python实现斐波那契数列_斐波那契数列:python实现和可视化

    1 说明 ==== 1.1 斐波那契数列的介绍. 1.2 斐波那契数列是上帝的指纹,大自然中随处可见,目前广泛应用到黄金分割线的布局美和股市等预测等等. 1.3 斐波那契数列的Python的matpl ...

  2. 循环斐波那契数列_剑指offer #10 斐波那契数列

    (递归和循环)#10 斐波那契数列 一.斐波那契数列 定义: n = 0 , f(n) = 0 n = 1 , f(n) = 1 n > 1 , f(n) = f(n-1) + f(n-2) 思 ...

  3. c语言斐波那契数列_神奇的数列——斐波那契数列

    斐波那契数列之美 斐波那契是一位数学家,生于公元1170年,籍贯大概是比萨,卒于1240年后.1202年,他撰写了<珠算原理>(Liber Abaci)一书.他是第一个研究了印度和阿拉伯数 ...

  4. python编写递归函数、求斐波那契数列_利用Python实现斐波那契数列的方法实例

    今天我们来使用Python实现递归算法求指定位数的斐波那契数列 首先我们得知道斐波那契数列是什么? 斐波那契数列又叫兔子数列 斐波那契数列就是一个数列从第三项开始第三项的值是第一项和第二项的和依次类推 ...

  5. c语言输出斐波那契数列pta,从斐波那契数列说起

    这段时间在看算法相关的一些东西: 因为算法不好连笔试都过不了(哭,其实算法不仅仅是为了笔试面试,更是为了日后在工作中提高软件的运行效率.这让我联想到了前不久看过的一篇文章:李开复:算法的力量 以前没有 ...

  6. JAVA中打印斐波拉契数列_java打印斐波那契数列

    每行 5 个,输出斐波那契数列的前 20 个数字 6. 编写程序接受用户输入一个... 3 ? 这一公式输出斐波那契数列中的前 40 个数. 保存文件名为:bnds11.java class bnds ...

  7. php菲波那切数列,php实现菲波那切数列和杨辉三角

    1.递归  显示斐波那契数列 function recursion($num){ //判断是否小于0 if($num<0){ return -1; } if($num==1){ return 0 ...

  8. c语言斐波那契数列_斐波那契数列之美

    美妙绝伦的基本算法 Image by Gerd Altmann on Pixabay 在研究和进行有关数据处理,计算相关计算机或数学运算的研究时,我们遇到了很多算法. 即使有时候我们不太喜欢数学,但我 ...

  9. 数字拆分为斐波那契数列_检查数字是否为斐波那契

    数字拆分为斐波那契数列 Description: 描述: We are often used to generate Fibonacci numbers. But in this article, w ...

最新文章

  1. 1920+1080+android三星手机,三星Galaxy Note3能拍摄1080p视频吗?支持1080p播放吗?
  2. 亲身验证切实可行的python项目部署方案
  3. 在家点点接入云信,打造全新社区商业和社交生态
  4. Stas and the Queue at the Buffet
  5. Kubernetes 容器编排
  6. Java多线程学习三十五: CyclicBarrier 和 CountDownLatch 有什么不同
  7. 将相同值的行内容进行合并操作--Sql2005
  8. a jquery 标签点击不跳转_jquery怎么让a标签不跳转?
  9. Android远程推送笔记
  10. 热烈祝贺排名进入前1000
  11. SDK和DDK ?
  12. 笔记本计算机运行程序,这几招让你的笔记本电脑运行速度变快 必学技巧
  13. MVP实现Recy多条目展示
  14. 打开我的收藏夹 -- Python时间序列分析篇
  15. Windows 11 任务栏、菜单栏无故消失解决方案
  16. 身份证号码的正则验证
  17. UML(一)六大关系
  18. 3698: XWW的难题[有源汇上下界最大流]
  19. C# 使用Vlc播放视频或者监控
  20. iCMS的article.admincp.php和content.admincp.php模块存在SQL注入

热门文章

  1. C# 2010 激活码
  2. dSYM文件解析与分析
  3. mysql VS oracle
  4. 微软开源人工智能工具和深度学习框架
  5. python代码桌面壁纸_python设置windows桌面壁纸的实现代码
  6. Mysql出现问题:ERROR 10055:Lost connection to MySQL server at ‘reading initial communication packet‘解决方案
  7. web太极八卦图纯css
  8. 粉丝福利-2019云栖大会学习资料
  9. 学好了Python可以干什么?
  10. BIM设计要做哪些准备工作才能真正完成建筑全生命周期的使命