Optimal Substructure:
The optimal solution to an instance of the problem contains optimal solutions of some subproblems
Overlapping Subproblems:
Different branches of the computation of an optimal solution require to compute the same subproblem several times
爬楼梯问题:(leetcode 70)
class Solution:
def climbStairs(self, n: int) -> int:
if n==1:
return 1
elif n==2:
return 2
else:
return self.climbStairs(n-2)+self.climbStairs(n-1)
#上面的代码会超时,改一下保存中间结果就可以了 --------------------------------
class Solution:
def climbStairs(self, n: int) -> int:
res = list()
res.insert(0,1)
res.insert(1,2)
for i in range(2, n):
res.insert(i, res[i-1]+res[i-2])
return res[n-1]
打家劫舍:(leetcode 198)
class Solution:
def rob(self, nums: List[int]) -> int:
if not nums: #空列表隐式为False
return 0
if len(nums) == 1:
return nums[0]
l = len(nums)
dp=[0] * l
dp[0]=nums[0]
dp[1]=max(dp[0],nums[1]) #max(nums[0],nums[1])
for i in range(2,l):
dp[i]= max(dp[i-1], dp[i-2]+nums[i])
return max(dp) # dp[-1]
最长上升子序列:(leetcode 300)
class Solution:
def lengthOfLIS(self, nums: List[int]) -> int:
if not nums:
return 0
l = len(nums)
dp = [1] * l
for i in range(1,l):
max_ = 0
for j in range(i-1,-1,-1):
if nums[j] < nums[i] and dp[j] > max_ :
max_ = dp[j]
dp[i] = dp[j]+1
return max(dp)
单词拆分:(leetcode 139)
class Solution:
def wordBreak(self, s: str, wordDict: List[str]) -> bool:
l = len(s)
dp = [False for _ in range(l+1)]
#dp[i]表示以s第i个字符截尾的字符串
#dp[0]初始化为True(前0个字符肯定能被拆分成wordDict中的单词)
dp[0] = True
for i in range(1,l+1):
for j in range(i):
if dp[j] and s[j:i] in wordDict:
dp[i] = True
break
return dp[-1]
不同路径:(leetcode 62)
class Solution:
def uniquePaths(self, m: int, n: int) -> int:
if m ==0 or n==0:
return 0
dp = [[ 0 for _ in range(n)] for _ in range(m)]
for i in range(m):
for j in range(n):
if i == 0 or j == 0:
dp[i][j] = 1
else:
dp[i][j] = dp[i-1][j] + dp[i][j-1]
return dp[m-1][n-1]
不同路径(有障碍):(leetcode 63)
class Solution:
def uniquePathsWithObstacles(self, obstacleGrid: List[List[int]]) -> int:
m=len(obstacleGrid)
n=len(obstacleGrid[0])
if m == 0 or n == 0:
return 0
dp = [[0 for _ in range(n)] for _ in range(m)]
for i in range(m):
for j in range(n):
if obstacleGrid[i][j]==1:
continue
if i == 0 and j == 0:
dp[i][j] = 1
# 不能随意初始化,因为起点可能有障碍
# else:
# dp[i][j] = dp[i-1][j]+dp[i][j-1]
if i > 0:
dp[i][j] += dp[i-1][j]
if j > 0:
dp[i][j] += dp[i][j-1]
return dp[m-1][n-1]
编辑距离:(leetcode72)
class Solution:
def minDistance(self, word1: str, word2: str) -> int:
m, n =len(word1), len(word2)
if m==0:
return n
if n==0:
return m
dp=[[0] * (n+1) for _ in range(m+1)]
for i in range(m+1):
for j in range(n+1):
if i==0:
dp[i][j] = j
elif j==0:
dp[i][j] = i
else:
# insert, delete and replace respectively.
dp[i][j]=min(dp[i][j-1]+1, dp[i-1][j]+1, dp[i-1][j-1]+(word1[i-1]!=word2[j-1]))
return dp[i][j]
Notes
- Loops may achieve a performance gain for your program. Recursion may achieve a performance gain for your programmer
- 理论上递归和迭代时间复杂度方面是一样的,但实际应用中(函数调用和函数调用堆栈的开销)递归比迭代效率要低。
- 因为需要同时保存成千上百个调用记录,所以递归非常耗费内存,递归容易产生"栈溢出"错误(stack overflow)。
- 递归调用是通过栈来实现的,每调用一次函数,系统都将函数当前的变量、返回地址等信息保存为一个栈帧压入到栈中,那么一旦要处理的运算很大或者数据很多,有可能会导致很多函数调用或者很大的栈帧,这样不断的压栈,很容易导致栈的溢出。
- 递归与动态规划:
- 递归:自顶向下,到递归出口时返回计算(明确递归终止条件和递归终止时的解决办法)
- 动态规划:自底向上,利用循环将结果存在数据里(以空间换时间)
- 递归和尾递归:
- 递归调用是整个函数体中最后执行的语句且它的返回值不属于表达式的一部分时,这个递归调用就是尾递归
- 覆盖当前的栈帧而不是在其之上重新添加一个,这样所使用的栈空间就大大缩减了,这使得实际的运行效率会变得更高
- exp:普通递归: return n+recursion(n-1) / 尾递归:return tail_recursion(n-1, total+n)
普通递归:return n * fact(n - 1) / 尾递归:return facttail(n - 1, n *res)
- Python中递归函数的self用法
-
continue:结束当前当次循环,即跳出循环体中还没有执行的语句,但是并不跳出当前循环
break:跳出当前层次的循环,脱离该循环后程序从循环代码后面继续执行