python初学者编程指南
编程辅导 (PROGRAMMING TUTORIAL)
Dynamic programming is an art, the more problems you solve easier it gets.
动态编程是一门艺术,您解决的问题越多,它越容易获得。
Sometimes when you write code it might take some time to execute or it may never run even if your logic is fine. The same problem occurred to me while solving Google Foobar challenge questions and I realized that the solution was not optimized and was using all available RAM (for large values).
有时,即使您的逻辑还不错,在编写代码时也可能需要花费一些时间才能执行或永远无法运行。 在解决Google Foobar挑战性问题时,我遇到了同样的问题,我意识到该解决方案并未进行优化,并且正在使用所有可用的RAM(用于大数值)。
An entirely different approach is required to solve such kinds of problems i.e. “optimization of code” by following the concept of dynamic programming.
通过遵循动态编程的概念,需要一种完全不同的方法来解决这类问题,即“代码优化” 。
什么是动态编程? (What is Dynamic Programming?)
Dynamic programming is a terrific approach that can be applied to a class of problems for obtaining an efficient and optimal solution.
动态编程是一种极好的方法,可以将其应用于一类问题,以获取有效且最佳的解决方案。
In simple words, the concept behind dynamic programming is to break the problems into sub-problems and save the result for the future so that we will not have to compute that same problem again. Further optimization of sub-problems which optimizes the overall solution is known as optimal substructure property.
简而言之,动态编程的概念是将问题分解为子问题并保存结果以备将来使用,这样我们就不必再次计算相同的问题。 优化整体解决方案的子问题的进一步优化称为最佳子结构属性。
Two ways in which dynamic programming can be applied:
可以应用动态编程的两种方式:
自顶向下: (Top-Down:)
In this method, the problem is broken down and if the problem is solved already then saved value is returned, otherwise, the value of the function is memoized i.e. it will be calculated for the first time; for every other time, the stored value will be called back. Memoization is a great way for computationally expensive programs. Don’t confuse memoization with memorize.
用这种方法可以解决问题,如果问题已经解决,则返回保存的值,否则将记忆该函数的值,即将首次计算该值。 每隔一段时间,存储的值将被调回一次。 记忆化是计算昂贵的程序的好方法。 不要将记忆与记忆混淆。
Memoize != memorize
记住!=记住
自下而上: (Bottom-Up:)
This is an effective way of avoiding recursion by decreasing the time complexity that recursion builds up (i.e. memory cost because of recalculation of the same values). Here, the solutions to small problems are calculated which builds up the solution to the overall problem. (You will have more clarity on this with the examples explained later in the article).
这是通过减少递归建立的时间复杂度(即,由于重新计算相同值而导致的内存开销)来避免递归的有效方法。 在这里,计算出小问题的解决方案,从而建立了整体问题的解决方案。 (您将在本文稍后解释的示例中对此有更清晰的了解)。
了解在何处使用此技术 (Understanding Where to Use This Technique)
As mentioned above, if you notice that the problem can be broken down into sub-problems and these can be broken into much smaller ones and some of these have overlap (i.e. requires the computation of previously calculated values). The main goal is to optimize the code by reducing the repetition of values by storing the results of sub-problems.
如上所述,如果您注意到问题可以分解为子问题,并且可以分解为小得多的子问题,并且其中一些具有重叠(即需要计算先前计算的值)。 主要目标是通过存储子问题的结果来减少值的重复,从而优化代码。
Dynamic Programming can be applied to any such problem that requires the re-calculation of certain values to reach the final solution.
动态编程可以应用于需要重新计算某些值才能达到最终解决方案的任何此类问题。
递归和动态编程 (Recursion and Dynamic Programming)
Remember, dynamic programming should not be confused with recursion.
请记住,动态编程不应与递归相混淆。
Recursion is a way of finding the solution by expressing the value of a function in terms of other values of that function directly or indirectly and such function is called a recursive function. It follows a top-down approach.
递归是通过直接或间接根据该函数的其他值来表达该函数的值来找到解决方案的方法,这种函数称为递归函数。 它遵循自上而下的方法。
Dynamic programming is nothing but recursion with memoization i.e. calculating and storing values that can be later accessed to solve subproblems that occur again, hence making your code faster and reducing the time complexity (computing CPU cycles are reduced).
动态编程不过是带有记忆的递归,即计算和存储可以稍后访问以解决再次出现的子问题的值,从而使您的代码更快并降低了时间复杂度(减少了计算CPU周期)。
Here, the basic idea is to save time by efficient use of space. Recursion takes time but no space while dynamic programming uses space to store solutions to subproblems for future reference thus saving time.
这里的基本思想是通过有效利用空间来节省时间。 递归需要时间,但是没有空间,而动态编程则使用空间来存储子问题的解决方案以供将来参考,从而节省了时间。
通过示例了解动态编程 (Understanding Dynamic Programming With Examples)
Let’s start with a basic example of the Fibonacci series.
让我们从斐波那契数列的基本示例开始。
Fibonacci series is a sequence of numbers in such a way that each number is the sum of the two preceding ones, starting from 0 and 1.
斐波那契数列是一个数字序列,每个数字都是从0和1开始的两个前一个数字的和。
F(n) = F(n-1) + F(n-2)
F(n)= F(n-1)+ F(n-2)
Recursive method:
递归方法 :
def r_fibo(n):
if n <= 1:
return n
else:
return(r_fibo(n-1) + r_fibo(n-2))
Here, the program will call itself, again and again, to calculate further values. The calculation of the time complexity of the recursion based approach is around O(2^N). The space complexity of this approach is O(N) as recursion can go max to N.
在这里,程序将一次又一次地调用自身,以计算其他值。 基于递归的方法的时间复杂度的计算约为O(2 ^ N)。 此方法的空间复杂度为O(N),因为递归可以最大为N。
For example-
例如-
F(4) = F(3) + F(2) = ((F(2) + F(1)) + F(2) = ((F(1) + F(0)) + F(1)) + (F(1) + F(0))
F(4)= F(3)+ F(2)=((F(2)+ F(1))+ F(2)=((F(1)+ F(0))+ F(1) )+(F(1)+ F(0))
In this method values like F(2) are computed twice and calls for F(1) and F(0) are made multiple times. Imagine the number of repetitions if you have to calculate it F(100). This method is ineffective for large values.
在这种方法中,像F(2)这样的值被计算两次,并且多次调用F(1)和F(0)。 想象一下如果必须计算重复次数F(100)。 此方法对于较大的值无效。
Top-Down Method
自上而下的方法
def fibo(n, memo):
if memo[n] != null:
return memo[n]
if n <= 1:
return n
else:
res = fibo(n-1) + fibo(n+1)
memo[n] = res
return res
Here, the computation time is reduced significantly as the outputs produced after each recursion are stored in a list which can be reused later. This method is much more efficient than the previous one.
在这里,由于每次递归后产生的输出都存储在一个列表中,以后可以重新使用,因此计算时间显着减少。 这种方法比以前的方法效率更高。
Bottom down
自下而上
def fib(n):
if n<=1:
return n
list_ = [0]*(n+1)
list_[0] = 0
list_[1] = 1
for i in range(2, n+1):
list_[i] = list_[i-1] + list[i-2]
return list_[n]
This code doesn’t use recursion at all. Here, we create an empty list of length (n+1) and set the base case of F(0) and F(1) at index positions 0 and 1. This list is created to store the corresponding calculated values using a for loop for index values 2 up to n.
这段代码根本不使用递归。 在这里,我们创建一个长度为(n + 1)的空列表,并在索引位置0和1处设置F(0)和F(1)的基数。创建此列表以使用for循环存储相应的计算值对于索引值2到n。
Unlike in the recursive method, the time complexity of this code is linear and takes much less time to compute the solution, as the loop runs from 2 to n, i.e., it runs in O(n). This approach is the most efficient way to write a program.
与递归方法不同,此代码的时间复杂度是线性的,并且由于循环从2到n,即以O ( n )运行,因此计算解的时间要少得多。 这种方法是编写程序的最有效方法 。
Time complexity: O(n) <<< O(2^N)
时间复杂度:O(n)<<< O(2 ^ N)
Now, let’s see another example (this is an intermediate level problem):
现在,让我们看另一个示例(这是一个中级问题):
Problem statement: You have to build a staircase in such a way that, each type of staircase should consist of 2 or more steps. No two steps are allowed to be at the same height — each step must be lower than the previous one. All steps must contain at least one brick. A step’s height is classified as the total amount of bricks that make up that step.For example, when N = 3, you have only 1 choice of how to build the staircase, with the first step having a height of 2, and the second step having a height of 1 i.e.(2,1). But when N = 5, there are two ways you can build a staircase from the given bricks. The two staircases can have heights (4, 1) or (3, 2).
问题陈述: 您必须以这样一种方式构建楼梯,每种楼梯应包含2个或更多步骤。 不允许两个台阶处于相同的高度-每个台阶都必须低于前一个台阶。 所有步骤必须至少包含一块砖。 台阶的高度归为组成该台阶的砖的总量,例如,当N = 3时,您只有一种选择如何建造楼梯的选项,第一步的高度为2,第二步高度为1 ie(2,1)的台阶。 但是,当N = 5时,有两种方法可以使用给定的砖块构建楼梯。 两个楼梯的高度可以分别为(4,1)或(3,2)。
Write a function called solution(n) that takes a positive integer n and returns the number of different staircases that can be built from exactly n bricks. n will always be at least 3 (so you can have a staircase at all), but no more than 200.
编写一个称为solution(n)的函数,该函数采用一个正整数n,并返回可以从正好n个砖块构建的不同楼梯的数量。 n始终至少为3(因此您可以拥有一个楼梯),但不超过200。
This is a problem I had to solve at level 3 of Google Foobar Challenge. I would suggest you try this question on your own before reading the solution, it will help you understand the concept better.
我必须在Google Foobar Challenge的 第3级上解决这个问题。 我建议您在阅读解决方案之前自行尝试这个问题,这将有助于您更好地理解概念。
An intuitive approach to this problem:
解决此问题的直观方法:
My first intuitive approach was to create a list
l
of integers tilln
.我的第一个直观方法是创建一个直到
n
的整数列表l
。Then append all the possible combinations of integers of list
l
into a new listsol
.然后将列表
l
所有可能的整数组合附加到新的列表sol
。And, at the final step, I used a for loop to check the sum of every element of the list
sol
that if it is equal to the required value. If the condition is true that element is appended to another new listfinal
. And the length offinal
is returned as a final solution to the problem.并且,在最后一步,我使用了for循环来检查列表
sol
中每个元素的和是否等于所需值。 如果条件为true,则将该元素追加到另一个新列表final
。final
的长度将作为问题的最终解决方案返回。
from itertools import combinationsdef solution(n):
l = []
for i in range(1,n):
l.append(i)
sol = []
for k in range(2,len(l)+1):
for m in combinations(l,k):
sol.append(m)
final = []
for z in (sol):
if sum(z) == n :
final.append(z)
steps = len(final)
return (steps)
solution(100)
This code turned out to be very ineffective and didn’t work for large values because of the same reason i.e. hight time complexity and repeated calculations of certain values. Running this code for large values(like 100) will use all available RAM and code will eventually crash.
事实证明,此代码非常无效,并且由于相同的原因(即较高的时间复杂度和某些值的重复计算) 而不适用于较大的值。 以较大的值(例如100)运行此代码将使用所有可用的RAM,并且代码最终将崩溃。
Bottom-up approach for the same problem:
自底向上方法解决同一问题:
def solution(n):
a = [1]+[0]* n
for i in range(1, n+1):
for k in reversed(range(i, n+1)):
a[k] = a[k-i] + a[k]
return a[n] - 1
- At the first step, an empty list ‘a’ is initiated to store all the values from the further loops. 第一步,启动一个空列表“ a”以存储来自其他循环的所有值。
After each iteration of the outer loop, a[j] is the number of staircases you can make with height at most
i
wherej
is the number of bricks used.在外循环的每次迭代之后,a [j]是您最多可以在高度
i
处制作的楼梯数量,其中j
是使用的积木数量。List
a
is initiated to[1,0,0,...]
because there can be only one stair with 0 blocks and 0 height.列表
a
起始于[1,0,0,...]
因为只能有一个台阶,且台阶数为0,高度为0。In each iteration of the inner loop, list
a
is transformed from representing max-heighti-1
to representing max-heighti
, by incorporating the possibility of adding a step of heighti
to any shorter staircase that leaves you with at leasti
blocks.在内部循环的每次迭代中,列表
a
通过将可能增加高度i
的步长加到任何至少使i
块留给您的阶梯上的可能性,将列表a
从表示最大高度i-1
为表示最大高度i
。 。In the final step, the number of different staircases that can be built from exactly
n
bricks is returned by the function (1 is subtracted at the end to exclude the case of single stair of heightn
).在最后一步中,函数可以返回恰好由
n
块砖构成的不同楼梯的数量(最后减去1以排除高度为n
的单个楼梯的情况)。
This method is effective for large values as well since the time complexity is traded for space here.
该方法对于大值也有效,因为在此将时间复杂度交换为空间。
This kind of approach can be applied to other problems as well, you just need to identify them and apply the basics of dynamic programming and you will be able to solve the problems efficiently.
这种方法也可以应用于其他问题,您只需要识别它们并应用动态编程的基础,就能有效解决这些问题。
结论 (Conclusion)
Dynamic programming is a very effective technique for the optimization of code. This technique is really simple and easy to learn however it requires some practice to master.
动态编程是一种用于代码优化的非常有效的技术。 该技术非常简单易学,但是需要掌握一些实践。
“Those who cannot remember the past are condemned to repeat it.”
“那些不记得过去的人应被重述。”
-George Santayana
-乔治·桑塔亚娜(George Santayana)
Bibliography:https://www.educative.io/edpresso/learn-dynamic-programming-in-10-minuteshttps://www.geeksforgeeks.org/dynamic-programming/https://www.hackerearth.com/practice/algorithms/dynamic-programming/introduction-to-dynamic-programming-1/tutorial/https://www.programiz.com/dsa/dynamic-programming
翻译自: https://towardsdatascience.com/beginners-guide-to-dynamic-programming-8eff07195667
python初学者编程指南